3

In my project, I use 3 layers with a line geometry. Every line in the layer is a contour with the same depth "valdco".

For operational ease, I color these lines blue, red en orange (as seen in the screenshot).

To color these lines I use rule-based symbology. When I need to change the value of the blue line I open the properties of the layer and change the value for the blue line in the rule, i.e. "valdco"= 'value'.

I would rather like to have a toolbar where I can set the value for the colored line.

My idea is to change back to a simple line and use the data override function on the color of the line:

CASE
 WHEN "valdco" = 'value_set_in_dialogbox1_of_toolbar' THEN BLUE
 WHEN "valdco" = 'value_set_in_dialogbox2_of_toolbar' THEN ORANGE
 WHEN "valdco" = 'value_set_in_dialogbox3_of_toolbar' THEN RED
 ELSE BLACK
END

Question: How do I make a toolbar with 3 dialogboxes where I can set the desired value for the colored lines. Is there a plugin available or a decent tutorial written?

example of colored lines

QT example

I've started to build the mentioned dialogboxes in Qt5 and made the layout as I would like it to have in QGIS. But this is my absolute first time.

What is my next step?

  • Can I make a simple database (with no geom) and then have the values from my plugin write-out to a field in the database?
  • Can I directly reference, the values in the plugin, to the symbology of the chart, so to manipulate the colors?
  • I am drawn to the signal/slot editor but not sure this is the right method.
5
+100

I've coded a simple QgsMessageBar which contains a widget with 3 double-spinbox (and their labels), and a QPushButton for load colors.

One for each color (Blue, Orange and Red).

Each SpinBox have a default value (0), a minimum (0), a maximum (100) and a step (0.5). These values can be easily edited in the code (in the BarWidget class).

The messageBar pop at the top of the Canvas and can be easily removed like all other QGIS messages.

The contour layer name must be : contour_layer. It can be edited at line 66 : layer_name.

The code actually working by paste it in the QGIS Python Console's editor (Ctrl + Alt + P) and click on the green arrow run script green arrowRun Script.

Code bellow :

from qgis.core import QgsProject, QgsSymbolLayer
from qgis.gui import QgsDoubleSpinBox
from PyQt5.QtWidgets import QLabel, QWidget, QHBoxLayout


class ColorSpin(QgsDoubleSpinBox):
    def __init__(self, default, min, max, step, parent=None):
        super(ColorSpin, self).__init__(parent)
        self.setValue(default)
        self.setClearValue(default)
        self.setMinimum(min)
        self.setMaximum(max)
        self.setSingleStep(step)


class BarWidget(QWidget):
    def __init__(self, iface, layer, parent=None):
        super(BarWidget, self).__init__(parent)
        self.iface = iface
        self._vl = layer

        self._spin_blue = ColorSpin(0, 0, 100, 0.5)
        self._label_blue = QLabel("&Blue : ")
        self._label_blue.setAlignment(Qt.AlignRight)
        self._label_blue.setBuddy(self._spin_blue)

        self._spin_orange = ColorSpin(0, 0, 100, 0.5)
        self._label_orange = QLabel("&Orange : ")
        self._label_orange.setAlignment(Qt.AlignRight)
        self._label_orange.setBuddy(self._spin_orange)

        self._spin_red = ColorSpin(0, 0, 100, 0.5)
        self._label_red = QLabel("&Red : ")
        self._label_red.setAlignment(Qt.AlignRight)
        self._label_red.setBuddy(self._spin_red)

        self._valid = QPushButton("Load values")
        self._valid.clicked.connect(self.load_contour_values)

        self._lyt = QHBoxLayout()
        self._lyt.addWidget(self._label_blue)
        self._lyt.addWidget(self._spin_blue)
        self._lyt.addWidget(self._label_orange)
        self._lyt.addWidget(self._spin_orange)
        self._lyt.addWidget(self._label_red)
        self._lyt.addWidget(self._spin_red)
        self._lyt.addWidget(self._valid)
        self.setLayout(self._lyt)

    def load_contour_values(self, checked):
        blue = self._spin_blue.value()
        orange = self._spin_orange.value()
        red = self._spin_red.value()

        exp = (f'CASE WHEN "valdco" = \'{blue}\' THEN color_rgb(0, 0, 255)'
               f' WHEN "valdco" = \'{orange}\' THEN color_rgb(255, 127, 0)'
               f' WHEN "valdco" = \'{red}\' THEN color_rgb(255, 0, 0)'
               ' ELSE color_rgb(0, 0, 0) END')
        fillColorProperty = self._vl.renderer().symbol().symbolLayer(0).dataDefinedProperties().property(QgsSymbolLayer.PropertyFillColor)
        fillColorProperty.setExpressionString(exp)
        fillColorProperty.setActive(True)
        self._vl.triggerRepaint()
        self.iface.messageBar().pushMessage("Colors have been set", Qgis.Success, 1)


layer_name = "contour_layer"
layers = QgsProject.instance().mapLayersByName(layer_name)
if len(layers) == 0:
    iface.messageBar().pushWarning("", f"Layer \"{layer_name}\" is not loaded")
elif len(layers) > 1:
    iface.messageBar().pushWarning("", f"Several layers \"{layer_name}\" are loaded")
elif len(layers) == 1:
    vl = layers[0]
    mybar = BarWidget(iface, vl)
    msgbar = iface.messageBar()
    msgbar.pushWidget(mybar)

It can be integrate in a QGIS plugin code with few additions.

  • A useful answer for those of us who still muddle through PyQGIS coding, especially U.I! From a portability and mainenance point of view, I'd recommend combining this with my answer using layer variables. It would be cleaner to have the CASE WHEN including the valdco encapsulated in the layer settings, referencing thresholds that as variables are also embedded in the layer settings, and then to use Python code and widgets like this to modify variables rather than overwrite the renderer settings. Also more portable, the layer name need not be hardcoded. But quick'n'dirty may beat elegant! – Houska Aug 28 at 10:35
  • 1
    How to avoid layer's name hardcoding ? Two options : load layer with PyQGIS or take the iface.activeLayer. For implement those, it's not so long to modify some of the last lines. My code is a beginning of a QGIS plugin for start to understand the different PyQGIS / Qt components and start to play with. For maintenance, it's better to know the reusability of the tool, if it will be integrated in a more bigger tool, etc. And this code doesn't modify the renderer, just the expression of FillColor. – J. Monticolo Aug 28 at 11:56
  • 1
    @J.Monticolo - Nice script :) – Joseph Aug 29 at 14:41
  • 1
    Thank you all for your answers. I think this can be a powerfull plug-in that will benefit a lot of users. If there is any interest in collaborating to build this plug-in, please let me know – CaptainAhab Aug 31 at 10:18
  • Why not, have you a project page, git, etc ? – J. Monticolo Sep 2 at 7:13
4

You want to style your layer based on three values and you want a quick way to change those values.

One way to get something close would be to create a layer with no geometry and put the three values into it and then dock its Attribute table window.
Create your style expression to get the values from the layer:

WHEN "valdco" =  attribute(get_feature('ContourValues','Id',1),'Value') THEN BLUE

Not as slick as what you envisaged and you would have to do a slight pan or zoom or something to make the map redraw for the styles to update, but very easy. Instead of three rows you could have one row, three columns to maybe make the docked window a little smaller... I just realised if you undock the window it will float on top of the main QGIS window even when it loses focus.

enter image description here

3

As a somewhat klugey solution, you can set project- or layer-level variables for the validation criteria.

For a project-level variable, go to Project / Properties / Variables and use the + button to add a variable named valdco_for_blue or similar and set it to the desired value. The data override function syntax you refer to the variable using @valdco_for_blue, etc. You can then change the variables, and therefore the colouring, in the Project / Properties / Variables dialog when you want.

For a layer-level variable (or if you want even a layer-level override of a project variable), go to the Variables tab (the epsilon) in the Layer Properties dialog instead.

I believe QGIS variables are always stored as strings, so depending on what your validation condition is, you may need to force it to convert it with to_string() for the comparison.

This approach doesn't give you a nice dialog with the specific design you want; you edit the variables in the existing QGIS layer or project settings windows. If you do want to invest the time to do it a prettier way, the most elegant approach would be to use embedded widgets in the layer tree, an underutilized feature (only a opacity slider widget is provided by default). If you want to try this, you will need to write PyQGIS. A good starting point is http://osgeo-org.1560.x6.nabble.com/QGIS-Developer-Using-embedded-widgets-in-the-layer-tree-on-a-python-plugin-td5378284.html and http://www.qgis.nl/2019/02/14/about-layer-tree-embedded-widgets-and-have-your-wmts-always-crispy-sharp/?lang=en (I've meant for some time to write a raster coloring slider widget using this approach but have never gotten around to it.)

Your widget would then refer to (a version of) the dialogs you have designed which would be installed as a plug-in as in the qgis.nl link provided. Of course, rather than the layer tree widget, any sort of infrastructure you've built (plugin?) that can activate your custom dialogs would also work. See How to get layer variables using QGIS Python APIs? how to access and modify the layer variables from PyQGIS and therefore from your dialog.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.