• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nens / ThreeDiToolbox / #2589

19 Sep 2025 08:50AM UTC coverage: 35.01% (-0.1%) from 35.146%
#2589

push

coveralls-python

web-flow
Merge 38792c162 into f6f4be1e7

62 of 260 new or added lines in 40 files covered. (23.85%)

6 existing lines in 5 files now uncovered.

4859 of 13879 relevant lines covered (35.01%)

0.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

15.6
/tool_fraction_analysis/fraction_plot.py
1
import re
1✔
2
from qgis.PyQt.QtGui import QColor
1✔
3

4
import pyqtgraph as pg
1✔
5

6

7
pg.setConfigOption("background", "w")
1✔
8
pg.setConfigOption("foreground", "k")
1✔
9
from qgis.PyQt.QtCore import Qt
1✔
10
from threedi_results_analysis.threedi_plugin_model import ThreeDiPluginModel
1✔
11
from threedi_results_analysis.tool_fraction_analysis.fraction_model import FractionModel
1✔
12

13

14
class FractionPlot(pg.PlotWidget):
1✔
15
    def __init__(self, parent, result_model: ThreeDiPluginModel, fraction_model: FractionModel):
1✔
16
        super().__init__(parent)
×
17
        self.showGrid(True, True, 0.5)
×
18
        self.fraction_model = fraction_model
×
19
        self.result_model = result_model
×
20
        self.item_map = {}
×
21
        self.setLabel("bottom", "Time", "hrs")
×
22
        self.setLabel("left", "Concentration", "")
×
23
        self.getAxis("left").enableAutoSIPrefix(False)
×
24

25
    def clear_plot(self):
1✔
26
        self.clear()
×
27
        self.item_map.clear()
×
28
        self.setLabel("left", "Concentration", "")
×
29

30
    def item_checked(self, model_item):
1✔
NEW
31
        if not self.item_map:  # No plots yet
×
NEW
32
            return
×
33
        substance = model_item.data()
×
34
        for plot in self.item_map[substance]:
×
NEW
35
            plot.setVisible(model_item.checkState() == Qt.CheckState.Checked)
×
36

37
    def item_color_changed(self, color_model_item):
1✔
NEW
38
        if not self.item_map:  # No plots yet
×
NEW
39
            return
×
40

41
        # Retrieve the substance name from the model
NEW
42
        row = color_model_item.index().row()
×
NEW
43
        selected_model_item = color_model_item.model().item(row, 0)
×
NEW
44
        substance = selected_model_item.data()
×
45

NEW
46
        style, color = color_model_item.data()
×
NEW
47
        pen = pg.mkPen(color=QColor(*color), width=2, style=style)
×
NEW
48
        self.item_map[substance][0].setPen(pen)
×
49

NEW
50
        if len(self.item_map[substance]) == 2:
×
51
            # there is a fill, also change that color
NEW
52
            fill_color = self.reduce_saturation(QColor(*color))
×
NEW
53
            self.item_map[substance][1].setBrush(pg.mkBrush(fill_color))
×
54

55
    def highlight_plot(self, row):
1✔
NEW
56
        if not self.item_map:  # No plots yet
×
NEW
57
            return
×
58

NEW
59
        self.unhighlight_plots()
×
60

NEW
61
        hovered_model_item = self.fraction_model.item(row, 0)
×
NEW
62
        substance = hovered_model_item.data()
×
63

NEW
64
        hovered_color_item = self.fraction_model.item(row, 1)
×
NEW
65
        style, _ = hovered_color_item.data()
×
NEW
66
        highlight_color = QColor(255, 141, 161)
×
NEW
67
        pen = pg.mkPen(color=highlight_color, width=4, style=style)
×
NEW
68
        self.item_map[substance][0].setPen(pen)
×
NEW
69
        if len(self.item_map[substance]) == 2:
×
NEW
70
            self.item_map[substance][1].setBrush(pg.mkBrush(highlight_color))
×
71

72
    def unhighlight_plots(self):
1✔
NEW
73
        if not self.item_map:  # No plots yet
×
NEW
74
            return
×
75

NEW
76
        for row in range(self.fraction_model.rowCount()):
×
NEW
77
            substance = self.fraction_model.item(row, 0).data()
×
NEW
78
            style, color = self.fraction_model.item(row, 1).data()
×
79

NEW
80
            pen = pg.mkPen(color=QColor(*color), width=2, style=style)
×
NEW
81
            self.item_map[substance][0].setPen(pen)
×
NEW
82
            if len(self.item_map[substance]) == 2:
×
NEW
83
                fill_color = self.reduce_saturation(QColor(*color))
×
NEW
84
                self.item_map[substance][1].setBrush(pg.mkBrush(fill_color))
×
85

86
    def fraction_selected(self, feature_id, substance_unit: str, time_unit: str, stacked: bool, volume: bool):
1✔
87
        """
88
        Retrieve info from model and create plots
89
        """
90
        self.clear_plot()
×
91

92
        substance_unit_conversion = 1.0
×
93

94
        if volume:
×
95
            # for known units, we apply basic conversion
96
            pattern = r"^(.*)/\s*(m3|l)\s*$"
×
97
            matches = re.findall(pattern, substance_unit, flags=re.IGNORECASE)
×
98
            if len(matches) == 1 and len(matches[0]) == 2:
×
99
                if matches[0][1].lower() == "l":
×
100
                    substance_unit_conversion = 1000.0
×
101
                    processed_substance_unit = matches[0][0].strip()
×
102
                    volume_label = "Load"
×
103
                elif matches[0][1].lower() == "m3":
×
104
                    substance_unit_conversion = 1.0
×
105
                    processed_substance_unit = matches[0][0].strip()
×
106
                    volume_label = "Load"
×
107
            elif substance_unit.strip() == "%":
×
108
                substance_unit_conversion = 1.0
×
109
                processed_substance_unit = "m<sup>3</sup>"
×
110
                volume_label = "Volume"
×
111
            else:  # unknown, take original unit as-is and append x m3
112
                substance_unit_conversion = 1.0
×
113
                processed_substance_unit = f"{substance_unit} ยท m<sup>3</sup>"
×
114
                volume_label = "Load"
×
115

116
        plots = self.fraction_model.create_plots(feature_id, time_unit, stacked, volume, substance_unit_conversion)
×
117
        prev_plot = None
×
NEW
118
        for substance, plot, visible in plots:
×
119
            self.item_map[substance] = [plot]
×
120
            plot.setZValue(100)
×
NEW
121
            plot.setVisible(visible)
×
UNCOV
122
            self.addItem(plot)
×
123

124
            if stacked:
×
125
                # Add fill between consecutive plots
126
                plot_color = plot.opts['pen'].color()
×
127
                # Reduce saturation for fill
128
                fill_color = self.reduce_saturation(plot_color)
×
129
                if not prev_plot:
×
130
                    # this is the first, just fill downward to axis
131
                    plot.setFillLevel(0)
×
132
                    plot.setFillBrush(pg.mkBrush(fill_color))
×
133
                else:
134
                    fill = pg.FillBetweenItem(plot, prev_plot, pg.mkBrush(fill_color))
×
135
                    fill.setZValue(20)
×
NEW
136
                    fill.setVisible(visible)
×
137
                    self.addItem(fill)
×
138
                    self.item_map[substance].append(fill)
×
139

140
            prev_plot = plot
×
141

142
        self.setLabel("left", volume_label if volume else "Concentration", processed_substance_unit if volume else substance_unit)
×
143
        self.plotItem.vb.menu.viewAll.triggered.emit()
×
144

145
    def reduce_saturation(self, plot_color):
1✔
146
        return QColor.fromHsvF(plot_color.hueF(), plot_color.saturationF()/2.0, plot_color.valueF())
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc