From c15b01ecc212ee51c51f4b6b1e14425600756141 Mon Sep 17 00:00:00 2001 From: Valentin Valls Date: Mon, 18 Dec 2023 14:04:37 +0100 Subject: [PATCH] Create an example to compare backend rendering --- examples/compareBackend.py | 261 +++++++++++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 examples/compareBackend.py diff --git a/examples/compareBackend.py b/examples/compareBackend.py new file mode 100644 index 0000000000..738170e64e --- /dev/null +++ b/examples/compareBackend.py @@ -0,0 +1,261 @@ +# /*########################################################################## +# +# Copyright (c) 2017-2021 European Synchrotron Radiation Facility +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ###########################################################################*/ + +""" +Compare one to one backend rendering +""" + +from __future__ import annotations + +__license__ = "MIT" + +import numpy +import sys +import functools + +from silx.gui import qt + +from silx.gui.plot import PlotWidget +from silx.gui.plot import items +from silx.gui.plot.items.marker import Marker +from silx.gui.plot.utils.axis import SyncAxes + + +_DESCRIPTIONS = {} + + +class MyPlotWindow(qt.QMainWindow): + """QMainWindow with selected tools""" + + def __init__(self, parent=None): + super(MyPlotWindow, self).__init__(parent) + + # Create a PlotWidget + self._plot1 = PlotWidget(parent=self, backend="mpl") + self._plot1.setGraphTitle("matplotlib") + self._plot2 = PlotWidget(parent=self, backend="opengl") + self._plot2.setGraphTitle("opengl") + + self.constraintX = SyncAxes( + [ + self._plot1.getXAxis(), + self._plot2.getXAxis(), + ] + ) + self.constraintY = SyncAxes( + [ + self._plot1.getYAxis(), + self._plot2.getYAxis(), + ] + ) + + plotWidget = qt.QWidget(self) + plotLayout = qt.QHBoxLayout(plotWidget) + plotLayout.addWidget(self._plot1) + plotLayout.addWidget(self._plot2) + plotLayout.setContentsMargins(0, 0, 0, 0) + plotLayout.setContentsMargins(0, 0, 0, 0) + + options = self.createOptions(self) + centralWidget = qt.QWidget(self) + layout = qt.QHBoxLayout(centralWidget) + layout.setSpacing(0) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(options) + layout.addWidget(plotWidget) + + self.setCentralWidget(centralWidget) + + self._state = {} + + def clear(self): + self._state = {} + + def createOptions(self, parent): + options = qt.QWidget(parent) + layout = qt.QVBoxLayout(options) + for id, description in _DESCRIPTIONS.items(): + label, _func = description + button = qt.QPushButton(label, self) + button.clicked.connect(functools.partial(self.showUseCase, id)) + layout.addWidget(button) + layout.addStretch() + return options + + def showUseCase(self, name: str): + description = _DESCRIPTIONS.get(name) + if description is None: + raise ValueError(f"Unknown use case '{name}'") + setupFunc = description[1] + self.clear() + for p in [self._plot1, self._plot2]: + p.clear() + setupFunc(self, p) + p.resetZoom() + + def _register(name, label): + def decorator(func): + _DESCRIPTIONS[name] = (label, func) + return func + return decorator + + def _addLine( + self, + plot, + lineWidth: float, + lineStyle: str, + color: str, + gapColor: str | None, + curve: bool + ): + state = self._state.setdefault(plot, {}) + x = state.get("x", 0) + y = state.get("y", 0) + x += 10 + state["x"] = x + state["y"] = y + + start = (x - 20, y + 0) + stop = (x + 40, y + 100) + + def createShape(): + shape = items.Shape("polylines") + shape.setPoints(numpy.array((start, stop))) + shape.setLineWidth(lineWidth) + shape.setLineStyle(lineStyle) + shape.setColor(color) + if gapColor is not None: + shape.setLineGapColor(gapColor) + return shape + + def createCurve(): + curve = items.Curve() + array = numpy.array((start, stop)).T + curve.setData(array[0], array[1]) + curve.setLineWidth(lineWidth) + curve.setLineStyle(lineStyle) + curve.setColor(color) + curve.setSymbol("") + if gapColor is not None: + curve.setLineGapColor(gapColor) + return curve + + if curve: + plot.addItem(createCurve()) + else: + plot.addItem(createShape()) + + @_register("linewidth", "Line width") + def _setupLineStyle(self, plot: PlotWidget): + self._addLine(plot, 0.5, "-", "#0000FF", None, curve=False) + self._addLine(plot, 1.0, "-", "#0000FF", None, curve=False) + self._addLine(plot, 2.0, "-", "#0000FF", None, curve=False) + self._addLine(plot, 4.0, "-", "#0000FF", None, curve=False) + self._addLine(plot, 0.5, "-", "#00FFFF", None, curve=True) + self._addLine(plot, 1.0, "-", "#00FFFF", None, curve=True) + self._addLine(plot, 2.0, "-", "#00FFFF", None, curve=True) + self._addLine(plot, 4.0, "-", "#00FFFF", None, curve=True) + + @_register("linestyle", "Line style") + def _setupLineStyle(self, plot: PlotWidget): + self._addLine(plot, 1.0, "--", "#0000FF", None, curve=False) + self._addLine(plot, 1.0, "-.", "#0000FF", None, curve=False) + self._addLine(plot, 1.0, ":", "#0000FF", None, curve=False) + self._addLine(plot, 2.0, "--", "#00FFFF", None, curve=True) + self._addLine(plot, 2.0, "-.", "#00FFFF", None, curve=True) + self._addLine(plot, 2.0, ":", "#00FFFF", None, curve=True) + + @_register("gapcolor", "LineStyle Gap Color") + def _setupLineStyleGapColor(self, plot): + self._addLine(plot, 1.0, "-", "#FF00FF", "black", curve=False) + self._addLine(plot, 1.0, "-.", "#FF00FF", "black", curve=False) + self._addLine(plot, 1.0, "--", "#FF00FF", "black", curve=False) + self._addLine(plot, 0.5, "--", "#FF00FF", "black", curve=False) + self._addLine(plot, 1.5, "--", "#FF00FF", "black", curve=False) + self._addLine(plot, 2.0, "--", "#FF00FF", "black", curve=False) + plot.setGraphXLimits(0, 100) + plot.setGraphYLimits(0, 100) + + @_register("curveshape", "Curve vs Shape") + def _setupLineStyleCurveShape(self, plot): + self._addLine(plot, 1.0, (0, (5, 5)), "#00FF00", None, curve=False) + self._addLine(plot, 4.0, (0, (3, 3)), "#00FF00", None, curve=False) + self._addLine(plot, 4.0, (0, (5, 5)), "#00FF00", None, curve=False) + self._addLine(plot, 4.0, (0, (7, 7)), "#00FF00", None, curve=False) + self._addLine(plot, 1.0, (0, (5, 5)), "#00FFFF", None, curve=True) + self._addLine(plot, 4.0, (0, (3, 3)), "#00FFFF", None, curve=True) + self._addLine(plot, 4.0, (0, (5, 5)), "#00FFFF", None, curve=True) + self._addLine(plot, 4.0, (0, (7, 7)), "#00FFFF", None, curve=True) + plot.setGraphXLimits(0, 100) + plot.setGraphYLimits(0, 100) + + @_register("text", "Text") + def _setupText(self, plot): + plot.clear() + plot.getDefaultColormap().setName("viridis") + + # Add an image to the plot + x = numpy.outer(numpy.linspace(-10, 10, 200), numpy.linspace(-10, 5, 150)) + image = numpy.sin(x) / x + plot.addImage(image) + + label = Marker() + label.setPosition(50, 50) + label.setText("Foo bar\nmmmmmmmmmmmmmmmmmmmm") + label.setBackgroundColor("#FFFFFF44") + plot.addItem(label) + + label2 = Marker() + label2.setPosition(70, 70) + label2.setText("Foo bar") + label2.setColor("red") + label2.setBackgroundColor("#00000044") + plot.addItem(label2) + + label3 = Marker() + label3.setPosition(10, 70) + label3.setText("Pioupiou") + label3.setColor("yellow") + label3.setBackgroundColor("#000000") + plot.addItem(label3) + + +def main(): + global app + app = qt.QApplication([]) + + # Create the ad hoc window containing a PlotWidget and associated tools + window = MyPlotWindow() + window.setAttribute(qt.Qt.WA_DeleteOnClose) + window.show() + if len(sys.argv) == 1: + useCase = "linestyle" + else: + useCase = sys.argv[1] + window.showUseCase(useCase) + app.exec() + + +if __name__ == "__main__": + main()