Source code for tools.UI_confPlot

#!/usr/bin/python3
# -*- coding: utf-8 -*-

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

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

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

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

Dialog for configuration of matplotlib plot widget

'''

from matplotlib import rcParams, rcdefaults, style
from matplotlib.colors import to_rgb
from numpy import arange

from lib.plot import PlotWidget, RCParams
from tools.qt import QtWidgets
from UI.widgets import ColorSelector, LineStyleCombo, MarkerCombo


[docs] class ConfPlot(QtWidgets.QDialog): """Matplotlib configuration"""
[docs] def __init__(self, config=None, parent=None): super().__init__(parent) layout = QtWidgets.QGridLayout(self) layout.addWidget(QtWidgets.QLabel(self.tr("Matplotlib Style:")), 1, 1) self.style = QtWidgets.QComboBox() layout.addWidget(self.style, 1, 2) self.style.addItem("default") for sty in style.available: self.style.addItem(sty) self.style.currentTextChanged.connect(self.updateStyle) self.customize = QtWidgets.QCheckBox(self.tr("Costomize Style:")) layout.addWidget(self.customize, 2, 1, 1, 3) self.tabRcParams = QtWidgets.QTabWidget() self.tabRcParams.setEnabled(False) layout.addWidget(self.tabRcParams, 3, 1, 1, 3) self.customize.toggled.connect(self.tabRcParams.setEnabled) self.customize.toggled.connect(self.updatePlot) tab = [] tab_widgets = [] for label, (tooltip, wdg, *opt) in sorted(RCParams.items()): title = label.split(".")[0] # Regroup tiny option in axes tab if title in ("xaxis", "yaxis", "axes3d"): title = "axes" # Add new tab if not exist if title not in tab: tab.append(title) tab_widgets.append(QtWidgets.QWidget()) QtWidgets.QVBoxLayout(tab_widgets[-1]) # Get index of current tab to add widget idx = tab.index(title) # Create widget with a horizontal layout to add label and # appropiate input widget rc_widget = QtWidgets.QWidget() lyt_wdg = QtWidgets.QHBoxLayout(rc_widget) lyt_wdg.addWidget(QtWidgets.QLabel(label, rc_widget)) # Create the appropiate input widget as defined in rc dict if wdg == QtWidgets.QDoubleSpinBox: wdg = QtWidgets.QDoubleSpinBox() wdg.setRange(*opt[:2]) if len(opt) >= 3: wdg.setSingleStep(opt[2]) if len(opt) == 4: wdg.setDecimals(opt[3]) elif opt[1] == 1 and opt[0] == 0: wdg.setSingleStep(0.01) wdg.valueChanged.connect(self.updatePlot) elif wdg == QtWidgets.QSpinBox: wdg = QtWidgets.QSpinBox() wdg.setRange(*opt) wdg.valueChanged.connect(self.updatePlot) elif wdg == QtWidgets.QComboBox: wdg = QtWidgets.QComboBox() for value in opt: wdg.addItem(value) wdg.currentIndexChanged.connect(self.updatePlot) elif wdg == LineStyleCombo: wdg = LineStyleCombo() wdg.currentIndexChanged.connect(self.updatePlot) elif wdg == MarkerCombo: wdg = MarkerCombo() wdg.currentIndexChanged.connect(self.updatePlot) elif wdg == ColorSelector: wdg = ColorSelector() wdg.valueChanged.connect(self.updatePlot) elif wdg == QtWidgets.QCheckBox: wdg = QtWidgets.QCheckBox() wdg.toggled.connect(self.updatePlot) wdg.setToolTip(tooltip) lyt_wdg.addWidget(wdg) lyt_wdg.addItem(QtWidgets.QSpacerItem( 10, 0, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Fixed)) tab_widgets[idx].layout().addWidget(rc_widget) for title, wdg in zip(tab, tab_widgets): # Add a spacer item at end of each tabwidget wdg.layout().addItem(QtWidgets.QSpacerItem( 10, 10, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding)) # Now when the widget is pupulate add the scrollArea scroll = QtWidgets.QScrollArea() scroll.setFrameStyle(QtWidgets.QFrame.Shape.NoFrame) scroll.setWidget(wdg) self.tabRcParams.addTab(scroll, title) layout.addItem(QtWidgets.QSpacerItem( 10, 10, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding), 10, 1, 1, 3) if config.has_section("Plot"): self.style.setCurrentIndex(config.getint("Plot", 'style')) self.customize.setChecked(config.getboolean("Plot", 'customize')) for idx in range(self.tabRcParams.count()): tab = self.tabRcParams.widget(idx).widget() for parent_wdg in tab.children()[1:]: label, wdg = parent_wdg.children()[1:] if isinstance(wdg, QtWidgets.QDoubleSpinBox): wdg.setValue(config.getfloat("Plot", label.text())) elif isinstance(wdg, QtWidgets.QSpinBox): if label.text() == "axes.formatter.limits": # Convert saved value as tuple to integer value = config.get("Plot", label.text()) value = -int(value.split(",")[0]) wdg.setValue(value) else: wdg.setValue(config.getint("Plot", label.text())) elif isinstance(wdg, LineStyleCombo): wdg.setCurrentText(config.get("Plot", label.text())) elif isinstance(wdg, MarkerCombo): wdg.setCurrentText(config.get("Plot", label.text())) elif isinstance(wdg, QtWidgets.QComboBox): wdg.setCurrentText(config.get("Plot", label.text())) elif isinstance(wdg, QtWidgets.QCheckBox): wdg.setChecked(config.getboolean("Plot", label.text())) elif isinstance(wdg, ColorSelector): wdg.setColor(config.get("Plot", label.text())) self.updatePlot()
[docs] def updateStyle(self, textStyle): """Update rcParams with the new selected style and update plot""" rcdefaults() if textStyle != "default": rcParams.update(style.library[textStyle]) self._setRcParams(rcParams) self.updatePlot()
[docs] def updatePlot(self): """Update plot with the new configuration, global style or only a rcParams changed""" if self.customize.isChecked(): textStyle = self._getRcParams() else: textStyle = self.style.currentText() self.plot(textStyle)
[docs] def plot(self, textStyle): """Do the sample plot using the specified style confuguration, the style configuration can be a named matplotlib style or a dictionary with rc_params""" with style.context(textStyle): x = arange(-2, 8, .2) y = .1 * x ** 3 - x ** 2 + 3 * x + 2 z = .01 * x ** 3 + x ** 2 - 3 * x - 2 plot = PlotWidget(width=1, height=1) plot.plot(x, y, label="f(x)") plot.plot(x, z, label="g(x)") plot.ax.set_title("Function representation") plot.ax.set_xlabel("x") plot.ax.set_ylabel(r"$y=f\left(x\right)$") plot.ax.legend() self.layout().addWidget(plot, 9, 1, 1, 3)
[docs] def _getRcParams(self): """Get rc parameters from widgets""" kw = {} for idx in range(self.tabRcParams.count()): tab = self.tabRcParams.widget(idx).widget() # The first children is always the layout used so we discard it for parent_wdg in tab.children()[1:]: label, wdg = parent_wdg.children()[1:] if isinstance(wdg, QtWidgets.QDoubleSpinBox): # Correct value for axes.titley if label.text() == "axes.titley" and wdg.value() == 1.: value = None else: value = wdg.value() elif isinstance(wdg, QtWidgets.QSpinBox): # Correct value to matplotlib expected format if label.text() == "axes.formatter.limits": value = f"-{wdg.value()}, {wdg.value()}" else: value = wdg.value() elif isinstance(wdg, (LineStyleCombo, MarkerCombo)): value = wdg.currentValue() elif isinstance(wdg, QtWidgets.QComboBox): value = wdg.currentText() elif isinstance(wdg, QtWidgets.QCheckBox): value = wdg.isChecked() elif isinstance(wdg, ColorSelector): value = wdg.color.name() else: value = None kw[label.text()] = value return kw
[docs] def _setRcParams(self, rcparams): """Populate widgets with the dict rcparams given as parameter""" for idx in range(self.tabRcParams.count()): tab = self.tabRcParams.widget(idx).widget() for parent_wdg in tab.children()[1:]: label, wdg = parent_wdg.children()[1:] wdg.blockSignals(True) value = rcparams.get(label.text(), None) if isinstance(wdg, QtWidgets.QDoubleSpinBox): # Correct default value if label.text() == "axes.titley" and value is None: value = 1. if label.text() == "legend.framealpha" and value is None: value = 0.8 wdg.setValue(value) elif isinstance(wdg, QtWidgets.QSpinBox): # Correct default value if label.text() == "savefig.dpi" and value == "figure": value = rcparams["figure.dpi"] if label.text() == "axes.formatter.limits": value = -value[0] wdg.setValue(int(value)) elif isinstance(wdg, LineStyleCombo): wdg.setCurrentValue(value) elif isinstance(wdg, MarkerCombo): wdg.setCurrentValue(value) elif isinstance(wdg, QtWidgets.QComboBox): # Do text manipulation for joinstyle enum if label.text() in ( "lines.dash_joinstyle", "lines.solid_joinstyle"): value = value.split(".")[-1] # Do text manipulation for capstyle enum if label.text() in ( "lines.dash_capstyle", "lines.solid_capstyle"): value = value.split(".")[-1] # Do text manipulation for font.family if label.text() == "font.family": value = value[0] wdg.setCurrentText(str(value)) elif isinstance(wdg, QtWidgets.QCheckBox): wdg.setChecked(value) elif isinstance(wdg, ColorSelector): # Correct auto and inherit values if label.text() == "axes.titlecolor" and value == "auto": value = rcparams["text.color"] if label.text() == "legend.facecolor" \ and value == "inherit": value = rcparams["axes.facecolor"] if label.text() == "legend.edgecolor" \ and value == "inherit": value = rcparams["axes.edgecolor"] if label.text() == "xtick.labelcolor" \ and value == "inherit": value = rcparams["xtick.color"] if label.text() == "ytick.labelcolor" \ and value == "inherit": value = rcparams["ytick.color"] if label.text() == "savefig.facecolor" and value == "auto": value = rcparams["figure.facecolor"] if label.text() == "savefig.edgecolor" and value == "auto": value = rcparams["figure.edgecolor"] if label.text() == "lines.markeredgecolor" \ and value == "auto": value = "#000000" if label.text() == "lines.markerfacecolor" \ and value == "auto": value = "C2" # Get the RGB code of color to avoid the several color # definition in matplotlib and its styles r, g, b = to_rgb(value) r = int(float(r)*255) g = int(float(g)*255) b = int(float(b)*255) value = f"#{r:x}{g:x}{b:x}" wdg.setColor(value) wdg.blockSignals(False)
[docs] def value(self, config): """Update ConfigParser instance with the config""" if not config.has_section("Plot"): config.add_section("Plot") config.set("Plot", "style", str(self.style.currentIndex())) config.set("Plot", "customize", str(self.customize.isChecked())) for k, val in self._getRcParams().items(): config.set("Plot", k, str(val)) return config
if __name__ == "__main__": import os import sys from configparser import ConfigParser conf_dir = os.path.expanduser('~') + "/.pychemqt/" pychemqt_dir = os.environ["PWD"] + "/" app = QtWidgets.QApplication(sys.argv) conf = ConfigParser() conf.read(conf_dir+"pychemqtrc") dialogo = ConfPlot(conf) dialogo.show() sys.exit(app.exec())