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

jjgomera / pychemqt / 8279cc5d-e8e0-481a-b24c-043dfae68499

30 Sep 2025 05:29PM UTC coverage: 72.467% (-0.002%) from 72.469%
8279cc5d-e8e0-481a-b24c-043dfae68499

push

circleci

jjgomera
Fix f-string string delimiter character

28576 of 39433 relevant lines covered (72.47%)

0.72 hits per line

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

9.86
/lib/plot.py
1
#!/usr/bin/python3
2
# -*- coding: utf-8 -*-
3

4
'''Pychemqt, Chemical Engineering Process simulator
5
Copyright (C) 2009-2025, Juan José Gómez Romera <jjgomera@gmail.com>
6

7
This program is free software: you can redistribute it and/or modify
8
it under the terms of the GNU General Public License as published by
9
the Free Software Foundation, either version 3 of the License, or
10
(at your option) any later version.
11

12
This program is distributed in the hope that it will be useful,
13
but WITHOUT ANY WARRANTY; without even the implied warranty of
14
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
GNU General Public License for more details.
16

17
You should have received a copy of the GNU General Public License
18
along with this program.  If not, see <http://www.gnu.org/licenses/>.'''
19

20

21
###############################################################################
22
# Plot library with all matplotlib functionality
23
###############################################################################
24

25

26
from matplotlib import rcParams, rcdefaults, style
1✔
27
from matplotlib.backends import backend_qtagg
1✔
28
from matplotlib.colors import to_rgb
1✔
29
from matplotlib.figure import Figure
1✔
30
from numpy import arange
1✔
31

32
from lib.config import Preferences
1✔
33
from tools.qt import QtWidgets, translate
1✔
34
from UI.widgets import ColorSelector, LineStyleCombo, MarkerCombo
1✔
35

36

37
class PlotWidget(backend_qtagg.FigureCanvasQTAgg):
1✔
38
    """QWidget with matplotlib integration"""
39
    def __init__(self, dim=2, width=15, height=5, dpi=100, parent=None):
1✔
40
        self.fig = Figure(figsize=(width, height), dpi=dpi)
×
41
        super().__init__(self.fig)
×
42

43
        self.dim = dim
×
44
        self.setParent(parent)
×
45
        self.setSizePolicy(
×
46
            QtWidgets.QSizePolicy.Policy.Expanding,
47
            QtWidgets.QSizePolicy.Policy.Expanding)
48

49
        if dim == 2:
×
50
            self.ax = self.fig.add_subplot(111)
×
51

52
        elif dim == 3:
×
53
            self.ax = self.fig.add_subplot(projection="3d")
×
54
            self.ax.mouse_init(rotate_btn=1, zoom_btn=2)
×
55
        # In any other case the axes are not defined and can be configured at
56
        # used site
57

58
    def plot(self, *args, **kwargs):
1✔
59
        """Direct accesst to ax plot procedure"""
60
        self.ax.plot(*args, **kwargs)
×
61

62
    def savePNG(self):
1✔
63
        """Save chart image to png file"""
64
        fmt = "Portable Network Graphics (*.png)"
×
65
        fname, ext = QtWidgets.QFileDialog.getSaveFileName(
×
66
            self, translate("Save chart to file"), "./", fmt)
67
        if fname and ext == fmt:
×
68
            if fname.split(".")[-1] != "png":
×
69
                fname += ".png"
×
70
            self.fig.savefig(fname, facecolor='#fafafa')
×
71

72
    # def plot_3D(self, labels, xdata, ydata, zdata, config=None):
73
        # """Método que dibuja la matriz de datos"""
74
        # self.ax.clear()
75
        # self.data = {"x": xdata[0], "y": ydata[:, 0], "z": zdata}
76

77
        # if config and config.getboolean("MEOS", "surface"):
78
            # self.ax.plot_surface(xdata, ydata, zdata, rstride=1, cstride=1)
79
        # else:
80
            # self.ax.plot_wireframe(xdata, ydata, zdata, rstride=1, cstride=1)
81

82
        # self.ax.set_xlabel(labels[0])
83
        # self.ax.set_ylabel(labels[1])
84
        # self.ax.set_zlabel(labels[2])
85
        # self.ax.mouse_init(rotate_btn=1, zoom_btn=2)
86

87

88
class PlotDialog(QtWidgets.QDialog):
1✔
89
    """QDialog including Plotwidget, navigationtoolbar and a button to close"""
90
    def __init__(self, accept=False, cancel=True, parent=None):
1✔
91
        super().__init__(parent)
×
92
        gridLayout = QtWidgets.QGridLayout(self)
×
93

94
        self.plot = PlotWidget()
×
95
        gridLayout.addWidget(self.plot, 1, 1, 1, 2)
×
96
        self.toolbar = backend_qtagg.NavigationToolbar2QT(self.plot, self.plot)
×
97
        gridLayout.addWidget(self.toolbar, 2, 1)
×
98
        btonBox = QtWidgets.QDialogButtonBox()
×
99
        if accept:
×
100
            btonBox.addButton(QtWidgets.QDialogButtonBox.StandardButton.Ok)
×
101
            btonBox.accepted.connect(self.accept)
×
102
        if cancel:
×
103
            btonBox.addButton(QtWidgets.QDialogButtonBox.StandardButton.Cancel)
×
104
            btonBox.rejected.connect(self.reject)
×
105
        btonBox.setSizePolicy(QtWidgets.QSizePolicy.Policy.Maximum,
×
106
                              QtWidgets.QSizePolicy.Policy.Maximum)
107
        gridLayout.addWidget(btonBox, 2, 2)
×
108

109
    def addText(self, *args, **kwargs):
1✔
110
        """Direct access to ax text procedure"""
111
        self.plot.ax.text(*args, **kwargs)
×
112

113
    def addData(self, *args, **kwargs):
1✔
114
        """Direct access to ax plot procedure"""
115
        self.plot.ax.plot(*args, **kwargs)
×
116

117

118
class ConfPlot(QtWidgets.QDialog):
1✔
119
    """Matplotlib configuration"""
120

121
    RCParams = {
1✔
122
        # Dict with options, the keys are the keyword matplotlib
123
        # Each option must define:
124
        #   - tooltip for widget with a tiny explanation
125
        #   - Widget class to use
126
        #   - Optional parameters
127
        #       - QComboBox: options to populate widget
128
        #       - QSpinBox: two values for define range (default 0-1)
129
        #       - QDoubleSpinBox: two values for define range (default 0-1),
130
        #           one more to define step (default 0.01 for range 0-1 or 1 in
131
        #           other cases) and one more for define decimals (default 2)
132

133
      "axes.facecolor": (translate("plot", "Axes background color"), ColorSelector),
134
      "axes.edgecolor": (translate("plot", "Axes edge color"), ColorSelector),
135
      "axes.linewidth": (translate("plot", "Edge line width"),
136
                         QtWidgets.QDoubleSpinBox, 0, 5, 0.1, 1),
137
      "axes.grid": (translate("plot", "Display grid or not"), QtWidgets.QCheckBox),
138
      "axes.grid.axis": (translate("plot", "Which axis the grid should apply to"),
139
        QtWidgets.QComboBox, "both", "x", "y"),
140
      "axes.grid.which": (translate("plot", "Grid lines at {major, minor, both} ticks"),
141
                          QtWidgets.QComboBox, "both", "major", "minor"),
142
      "axes.titlelocation": (translate("plot", "Alignment of the title"),
143
                             QtWidgets.QComboBox, "left", "right", "center"),
144
      "axes.titlesize": (translate("plot", "Font size of the axes title"),
145
                         QtWidgets.QComboBox, 'xx-small', 'x-small', 'small',
146
                         'medium', 'large', 'x-large', 'xx-large'),
147
      "axes.titleweight": (translate("plot", "Font weight of title"),
148
                           QtWidgets.QComboBox, "normal", "bold"),
149
      "axes.titlecolor": (translate("plot", "Color of axes title"), ColorSelector),
150
      "axes.titley": (translate("plot", "Position title (axes relative units)"),
151
                      QtWidgets.QDoubleSpinBox, 0, 1),
152
      "axes.titlepad": (translate("plot", "Pad between axes and title in points"),
153
                        QtWidgets.QSpinBox, 0, 20),
154
      "axes.labelsize": (translate("plot", "Font size of the x and y labels"),
155
                         QtWidgets.QComboBox, 'xx-small', 'x-small', 'small',
156
                         'medium', 'large', 'x-large', 'xx-large'),
157
      "axes.labelpad": (translate("plot", "Pad between label and axis"),
158
                        QtWidgets.QSpinBox, 0, 20),
159
      "axes.labelweight": (translate("plot", "Weight of the x and y labels"),
160
                           QtWidgets.QComboBox, "normal", "bold"),
161
      "axes.labelcolor": (translate("plot", "Axes label color"), ColorSelector),
162
      "axes.axisbelow": (translate("plot", "Draw axis gridlines and ticks"),
163
                         QtWidgets.QComboBox, "True", "line", "False"),
164
      "axes.formatter.limits": (
165
          translate("plot", "Use scientific notation if log10 of the axis range is "
166
             "larger than this value"),
167
          QtWidgets.QSpinBox, 0, 10),
168
      "axes.formatter.use_locale": (
169
          translate("plot", "Format tick labels according to the user's locale"),
170
          QtWidgets.QCheckBox),
171
      "axes.formatter.use_mathtext": (
172
          translate("plot", "Use mathtext for scientific notation"), QtWidgets.QCheckBox),
173
      "axes.formatter.min_exponent": (
174
          translate("plot", "Minimum exponent to format in scientific notation"),
175
          QtWidgets.QSpinBox, 0, 10),
176
      "axes.formatter.useoffset": (
177
          translate("plot", "The tick label formatter will default to labeling ticks "
178
          "relative to an offset when the data range is small compared to the "
179
          "minimum absolute value of the data."), QtWidgets.QCheckBox),
180
      "axes.formatter.offset_threshold": (
181
          translate("plot", "When useoffset is True, the offset will be used when it can "
182
          "remove at least this number of significant digits from tick labels"),
183
          QtWidgets.QSpinBox, 0, 10),
184
      "axes.spines.left": (translate("plot", "Display axis spines"), QtWidgets.QCheckBox),
185
      "axes.spines.bottom": (translate("plot", "Display axis spines"), QtWidgets.QCheckBox),
186
      "axes.spines.top": (translate("plot", "Display axis spines"), QtWidgets.QCheckBox),
187
      "axes.spines.right": (translate("plot", "Display axis spines"), QtWidgets.QCheckBox),
188
      "axes.unicode_minus": (
189
          translate("plot", "Use Unicode for the minus symbol rather than hyphen"),
190
          QtWidgets.QCheckBox),
191
      "axes.xmargin": (translate("plot", "X margin"), QtWidgets.QDoubleSpinBox, 0, 1),
192
      "axes.ymargin": (translate("plot", "Y margin"), QtWidgets.QDoubleSpinBox, 0, 1),
193
      "axes.zmargin": (translate("plot", "Z margin"), QtWidgets.QDoubleSpinBox, 0, 1),
194

195
      "axes3d.grid": (translate("plot", "Display grid on 3D axes"), QtWidgets.QCheckBox),
196

197
      "figure.titlesize": (translate("plot", "Size of the figure title"),
198
                           QtWidgets.QComboBox, 'xx-small', 'x-small', 'small',
199
                           'medium', 'large', 'x-large', 'xx-large'),
200
      "figure.titleweight": (translate("plot", "Weight of the figure title"),
201
                             QtWidgets.QComboBox, "normal", "bold"),
202
      "figure.labelsize": (translate("plot", "Size of the figure label"),
203
                           QtWidgets.QComboBox, 'xx-small', 'x-small', 'small',
204
                           'medium', 'large', 'x-large', 'xx-large'),
205
      "figure.labelweight": (translate("plot", "Weight of figure label"),
206
                             QtWidgets.QComboBox, "normal", "bold"),
207
      # ("figure.figsize": (,     6.4, 4.8  # figure size in inches
208
      "figure.dpi": (translate("plot", "Figure dots per inch"), QtWidgets.QSpinBox, 10,200),
209
      "figure.facecolor": (translate("plot", "Figure face color"), ColorSelector),
210
      "figure.edgecolor": (translate("plot", "Figure edge color"), ColorSelector),
211
      "figure.frameon": (translate("plot", "Enable figure frame"), QtWidgets.QCheckBox),
212
      "figure.subplot.left": (
213
          translate("plot", "The left side of the subplots of the figure"),
214
          QtWidgets.QDoubleSpinBox, 0, 1),
215
      "figure.subplot.right": (
216
          translate("plot", "The right side of the subplots of the figure"),
217
          QtWidgets.QDoubleSpinBox, 0, 1),
218
      "figure.subplot.bottom": (
219
        translate("plot", "The bottom of the subplots of the figure"),
220
        QtWidgets.QDoubleSpinBox, 0, 1),
221
      "figure.subplot.top": (
222
        translate("plot", "The top of the subplots of the figure"),
223
        QtWidgets.QDoubleSpinBox, 0, 1),
224
      "figure.subplot.wspace": (
225
        translate("plot", "Width reserved for space between subplots"),
226
        QtWidgets.QDoubleSpinBox, 0, 1),
227
      "figure.subplot.hspace": (
228
        translate("plot", "Height reserved for space between subplots"),
229
        QtWidgets.QDoubleSpinBox, 0, 1),
230
      "figure.autolayout": (
231
        translate("plot", "Automatically adjust subplot"), QtWidgets.QCheckBox),
232
      "figure.constrained_layout.h_pad": (
233
        translate("plot", "Padding around axes objects. Float representing"),
234
        QtWidgets.QDoubleSpinBox, 0, 1),
235
      "figure.constrained_layout.w_pad": (
236
        translate("plot", "Padding around axes objects. Float representing"),
237
        QtWidgets.QDoubleSpinBox, 0, 1),
238
      "figure.constrained_layout.hspace": (
239
        translate("plot", "Space between subplot groups. Float representing"),
240
        QtWidgets.QDoubleSpinBox, 0, 1),
241
      "figure.constrained_layout.wspace": (
242
        translate("plot", "Space between subplot groups. Float representing"),
243
        QtWidgets.QDoubleSpinBox, 0, 1),
244

245
      "font.family": ("", QtWidgets.QComboBox, "serif", "sans-serif",
246
                      "cursive", "fantasy", "monospace"),
247
      "font.style": ("", QtWidgets.QComboBox, "normal", "italic", "oblique"),
248
      "font.variant": ("", QtWidgets.QComboBox, "normal", "small-caps"),
249
      "font.weight": ("", QtWidgets.QComboBox, "normal", "bold"),
250
      "font.stretch": ("", QtWidgets.QComboBox, "ultra-condensed",
251
                       "extra-condensed", "condensed", "semi-condensed",
252
                       "normal", "semi-expanded", "expanded", "extra-expanded",
253
                       "ultra-expanded", "wider", "narrower"),
254
      "font.size": ("", QtWidgets.QDoubleSpinBox, 5, 20, 0.5, 1),
255

256
      "grid.color": (translate("plot", "Grid color"), ColorSelector),
257
      "grid.linestyle": (translate("plot", "Grid line style"), LineStyleCombo),
258
      "grid.linewidth": (translate("plot", "Grid line width in points"),
259
                         QtWidgets.QDoubleSpinBox, 0, 5, 0.1, 1),
260
      "grid.alpha": (translate("plot", "Grid lines transparency"),
261
                     QtWidgets.QDoubleSpinBox, 0, 1),
262

263
      "hatch.linewidth": (translate("plot", "Line width in points"),
264
                          QtWidgets.QDoubleSpinBox, 0, 5, 0.1, 1),
265
      "hatch.color": (translate("plot", "Hatch color"), ColorSelector),
266

267
      "legend.loc": (
268
          translate("plot", "Location of legend in axes"), QtWidgets.QComboBox, 'best',
269
          'center', 'center left', 'center right', 'lower center',
270
          'lower left', 'lower right', 'right', 'upper center', 'upper left',
271
          'upper right'),
272
      "legend.frameon": (translate("plot", "Draw the legend on a background patch"),
273
                         QtWidgets.QCheckBox),
274
      "legend.framealpha": (translate("plot", "Legend patch transparency"),
275
                            QtWidgets.QDoubleSpinBox, 0, 1),
276
      "legend.facecolor": (translate("plot", "Legend patch color"), ColorSelector),
277
      "legend.edgecolor": (translate("plot", "Background patch boundary color"), ColorSelector),
278
      "legend.fancybox": (
279
          translate("plot", "Use a rounded box for the legend background, else a rectangle"),
280
          QtWidgets.QCheckBox),
281
      "legend.shadow": (translate("plot", "Give background a shadow effect"),
282
                        QtWidgets.QCheckBox),
283
      "legend.numpoints": (translate("plot", "Number of marker points in the legend line"),
284
                           QtWidgets.QSpinBox, 1, 10),
285
      "legend.scatterpoints": (translate("plot", "Number of scatter points"),
286
                               QtWidgets.QSpinBox, 1, 10),
287
      "legend.markerscale": (translate("plot", "Relative size of legend markers vs. original"),
288
                             QtWidgets.QDoubleSpinBox, 0, 2, 0.1, 1),
289
      "legend.fontsize": ("", QtWidgets.QComboBox, 'xx-small', 'x-small',
290
                          'small', 'medium', 'large', 'x-large', 'xx-large'),
291
      "legend.labelcolor": ("", ColorSelector),
292
      "legend.title_fontsize": ("", QtWidgets.QComboBox, 'xx-small', 'x-small',
293
                                'small', 'medium', 'large', 'x-large',
294
                                'xx-large'),
295
      "legend.borderpad": (
296
          translate("plot", "Dimensions as fraction of font size for border whitespace"),
297
          QtWidgets.QDoubleSpinBox, 0, 10, 0.1, 1),
298
      "legend.labelspacing": (
299
          translate("plot", "Dimensions as fraction of font size for the vertical space "
300
          "between the legend entries"),
301
          QtWidgets.QDoubleSpinBox, 0, 10, 0.1, 1),
302
      "legend.handlelength": (
303
          translate("plot", "Dimensions as fraction of font size for the length of the "
304
          "legend lines"), QtWidgets.QDoubleSpinBox, 0, 10, 0.1, 1),
305
      "legend.handleheight": (
306
          translate("plot", "Dimensions as fraction of font size for the height of the "
307
          "legend handle"), QtWidgets.QDoubleSpinBox, 0, 10, 0.1, 1),
308
      "legend.handletextpad": (
309
          translate("plot", "Dimensions as fraction of font size for the space between the "
310
          "legend line and legend text"),
311
          QtWidgets.QDoubleSpinBox, 0, 10, 0.1, 1),
312
      "legend.borderaxespad": (
313
          translate("plot", "Dimensions as fraction of font size for the border between the "
314
          "axes and legend edge"), QtWidgets.QDoubleSpinBox, 0, 10, 0.1, 1),
315
      "legend.columnspacing": (
316
          translate("plot", "Dimensions as fraction of font size for column separation"),
317
          QtWidgets.QDoubleSpinBox, 0, 10, 0.1, 1),
318

319
      "lines.linewidth": (translate("plot", "Line width in points"),
320
                          QtWidgets.QDoubleSpinBox, 0, 10, 0.1, 1),
321
      "lines.linestyle": (translate("plot", "Line style"), LineStyleCombo),
322
      "lines.color": (translate("plot", "Line color"), ColorSelector),
323
      "lines.marker": (translate("plot", "Marker style"), MarkerCombo),
324
      "lines.markerfacecolor": (translate("plot", "Marker face color"), ColorSelector),
325
      "lines.markeredgecolor": (translate("plot", "Marker edge color"), ColorSelector),
326
      "lines.markeredgewidth": (translate("plot", "Line width around the marker symbol"),
327
                                QtWidgets.QDoubleSpinBox, 0, 5, 0.1, 1),
328
      "lines.markersize": (translate("plot", "Marker size, in points"),
329
                           QtWidgets.QDoubleSpinBox, 0, 10, 0.1, 1),
330
      "lines.dash_joinstyle": ("", QtWidgets.QComboBox, "miter", "round",
331
                               "bevel"),
332
      "lines.dash_capstyle": ("", QtWidgets.QComboBox, "butt", "round",
333
                              "projecting"),
334
      "lines.solid_joinstyle": ("", QtWidgets.QComboBox, "miter", "round",
335
                                "bevel"),
336
      "lines.solid_capstyle": ("", QtWidgets.QComboBox, "butt", "round",
337
                               "projecting"),
338
      "lines.antialiased": (translate("plot", "Render antialiased"), QtWidgets.QCheckBox),
339
      "lines.scale_dashes": ("", QtWidgets.QCheckBox),
340

341
      "patch.linewidth": (translate("plot", "Edge width in points"),
342
                          QtWidgets.QDoubleSpinBox, 0, 10, 0.1, 1),
343
      "patch.facecolor": (translate("plot", "Patch face color"), ColorSelector),
344
      "patch.edgecolor": (translate("plot", "Patch edge color"), ColorSelector),
345
      "patch.force_edgecolor": (translate("plot", "Always use edgecolor"), QtWidgets.QCheckBox),
346
      "patch.antialiased": (translate("plot", "Render patch antialiased"), QtWidgets.QCheckBox),
347

348
      "xtick.top": (translate("plot", "Draw ticks on the top side"), QtWidgets.QCheckBox),
349
      "xtick.bottom": (translate("plot", "Draw ticks on the bottom side"), QtWidgets.QCheckBox),
350
      "xtick.labeltop": (translate("plot", "Draw label on the top"), QtWidgets.QCheckBox),
351
      "xtick.labelbottom": (translate("plot", "Draw label on the bottom"), QtWidgets.QCheckBox),
352
      "xtick.major.size": (translate("plot", "Major tick size in points"),
353
                           QtWidgets.QDoubleSpinBox, 0, 10, 0.1, 1),
354
      "xtick.minor.size": (translate("plot", "Minor tick size in points"),
355
                           QtWidgets.QDoubleSpinBox, 0, 10, 0.1, 1),
356
      "xtick.major.width": (translate("plot", "Major tick width in points"),
357
                            QtWidgets.QDoubleSpinBox, 0, 10, 0.1, 1),
358
      "xtick.minor.width": (translate("plot", "Minor tick width in points"),
359
                            QtWidgets.QDoubleSpinBox, 0, 10, 0.1, 1),
360
      "xtick.major.pad": (translate("plot", "Distance to major tick label in points"),
361
                          QtWidgets.QDoubleSpinBox, 0, 20, 0.1, 1),
362
      "xtick.minor.pad": (translate("plot", "Distance to the minor tick label in points"),
363
                          QtWidgets.QDoubleSpinBox, 0, 20, 0.1, 1),
364
      "xtick.color": (translate("plot", "Color of the ticks"), ColorSelector),
365
      "xtick.labelcolor": (
366
          translate("plot", "Color of the tick labels or inherit from xtick.color"),
367
          ColorSelector),
368
      "xtick.labelsize": (translate("plot", "Font size of the tick labels"),
369
                          QtWidgets.QComboBox, 'xx-small', 'x-small', 'small',
370
                          'medium', 'large', 'x-large', 'xx-large'),
371
      "xtick.direction": (translate("plot", "Direction"), QtWidgets.QComboBox,
372
                          "in", "out", "inout"),
373
      "xtick.minor.visible": (translate("plot", "Visibility of minor ticks on x-axis"),
374
                              QtWidgets.QCheckBox),
375
      "xtick.major.top": (translate("plot", "Draw x axis top major ticks"),
376
                          QtWidgets.QCheckBox),
377
      "xtick.major.bottom": (translate("plot", "Draw x axis bottom major ticks"),
378
                             QtWidgets.QCheckBox),
379
      "xtick.minor.top": (translate("plot", "Draw x axis top minor ticks"),
380
                          QtWidgets.QCheckBox),
381
      "xtick.minor.bottom": (translate("plot", "Draw x axis bottom minor ticks"),
382
                             QtWidgets.QCheckBox),
383
      "xtick.alignment": (translate("plot", "Alignment of ticks"), QtWidgets.QComboBox,
384
                          "left", "center", "right"),
385

386
      "ytick.left": (translate("plot", "Draw ticks on the left side"), QtWidgets.QCheckBox),
387
      "ytick.right": (translate("plot", "Draw ticks on the right side"), QtWidgets.QCheckBox),
388
      "ytick.labelleft": (translate("plot", "Draw label on the left"), QtWidgets.QCheckBox),
389
      "ytick.labelright": (translate("plot", "Draw label on the right"), QtWidgets.QCheckBox),
390
      "ytick.major.size": (translate("plot", "Major tick size in points"),
391
                           QtWidgets.QDoubleSpinBox, 0, 10, 0.1, 1),
392
      "ytick.minor.size": (translate("plot", "Minor tick size in points"),
393
                           QtWidgets.QDoubleSpinBox, 0, 10, 0.1, 1),
394
      "ytick.major.width": (translate("plot", "Major tick width in points"),
395
                            QtWidgets.QDoubleSpinBox, 0, 10, 0.1, 1),
396
      "ytick.minor.width": (translate("plot", "Minor tick width in points"),
397
                            QtWidgets.QDoubleSpinBox, 0, 10, 0.1, 1),
398
      "ytick.major.pad": (translate("plot", "Distance to major tick label in points"),
399
                          QtWidgets.QDoubleSpinBox, 0, 20, 0.1, 1),
400
      "ytick.minor.pad": (translate("plot", "Distance to the minor tick label in points"),
401
                          QtWidgets.QDoubleSpinBox, 0, 20, 0.1, 1),
402
      "ytick.color": (translate("plot", "Color of the ticks"), ColorSelector),
403
      "ytick.labelcolor": (
404
          translate("plot", "Color of the tick labels or inherit from ytick.color"),
405
          ColorSelector),
406
      "ytick.labelsize": (translate("plot", "Font size of the tick labels"),
407
                          QtWidgets.QComboBox, 'xx-small', 'x-small', 'small',
408
                          'medium', 'large', 'x-large', 'xx-large'),
409
      "ytick.direction": (translate("plot", "Direction"), QtWidgets.QComboBox,
410
                          "in", "out", "inout"),
411
      "ytick.minor.visible": (translate("plot", "Visibility of minor ticks on y-axis"),
412
                              QtWidgets.QCheckBox),
413
      "ytick.major.left": (translate("plot", "Draw y axis left major ticks"),
414
                           QtWidgets.QCheckBox),
415
      "ytick.major.right": (translate("plot", "Draw y axis right major ticks"),
416
                            QtWidgets.QCheckBox),
417
      "ytick.minor.left": (translate("plot", "Draw y axis left minor ticks"),
418
                           QtWidgets.QCheckBox),
419
      "ytick.minor.right": (translate("plot", "Draw y axis right minor ticks"),
420
                            QtWidgets.QCheckBox),
421
      "ytick.alignment": (translate("plot", "Alignment of ticks"), QtWidgets.QComboBox,
422
                          'bottom', 'baseline', 'center', 'center_baseline',
423
                          'top'),
424

425
      "xaxis.labellocation": (translate("plot", "Alignment of the xaxis label"),
426
                              QtWidgets.QComboBox, "center", "left", "right"),
427
      "yaxis.labellocation": (translate("plot",  "Alignment of the yaxis label"),
428
                              QtWidgets.QComboBox, "center", "bottom", "top"),
429

430
      "savefig.dpi": (translate("plot", "Figure dots per inch"), QtWidgets.QSpinBox, 0, 100),
431
      "savefig.facecolor": (translate("plot", "Figure facecolor when saving"), ColorSelector),
432
      "savefig.edgecolor": (translate("plot", "Figure edgecolor when saving"), ColorSelector),
433
      "savefig.format": (translate("plot", "File format so save"), QtWidgets.QComboBox,
434
                         "png", "ps", "pdf", "svg"),
435
      "savefig.bbox": ("", QtWidgets.QComboBox, "standard", "tight"),
436
      "savefig.pad_inches": (
437
          translate("plot", "Padding to be used, when bbox is set to 'tight'"),
438
          QtWidgets.QDoubleSpinBox, 0, 10, 0.1, 1),
439
      "savefig.transparent": (
440
          translate("plot", "Figures are saved with a transparent background"),
441
          QtWidgets.QCheckBox)}
442

443
    def __init__(self, config=None, parent=None):
1✔
444
        super().__init__(parent)
×
445

446
        layout = QtWidgets.QGridLayout(self)
×
447
        layout.addWidget(QtWidgets.QLabel(self.tr("Matplotlib Style:")), 1, 1)
×
448
        self.style = QtWidgets.QComboBox()
×
449
        layout.addWidget(self.style, 1, 2)
×
450
        self.style.addItem("default")
×
451
        for sty in style.available:
×
452
            self.style.addItem(sty)
×
453
        self.style.currentTextChanged.connect(self.updateStyle)
×
454

455
        self.customize = QtWidgets.QCheckBox(self.tr("Costomize Style:"))
×
456
        layout.addWidget(self.customize, 2, 1, 1, 3)
×
457
        self.tabRcParams = QtWidgets.QTabWidget()
×
458
        self.tabRcParams.setEnabled(False)
×
459
        layout.addWidget(self.tabRcParams, 3, 1, 1, 3)
×
460
        self.customize.toggled.connect(self.tabRcParams.setEnabled)
×
461
        self.customize.toggled.connect(self.updatePlot)
×
462

463
        tab = []
×
464
        tab_widgets = []
×
465
        for label, (tooltip, wdg, *opt) in sorted(self.RCParams.items()):
×
466
            title = label.split(".")[0]
×
467

468
            # Regroup tiny option in axes tab
469
            if title in ("xaxis", "yaxis", "axes3d"):
×
470
                title = "axes"
×
471

472
            # Add new tab if not exist
473
            if title not in tab:
×
474
                tab.append(title)
×
475
                tab_widgets.append(QtWidgets.QWidget())
×
476
                QtWidgets.QVBoxLayout(tab_widgets[-1])
×
477

478
            # Get index of current tab to add widget
479
            idx = tab.index(title)
×
480

481
            # Create widget with a horizontal layout to add label and
482
            # appropiate input widget
483
            rc_widget = QtWidgets.QWidget()
×
484
            lyt_wdg = QtWidgets.QHBoxLayout(rc_widget)
×
485
            lyt_wdg.addWidget(QtWidgets.QLabel(label, rc_widget))
×
486

487
            # Create the appropiate input widget as defined in rc dict
488
            if wdg == QtWidgets.QDoubleSpinBox:
×
489
                wdg = QtWidgets.QDoubleSpinBox()
×
490
                wdg.setRange(*opt[:2])
×
491
                if len(opt) >= 3:
×
492
                    wdg.setSingleStep(opt[2])
×
493
                if len(opt) == 4:
×
494
                    wdg.setDecimals(opt[3])
×
495
                elif opt[1] == 1 and opt[0] == 0:
×
496
                    wdg.setSingleStep(0.01)
×
497
                wdg.valueChanged.connect(self.updatePlot)
×
498
            elif wdg == QtWidgets.QSpinBox:
×
499
                wdg = QtWidgets.QSpinBox()
×
500
                wdg.setRange(*opt)
×
501
                wdg.valueChanged.connect(self.updatePlot)
×
502
            elif wdg == QtWidgets.QComboBox:
×
503
                wdg = QtWidgets.QComboBox()
×
504
                for value in opt:
×
505
                    wdg.addItem(value)
×
506
                wdg.currentIndexChanged.connect(self.updatePlot)
×
507
            elif wdg == LineStyleCombo:
×
508
                wdg = LineStyleCombo()
×
509
                wdg.currentIndexChanged.connect(self.updatePlot)
×
510
            elif wdg == MarkerCombo:
×
511
                wdg = MarkerCombo()
×
512
                wdg.currentIndexChanged.connect(self.updatePlot)
×
513
            elif wdg == ColorSelector:
×
514
                wdg = ColorSelector()
×
515
                wdg.valueChanged.connect(self.updatePlot)
×
516
            elif wdg == QtWidgets.QCheckBox:
×
517
                wdg = QtWidgets.QCheckBox()
×
518
                wdg.toggled.connect(self.updatePlot)
×
519

520
            wdg.setToolTip(tooltip)
×
521
            lyt_wdg.addWidget(wdg)
×
522
            lyt_wdg.addItem(QtWidgets.QSpacerItem(
×
523
                10, 0, QtWidgets.QSizePolicy.Policy.Expanding,
524
                QtWidgets.QSizePolicy.Policy.Fixed))
525

526
            tab_widgets[idx].layout().addWidget(rc_widget)
×
527

528
        for title, wdg in zip(tab, tab_widgets):
×
529
            # Add a spacer item at end of each tabwidget
530
            wdg.layout().addItem(QtWidgets.QSpacerItem(
×
531
                10, 10, QtWidgets.QSizePolicy.Policy.Expanding,
532
                QtWidgets.QSizePolicy.Policy.Expanding))
533

534
            # Now when the widget is pupulate add the scrollArea
535
            scroll = QtWidgets.QScrollArea()
×
536
            scroll.setFrameStyle(QtWidgets.QFrame.Shape.NoFrame)
×
537
            scroll.setWidget(wdg)
×
538
            self.tabRcParams.addTab(scroll, title)
×
539

540
        layout.addItem(QtWidgets.QSpacerItem(
×
541
            10, 10, QtWidgets.QSizePolicy.Policy.Expanding,
542
            QtWidgets.QSizePolicy.Policy.Expanding), 10, 1, 1, 3)
543

544
        if config.has_section("Plot"):
×
545
            self.style.setCurrentIndex(config.getint("Plot", 'style'))
×
546
            self.customize.setChecked(config.getboolean("Plot", 'customize'))
×
547

548
            for idx in range(self.tabRcParams.count()):
×
549
                tab = self.tabRcParams.widget(idx).widget()
×
550
                for parent_wdg in tab.children()[1:]:
×
551
                    label, wdg = parent_wdg.children()[1:]
×
552
                    if isinstance(wdg, QtWidgets.QDoubleSpinBox):
×
553
                        wdg.setValue(config.getfloat("Plot", label.text()))
×
554
                    elif isinstance(wdg, QtWidgets.QSpinBox):
×
555
                        if label.text() == "axes.formatter.limits":
×
556
                            # Convert saved value as tuple to integer
557
                            value = config.get("Plot", label.text())
×
558
                            value = -int(value.split(",")[0])
×
559
                            wdg.setValue(value)
×
560
                        else:
561
                            wdg.setValue(config.getint("Plot", label.text()))
×
562
                    elif isinstance(wdg, LineStyleCombo):
×
563
                        wdg.setCurrentText(config.get("Plot", label.text()))
×
564
                    elif isinstance(wdg, MarkerCombo):
×
565
                        wdg.setCurrentText(config.get("Plot", label.text()))
×
566
                    elif isinstance(wdg, QtWidgets.QComboBox):
×
567
                        wdg.setCurrentText(config.get("Plot", label.text()))
×
568
                    elif isinstance(wdg, QtWidgets.QCheckBox):
×
569
                        wdg.setChecked(config.getboolean("Plot", label.text()))
×
570
                    elif isinstance(wdg, ColorSelector):
×
571
                        wdg.setColor(config.get("Plot", label.text()))
×
572

573
        self.updatePlot()
×
574

575
    def updateStyle(self, textStyle):
1✔
576
        """Update rcParams with the new selected style and update plot"""
577
        rcdefaults()
×
578

579
        if textStyle != "default":
×
580
            rcParams.update(style.library[textStyle])
×
581

582
        self._setRcParams(rcParams)
×
583
        self.updatePlot()
×
584

585
    def updatePlot(self):
1✔
586
        """Update plot with the new configuration, global style or only a
587
        rcParams changed"""
588
        if self.customize.isChecked():
×
589
            textStyle = self._getRcParams()
×
590

591
        else:
592
            textStyle = self.style.currentText()
×
593

594
        self.plot(textStyle)
×
595

596
    def plot(self, textStyle):
1✔
597
        """Do the sample plot using the specified style confuguration, the
598
        style configuration can be a named matplotlib style or a dictionary
599
        with rc_params"""
600
        with style.context(textStyle):
×
601
            x = arange(-2, 8, .2)
×
602
            y = .1 * x ** 3 - x ** 2 + 3 * x + 2
×
603
            z = .01 * x ** 3 + x ** 2 - 3 * x - 2
×
604

605
            plot = PlotWidget(width=1, height=1)
×
606
            plot.plot(x, y, label="f(x)")
×
607
            plot.plot(x, z, label="g(x)")
×
608
            plot.ax.set_title("Function representation")
×
609
            plot.ax.set_xlabel("x")
×
610
            plot.ax.set_ylabel(r"$y=f\left(x\right)$")
×
611
            plot.ax.legend()
×
612
            self.layout().addWidget(plot, 9, 1, 1, 3)
×
613

614
    def _getRcParams(self):
1✔
615
        """Get rc parameters from widgets"""
616
        kw = {}
×
617
        for idx in range(self.tabRcParams.count()):
×
618
            tab = self.tabRcParams.widget(idx).widget()
×
619

620
            # The first children is always the layout used so we discard it
621
            for parent_wdg in tab.children()[1:]:
×
622
                label, wdg = parent_wdg.children()[1:]
×
623

624
                if isinstance(wdg, QtWidgets.QDoubleSpinBox):
×
625
                    # Correct value for axes.titley
626
                    if label.text() == "axes.titley" and value == 1.:
×
627
                        value = None
×
628
                    else:
629
                        value = wdg.value()
×
630
                elif isinstance(wdg, QtWidgets.QSpinBox):
×
631
                    # Correct value to matplotlib expected format
632
                    if label.text() == "axes.formatter.limits":
×
633
                        value = f"-{wdg.value()}, {wdg.value()}"
×
634
                    else:
635
                        value = wdg.value()
×
636
                elif isinstance(wdg, (LineStyleCombo, MarkerCombo)):
×
637
                    value = wdg.currentValue()
×
638
                elif isinstance(wdg, QtWidgets.QComboBox):
×
639
                    value = wdg.currentText()
×
640
                elif isinstance(wdg, QtWidgets.QCheckBox):
×
641
                    value = wdg.isChecked()
×
642
                elif isinstance(wdg, ColorSelector):
×
643
                    value = wdg.color.name()
×
644

645
                kw[label.text()] = value
×
646
        return kw
×
647

648
    def _setRcParams(self, rcparams):
1✔
649
        """Populate widgets with the dict rcparams given as parameter"""
650
        for idx in range(self.tabRcParams.count()):
×
651
            tab = self.tabRcParams.widget(idx).widget()
×
652
            for parent_wdg in tab.children()[1:]:
×
653
                label, wdg = parent_wdg.children()[1:]
×
654
                wdg.blockSignals(True)
×
655
                value = rcparams.get(label.text(), None)
×
656

657
                if isinstance(wdg, QtWidgets.QDoubleSpinBox):
×
658
                    # Correct default value
659
                    if label.text() == "axes.titley" and value is None:
×
660
                        value = 1.
×
661
                    if label.text() == "legend.framealpha" and value is None:
×
662
                        value = 0.8
×
663
                    wdg.setValue(value)
×
664
                elif isinstance(wdg, QtWidgets.QSpinBox):
×
665
                    # Correct default value
666
                    if label.text() == "savefig.dpi" and value == "figure":
×
667
                        value = rcparams["figure.dpi"]
×
668
                    if label.text() == "axes.formatter.limits":
×
669
                        value = -value[0]
×
670
                    wdg.setValue(int(value))
×
671
                elif isinstance(wdg, LineStyleCombo):
×
672
                    wdg.setCurrentValue(value)
×
673
                elif isinstance(wdg, MarkerCombo):
×
674
                    wdg.setCurrentValue(value)
×
675
                elif isinstance(wdg, QtWidgets.QComboBox):
×
676
                    # Do text manipulation for joinstyle enum
677
                    if label.text() in (
×
678
                            "lines.dash_joinstyle", "lines.solid_joinstyle"):
679
                        value = value.split(".")[-1]
×
680
                    # Do text manipulation for capstyle enum
681
                    if label.text() in (
×
682
                            "lines.dash_capstyle", "lines.solid_capstyle"):
683
                        value = value.split(".")[-1]
×
684
                    # Do text manipulation for font.family
685
                    if label.text() == "font.family":
×
686
                        value = value[0]
×
687
                    wdg.setCurrentText(str(value))
×
688
                elif isinstance(wdg, QtWidgets.QCheckBox):
×
689
                    wdg.setChecked(value)
×
690
                elif isinstance(wdg, ColorSelector):
×
691

692
                    # Correct auto and inherit values
693
                    if label.text() == "axes.titlecolor" and value == "auto":
×
694
                        value = rcparams["text.color"]
×
695
                    if label.text() == "legend.facecolor" and value == "inherit":
×
696
                        value = rcparams["axes.facecolor"]
×
697
                    if label.text() == "legend.edgecolor" and value == "inherit":
×
698
                        value = rcparams["axes.edgecolor"]
×
699
                    if label.text() == "xtick.labelcolor" and value == "inherit":
×
700
                        value = rcparams["xtick.color"]
×
701
                    if label.text() == "ytick.labelcolor" and value == "inherit":
×
702
                        value = rcparams["ytick.color"]
×
703
                    if label.text() == "savefig.facecolor" and value == "auto":
×
704
                        value = rcparams["figure.facecolor"]
×
705
                    if label.text() == "savefig.edgecolor" and value == "auto":
×
706
                        value = rcparams["figure.edgecolor"]
×
707
                    if label.text() == "lines.markeredgecolor" and value == "auto":
×
708
                        value = "#000000"
×
709
                    if label.text() == "lines.markerfacecolor" and value == "auto":
×
710
                        value = "C2"
×
711

712
                    # Get the RGB code of color to avoid the several color
713
                    # definition in matplotlib and its styles
714
                    r, g, b = to_rgb(value)
×
715

716
                    r = int(float(r)*255)
×
717
                    g = int(float(g)*255)
×
718
                    b = int(float(b)*255)
×
719
                    value = f"#{r:x}{g:x}{b:x}"
×
720

721
                    wdg.setColor(value)
×
722

723
                wdg.blockSignals(False)
×
724

725
    def value(self, config):
1✔
726
        """Update ConfigParser instance with the config"""
727
        if not config.has_section("Plot"):
×
728
            config.add_section("Plot")
×
729
        config.set("Plot", "style", str(self.style.currentIndex()))
×
730
        config.set("Plot", "customize", str(self.customize.isChecked()))
×
731

732
        for k, val in self._getRcParams().items():
×
733
            config.set("Plot", k, str(val))
×
734

735
        return config
×
736

737

738
# Load style defined in preferences
739
if Preferences.getboolean("Plot", 'customize'):
1✔
740
    rc = {}
×
741
    for key, (tip, widget, *args) in ConfPlot.RCParams.items():
×
742
        if widget == QtWidgets.QDoubleSpinBox:
×
743
            rc[key] = Preferences.getfloat("Plot", key)
×
744
        elif widget == QtWidgets.QSpinBox:
×
745
            if key == "axes.formatter.limits":
×
746
                rc[key] = Preferences.get("Plot", key)
×
747
            else:
748
                rc[key] = Preferences.getint("Plot", key)
×
749
        elif widget == QtWidgets.QCheckBox:
×
750
            rc[key] = Preferences.getboolean("Plot", key)
×
751
        else:
752
            rc[key] = Preferences.get("Plot", key)
×
753
    style.use(rc)
×
754
elif Preferences.getint("Plot", 'style') == 0:
1✔
755
    style.use("default")
1✔
756
else:
757
    style.use(style.available[Preferences.getint("Plot", 'style')-1])
×
758

759

760
if __name__ == "__main__":
1✔
761
    import os
×
762
    import sys
×
763
    from configparser import ConfigParser
×
764
    conf_dir = os.path.expanduser('~') + "/.pychemqt/"
×
765
    pychemqt_dir = os.environ["PWD"] + "/"
×
766
    app = QtWidgets.QApplication(sys.argv)
×
767

768
    conf = ConfigParser()
×
769
    conf.read(conf_dir+"pychemqtrc")
×
770
    dialogo = ConfPlot(conf)
×
771

772
    dialogo.show()
×
773
    sys.exit(app.exec())
×
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