+
+The `PythonQwt` project was initiated to solve -at least temporarily- the
+obsolescence issue of `PyQwt` (the Python-Qwt C++ bindings library) which is
+no longer maintained. The idea was to translate the original Qwt C++ code to
+Python and then to optimize some parts of the code by writing new modules
+based on NumPy and other libraries.
+
+The `PythonQwt` package consists of a single Python package named `qwt` and
+of a few other files (examples, doc, ...).
+
+See documentation [online](https://pythonqwt.readthedocs.io/en/latest/) or [PDF](https://pythonqwt.readthedocs.io/_/downloads/en/latest/pdf/) for more details on
+the library and [changelog](CHANGELOG.md) for recent history of changes.
+
+## Sample
+
+```python
+import qwt
+import numpy as np
+
+app = qwt.qt.QtGui.QApplication([])
+
+# Create plot widget
+plot = qwt.QwtPlot("Trigonometric functions")
+plot.insertLegend(qwt.QwtLegend(), qwt.QwtPlot.BottomLegend)
+
+# Create two curves and attach them to plot
+x = np.linspace(-10, 10, 500)
+qwt.QwtPlotCurve.make(x, np.cos(x), "Cosinus", plot, linecolor="red", antialiased=True)
+qwt.QwtPlotCurve.make(x, np.sin(x), "Sinus", plot, linecolor="blue", antialiased=True)
+
+# Resize and show plot
+plot.resize(600, 300)
+plot.show()
+
+app.exec_()
+```
+
+
+## Examples (tests)
+
+The GUI-based test launcher may be executed from Python:
+
+```python
+from qwt import tests
+tests.run()
+```
+
+or from the command line:
+
+```bash
+PythonQwt-tests
+```
+
+## Overview
+
+The `qwt` package is a pure Python implementation of `Qwt` C++ library with
+the following limitations.
+
+The following `Qwt` classes won't be reimplemented in `qwt` because more
+powerful features already exist in `guiqwt`: `QwtPlotZoomer`,
+`QwtCounter`, `QwtEventPattern`, `QwtPicker`, `QwtPlotPicker`.
+
+Only the following plot items are currently implemented in `qwt` (the only
+plot items needed by `guiqwt`): `QwtPlotItem` (base class), `QwtPlotItem`,
+`QwtPlotMarker`, `QwtPlotSeriesItem` and `QwtPlotCurve`.
+
+See "Overview" section in [documentation](https://pythonqwt.readthedocs.io/en/latest/)
+for more details on API limitations when comparing to Qwt.
+
+## Dependencies
+
+### Requirements ###
+- Python >=2.6 or Python >=3.2
+- PyQt4 >=4.4 or PyQt5 >= 5.5
+- NumPy >= 1.5
+
+## Installation
+
+From the source package:
+
+```bash
+python setup.py install
+```
+
+## Copyrights
+
+#### Main code base
+- Copyright © 2002 Uwe Rathmann, for the original Qwt C++ code
+- Copyright © 2015 Pierre Raybaut, for the Qwt C++ to Python translation and
+optimization
+- Copyright © 2015 Pierre Raybaut, for the PythonQwt specific and exclusive
+Python material
+
+#### PyQt, PySide and Python2/Python3 compatibility modules
+- Copyright © 2009-2013 Pierre Raybaut
+- Copyright © 2013-2015 The Spyder Development Team
+
+#### Some examples
+- Copyright © 2003-2009 Gerard Vermeulen, for the original PyQwt code
+- Copyright © 2015 Pierre Raybaut, for the PyQt5/PySide port and further
+developments (e.g. ported to PythonQwt API)
+
+## License
+
+The `qwt` Python package was partly (>95%) translated from Qwt C++ library:
+the associated code is distributed under the terms of the LGPL license. The
+rest of the code was either wrote from scratch or strongly inspired from MIT
+licensed third-party software.
+
+See included [LICENSE](LICENSE) file for more details about licensing terms.
diff --git a/pyproject.toml b/pyproject.toml
deleted file mode 100644
index c972b99..0000000
--- a/pyproject.toml
+++ /dev/null
@@ -1,83 +0,0 @@
-# PythonQwt setup configuration file
-
-[build-system]
-requires = ["setuptools"]
-build-backend = "setuptools.build_meta"
-
-[project]
-name = "PythonQwt"
-authors = [{ name = "Pierre Raybaut", email = "pierre.raybaut@gmail.com" }]
-description = "Qt plotting widgets for Python"
-readme = "README.md"
-license = { file = "LICENSE" }
-classifiers = [
- "Topic :: Scientific/Engineering",
- "Topic :: Scientific/Engineering :: Human Machine Interfaces",
- "Topic :: Scientific/Engineering :: Visualization",
- "Topic :: Software Development :: Widget Sets",
- "Topic :: Software Development :: Libraries :: Python Modules",
- "Topic :: Utilities",
- "Topic :: Software Development :: User Interfaces",
- "Operating System :: MacOS",
- "Operating System :: Microsoft :: Windows",
- "Operating System :: OS Independent",
- "Operating System :: POSIX",
- "Operating System :: Unix",
- "Programming Language :: Python :: 3.9",
- "Programming Language :: Python :: 3.10",
- "Programming Language :: Python :: 3.11",
- "Programming Language :: Python :: 3.12",
- "Programming Language :: Python :: 3.13",
- "Programming Language :: Python :: 3.14",
-]
-requires-python = ">=3.9, <4"
-dependencies = ["NumPy>=1.21", "QtPy>=1.9"]
-dynamic = ["version"]
-
-[project.urls]
-Homepage = "https://github.com/PlotPyStack/PythonQwt/"
-Documentation = "https://PythonQwt.readthedocs.io/en/latest/"
-
-[project.gui-scripts]
-PythonQwt-tests = "qwt.tests:run"
-
-[project.optional-dependencies]
-dev = ["build", "ruff", "pylint", "Coverage", "pre-commit"]
-doc = ["PyQt5", "sphinx>6", "python-docs-theme"]
-test = ["pytest", "pytest-xvfb"]
-
-[tool.setuptools.packages.find]
-include = ["qwt*"]
-
-[tool.setuptools.package-data]
-"*" = ["*.png", "*.svg", "*.mo", "*.cfg", "*.toml"]
-
-[tool.setuptools.dynamic]
-version = { attr = "qwt.__version__" }
-
-[tool.pytest.ini_options]
-addopts = "qwt"
-
-[tool.ruff]
-exclude = [".git", ".vscode", "build", "dist"]
-line-length = 88 # Same as Black.
-indent-width = 4 # Same as Black.
-target-version = "py39" # Assume Python 3.9.
-
-[tool.ruff.lint]
-# all rules can be found here: https://beta.ruff.rs/docs/rules/
-select = ["E", "F", "W", "I", "NPY201"]
-ignore = [
- "E203", # space before : (needed for how black formats slicing)
- "E501", # line too long
-]
-
-[tool.ruff.format]
-quote-style = "double" # Like Black, use double quotes for strings.
-indent-style = "space" # Like Black, indent with spaces, rather than tabs.
-skip-magic-trailing-comma = false # Like Black, respect magic trailing commas.
-line-ending = "auto" # Like Black, automatically detect the appropriate line ending.
-
-[tool.ruff.lint.per-file-ignores]
-"doc/*" = ["E402"]
-"qwt/tests/*" = ["E402"]
diff --git a/qwt/__init__.py b/qwt/__init__.py
deleted file mode 100644
index 8f0c9ed..0000000
--- a/qwt/__init__.py
+++ /dev/null
@@ -1,170 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the Qwt License
-# Copyright (c) 2002 Uwe Rathmann, for the original C++ code
-# Copyright (c) 2015 Pierre Raybaut, for the Python translation/optimization
-# (see LICENSE file for more details)
-
-"""
-PythonQwt
-=========
-
-The ``PythonQwt`` package is a 2D-data plotting library using Qt graphical
-user interfaces for the Python programming language.
-
-It consists of a single Python package named `qwt` which is a pure Python
-implementation of Qwt C++ library with some limitations.
-
-.. image:: /../qwt/tests/data/testlauncher.png
-
-External resources:
- * Python Package Index: `PyPI`_
- * Project page on GitHub: `GitHubPage`_
- * Bug reports and feature requests: `GitHub`_
-
-.. _PyPI: https://pypi.org/project/PythonQwt/
-.. _GitHubPage: https://github.com/PlotPyStack/PythonQwt
-.. _GitHub: https://github.com/PlotPyStack/PythonQwt
-"""
-
-import warnings
-
-from qwt.color_map import QwtLinearColorMap # noqa: F401
-from qwt.interval import QwtInterval
-from qwt.legend import QwtLegend, QwtLegendData, QwtLegendLabel # noqa: F401
-from qwt.painter import QwtPainter # noqa: F401
-from qwt.plot import QwtPlot # noqa: F401
-from qwt.plot_canvas import QwtPlotCanvas # noqa: F401
-from qwt.plot_curve import QwtPlotCurve as QPC # see deprecated section
-from qwt.plot_curve import QwtPlotItem # noqa: F401
-from qwt.plot_directpainter import QwtPlotDirectPainter # noqa: F401
-from qwt.plot_grid import QwtPlotGrid as QPG # see deprecated section
-from qwt.plot_marker import QwtPlotMarker # noqa: F401
-from qwt.plot_renderer import QwtPlotRenderer # noqa: F401
-from qwt.plot_series import ( # noqa: F401
- QwtPlotSeriesItem,
- QwtPointArrayData,
- QwtSeriesData,
- QwtSeriesStore,
-)
-from qwt.scale_div import QwtScaleDiv # noqa: F401
-from qwt.scale_draw import ( # noqa: F401
- QwtAbstractScaleDraw,
- QwtDateTimeScaleDraw,
- QwtScaleDraw,
-)
-from qwt.scale_engine import ( # noqa: F401
- QwtDateTimeScaleEngine,
- QwtLinearScaleEngine,
- QwtLogScaleEngine,
-)
-from qwt.scale_map import QwtScaleMap # noqa: F401
-from qwt.symbol import QwtSymbol as QSbl # see deprecated section
-from qwt.text import QwtText # noqa: F401
-from qwt.toqimage import array_to_qimage as toQImage # noqa: F401
-
-__version__ = "0.16.0"
-QWT_VERSION_STR = "6.1.5"
-
-
-## ============================================================================
-## Deprecated classes and attributes (to be removed in next major release)
-## ============================================================================
-# Remove deprecated QwtPlotItem.setAxis (replaced by setAxes)
-# Remove deprecated QwtPlotCanvas.invalidatePaintCache (replaced by replot)
-## ============================================================================
-class QwtDoubleInterval(QwtInterval):
- def __init__(self, minValue=0.0, maxValue=-1.0, borderFlags=None):
- warnings.warn(
- "`QwtDoubleInterval` has been removed in Qwt6: "
- "please use `QwtInterval` instead",
- RuntimeWarning,
- )
- super(QwtDoubleInterval, self).__init__(minValue, maxValue, borderFlags)
-
-
-## ============================================================================
-class QwtLog10ScaleEngine(QwtLogScaleEngine):
- def __init__(self):
- warnings.warn(
- "`QwtLog10ScaleEngine` has been removed in Qwt6: "
- "please use `QwtLogScaleEngine` instead",
- RuntimeWarning,
- )
- super(QwtLog10ScaleEngine, self).__init__(10)
-
-
-## ============================================================================
-class QwtPlotPrintFilter(object):
- def __init__(self):
- raise NotImplementedError(
- "`QwtPlotPrintFilter` has been removed in Qwt6: "
- "please rely on `QwtPlotRenderer` instead"
- )
-
-
-## ============================================================================
-class QwtPlotCurve(QPC):
- @property
- def Yfx(self):
- raise NotImplementedError(
- "`Yfx` attribute has been removed "
- "(curve types are no longer implemented in Qwt6)"
- )
-
- @property
- def Xfy(self):
- raise NotImplementedError(
- "`Yfx` attribute has been removed "
- "(curve types are no longer implemented in Qwt6)"
- )
-
-
-## ============================================================================
-class QwtSymbol(QSbl):
- def draw(self, painter, *args):
- warnings.warn(
- "`draw` has been removed in Qwt6: "
- "please rely on `drawSymbol` and `drawSymbols` instead",
- RuntimeWarning,
- )
- from qtpy.QtCore import QPointF
-
- if len(args) == 2:
- self.drawSymbols(painter, [QPointF(*args)])
- else:
- self.drawSymbol(painter, *args)
-
-
-## ============================================================================
-class QwtPlotGrid(QPG):
- def majPen(self):
- warnings.warn(
- "`majPen` has been removed in Qwt6: please use `majorPen` instead",
- RuntimeWarning,
- )
- return self.majorPen()
-
- def minPen(self):
- warnings.warn(
- "`minPen` has been removed in Qwt6: please use `minorPen` instead",
- RuntimeWarning,
- )
- return self.minorPen()
-
- def setMajPen(self, *args):
- warnings.warn(
- "`setMajPen` has been removed in Qwt6: please use `setMajorPen` instead",
- RuntimeWarning,
- )
- return self.setMajorPen(*args)
-
- def setMinPen(self, *args):
- warnings.warn(
- "`setMinPen` has been removed in Qwt6: please use `setMinorPen` instead",
- RuntimeWarning,
- )
- return self.setMinorPen(*args)
-
-
-## ============================================================================
diff --git a/qwt/_math.py b/qwt/_math.py
deleted file mode 100644
index 5ee6911..0000000
--- a/qwt/_math.py
+++ /dev/null
@@ -1,75 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the Qwt License
-# Copyright (c) 2002 Uwe Rathmann, for the original C++ code
-# Copyright (c) 2015 Pierre Raybaut, for the Python translation/optimization
-# (see LICENSE file for more details)
-
-import math
-
-from qtpy.QtCore import qFuzzyCompare
-
-
-def qwtFuzzyCompare(value1, value2, intervalSize):
- eps = abs(1.0e-6 * intervalSize)
- if value2 - value1 > eps:
- return -1
- elif value1 - value2 > eps:
- return 1
- else:
- return 0
-
-
-def qwtFuzzyGreaterOrEqual(d1, d2):
- return (d1 >= d2) or qFuzzyCompare(d1, d2)
-
-
-def qwtFuzzyLessOrEqual(d1, d2):
- return (d1 <= d2) or qFuzzyCompare(d1, d2)
-
-
-def qwtSign(x):
- if x > 0.0:
- return 1
- elif x < 0.0:
- return -1
- else:
- return 0
-
-
-def qwtSqr(x):
- return x**2
-
-
-def qwtFastAtan(x):
- if x < -1.0:
- return -0.5 * math.pi - x / (x**2 + 0.28)
- elif x > 1.0:
- return 0.5 * math.pi - x / (x**2 + 0.28)
- else:
- return x / (1.0 + x**2 * 0.28)
-
-
-def qwtFastAtan2(y, x):
- if x > 0:
- return qwtFastAtan(y / x)
- elif x < 0:
- d = qwtFastAtan(y / x)
- if y >= 0:
- return d + math.pi
- else:
- return d - math.pi
- elif y < 0.0:
- return -0.5 * math.pi
- elif y > 0.0:
- return 0.5 * math.pi
- else:
- return 0.0
-
-
-def qwtRadians(degrees):
- return degrees * math.pi / 180.0
-
-
-def qwtDegrees(radians):
- return radians * 180.0 / math.pi
diff --git a/qwt/color_map.py b/qwt/color_map.py
deleted file mode 100644
index 9a54cf0..0000000
--- a/qwt/color_map.py
+++ /dev/null
@@ -1,386 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the Qwt License
-# Copyright (c) 2002 Uwe Rathmann, for the original C++ code
-# Copyright (c) 2015 Pierre Raybaut, for the Python translation/optimization
-# (see LICENSE file for more details)
-
-"""
-Color maps
-----------
-
-QwtColorMap
-~~~~~~~~~~~
-
-.. autoclass:: QwtColorMap
- :members:
-
-QwtLinearColorMap
-~~~~~~~~~~~~~~~~~
-
-.. autoclass:: QwtLinearColorMap
- :members:
-
-QwtAlphaColorMap
-~~~~~~~~~~~~~~~~
-
-.. autoclass:: QwtAlphaColorMap
- :members:
-"""
-
-from qtpy.QtCore import QObject, Qt, qIsNaN
-from qtpy.QtGui import QColor, qAlpha, qBlue, qGreen, qRed, qRgb, qRgba
-
-
-class ColorStop(object):
- def __init__(self, pos=0.0, color=None):
- self.pos = pos
- if color is None:
- self.rgb = 0
- else:
- self.rgb = color.rgba()
- self.r = qRed(self.rgb)
- self.g = qGreen(self.rgb)
- self.b = qBlue(self.rgb)
- self.a = qAlpha(self.rgb)
-
- # when mapping a value to rgb we will have to calcualate:
- # - const int v = int( ( s1.v0 + ratio * s1.vStep ) + 0.5 );
- # Thus adding 0.5 ( for rounding ) can be done in advance
- self.r0 = self.r + 0.5
- self.g0 = self.g + 0.5
- self.b0 = self.b + 0.5
- self.a0 = self.a + 0.5
-
- self.rStep = self.gStep = self.bStep = self.aStep = 0.0
- self.posStep = 0.0
-
- def updateSteps(self, nextStop):
- self.rStep = nextStop.r - self.r
- self.gStep = nextStop.g - self.g
- self.bStep = nextStop.b - self.b
- self.aStep = nextStop.a - self.a
- self.posStep = nextStop.pos - self.pos
-
-
-class ColorStops(object):
- def __init__(self):
- self.__doAlpha = False
- self.__stops = []
-
- def insert(self, pos, color):
- if pos < 0.0 or pos > 1.0:
- return
- if len(self.__stops) == 0:
- index = 0
- self.__stops = [None]
- else:
- index = self.findUpper(pos)
- if (
- index == len(self.__stops)
- or abs(self.__stops[index].pos - pos) >= 0.001
- ):
- self.__stops.append(None)
- for i in range(len(self.__stops) - 1, index, -1):
- self.__stops[i] = self.__stops[i - 1]
- self.__stops[index] = ColorStop(pos, color)
- self.__doAlpha = color.alpha() != 255
- if index > 0:
- self.__stops[index - 1].updateSteps(self.__stops[index])
- if index < len(self.__stops) - 1:
- self.__stops[index].updateSteps(self.__stops[index + 1])
-
- def stops(self):
- return self.__stops
-
- def findUpper(self, pos):
- index = 0
- n = len(self.__stops)
-
- while n > 0:
- half = n >> 1
- middle = index + half
- if self.__stops[middle].pos <= pos:
- index = middle + 1
- n -= half + 1
- else:
- n = half
- return index
-
- def rgb(self, mode, pos):
- if pos <= 0.0:
- return self.__stops[0].rgb
- if pos >= 1.0:
- return self.__stops[-1].rgb
-
- index = self.findUpper(pos)
- if mode == QwtLinearColorMap.FixedColors:
- return self.__stops[index - 1].rgb
- else:
- s1 = self.__stops[index - 1]
- ratio = (pos - s1.pos) / s1.posStep
- r = int(s1.r0 + ratio * s1.rStep)
- g = int(s1.g0 + ratio * s1.gStep)
- b = int(s1.b0 + ratio * s1.bStep)
- if self.__doAlpha:
- if s1.aStep:
- a = int(s1.a0 + ratio * s1.aStep)
- return qRgba(r, g, b, a)
- else:
- return qRgba(r, g, b, s1.a)
- else:
- return qRgb(r, g, b)
-
-
-class QwtColorMap(object):
- """
- QwtColorMap is used to map values into colors.
-
- For displaying 3D data on a 2D plane the 3rd dimension is often
- displayed using colors, like f.e in a spectrogram.
-
- Each color map is optimized to return colors for only one of the
- following image formats:
-
- * `QImage.Format_Indexed8`
- * `QImage.Format_ARGB32`
-
- .. py:class:: QwtColorMap(format_)
-
- :param int format_: Preferred format of the color map (:py:data:`QwtColorMap.RGB` or :py:data:`QwtColorMap.Indexed`)
-
- .. seealso ::
-
- :py:data:`qwt.QwtScaleWidget`
- """
-
- # enum Format
- RGB, Indexed = list(range(2))
-
- def __init__(self, format_=None):
- if format_ is None:
- format_ = self.RGB
- self.__format = format_
-
- def color(self, interval, value):
- """
- Map a value into a color
-
- :param qwt.interval.QwtInterval interval: valid interval for value
- :param float value: value
- :return: the color corresponding to value
-
- .. warning ::
-
- This method is slow for Indexed color maps. If it is necessary to
- map many values, its better to get the color table once and find
- the color using `colorIndex()`.
- """
- if self.__format == self.RGB:
- return QColor.fromRgba(self.rgb(interval, value))
- else:
- index = self.colorIndex(interval, value)
- return self.colorTable(interval)[index]
-
- def format(self):
- return self.__format
-
- def colorTable(self, interval):
- """
- Build and return a color map of 256 colors
-
- :param qwt.interval.QwtInterval interval: range for the values
- :return: a color table, that can be used for a `QImage`
-
- The color table is needed for rendering indexed images in combination
- with using `colorIndex()`.
- """
- table = [0] * 256
- if interval.isValid():
- step = interval.width() / (len(table) - 1)
- for i in range(len(table)):
- table[i] = self.rgb(interval, interval.minValue() + step * i)
- return table
-
- def rgb(self, interval, value):
- # To be reimplemented
- return QColor().rgb()
-
- def colorIndex(self, interval, value):
- # To be reimplemented
- return 0
-
-
-class QwtLinearColorMap_PrivateData(QObject):
- def __init__(self):
- QObject.__init__(self)
-
- self.colorStops = ColorStops()
- self.mode = None
-
-
-class QwtLinearColorMap(QwtColorMap):
- """
- Build a linear color map with two stops.
-
- .. py:class:: QwtLinearColorMap(format_)
-
- Build a color map with two stops at 0.0 and 1.0.
- The color at 0.0 is `Qt.blue`, at 1.0 it is `Qt.yellow`.
-
- :param int format_: Preferred format of the color map (:py:data:`QwtColorMap.RGB` or :py:data:`QwtColorMap.Indexed`)
-
- .. py:class:: QwtLinearColorMap(color1, color2, [format_=QwtColorMap.RGB]):
- :noindex:
-
- Build a color map with two stops at 0.0 and 1.0.
-
- :param QColor color1: color at 0.
- :param QColor color2: color at 1.
- :param int format_: Preferred format of the color map (:py:data:`QwtColorMap.RGB` or :py:data:`QwtColorMap.Indexed`)
- """
-
- # enum Mode
- FixedColors, ScaledColors = list(range(2))
-
- def __init__(self, *args):
- color1, color2 = QColor(Qt.blue), QColor(Qt.yellow)
- format_ = QwtColorMap.RGB
- if len(args) == 1:
- (format_,) = args
- elif len(args) == 2:
- color1, color2 = args
- elif len(args) == 3:
- color1, color2, format_ = args
- elif len(args) != 0:
- raise TypeError(
- "%s() takes 0, 1, 2 or 3 argument(s) (%s given)"
- % (self.__class__.__name__, len(args))
- )
- super(QwtLinearColorMap, self).__init__(format_)
- self.__data = QwtLinearColorMap_PrivateData()
- self.__data.mode = self.ScaledColors
- self.setColorInterval(color1, color2)
-
- def setMode(self, mode):
- """
- Set the mode of the color map
-
- :param int mode: :py:data:`QwtLinearColorMap.FixedColors` or :py:data:`QwtLinearColorMap.ScaledColors`
-
- `FixedColors` means the color is calculated from the next lower color
- stop. `ScaledColors` means the color is calculated by interpolating
- the colors of the adjacent stops.
- """
- self.__data.mode = mode
-
- def mode(self):
- """
- :return: the mode of the color map
-
- .. seealso ::
-
- :py:meth:`QwtLinearColorMap.setMode`
- """
- return self.__data.mode
-
- def setColorInterval(self, color1, color2):
- self.__data.colorStops = ColorStops()
- self.__data.colorStops.insert(0.0, QColor(color1))
- self.__data.colorStops.insert(1.0, QColor(color2))
-
- def addColorStop(self, value, color):
- if value >= 0.0 and value <= 1.0:
- self.__data.colorStops.insert(value, QColor(color))
-
- def colorStops(self):
- return self.__data.colorStops.stops()
-
- def color1(self):
- return QColor(self.__data.colorStops.rgb(self.__data.mode, 0.0))
-
- def color2(self):
- return QColor(self.__data.colorStops.rgb(self.__data.mode, 1.0))
-
- def rgb(self, interval, value):
- if qIsNaN(value):
- return 0
- width = interval.width()
- if width <= 0.0:
- return 0
- ratio = (value - interval.minValue()) / width
- return self.__data.colorStops.rgb(self.__data.mode, ratio)
-
- def colorIndex(self, interval, value):
- width = interval.width()
- if qIsNaN(value) or width <= 0.0 or value <= interval.minValue():
- return 0
- if value >= interval.maxValue():
- return 255
- ratio = (value - interval.minValue()) / width
- if self.__data.mode == self.FixedColors:
- return int(ratio * 255)
- else:
- return int(ratio * 255 + 0.5)
-
-
-class QwtAlphaColorMap_PrivateData(QObject):
- def __init__(self):
- QObject.__init__(self)
-
- self.color = QColor()
- self.rgb = QColor().rgb()
- self.rgbMax = QColor().rgb()
-
-
-class QwtAlphaColorMap(QwtColorMap):
- """
- QwtAlphaColorMap varies the alpha value of a color
-
- .. py:class:: QwtAlphaColorMap(color)
-
- Build a color map varying the alpha value of a color.
-
- :param QColor color: color of the map
- """
-
- def __init__(self, color):
- super(QwtAlphaColorMap, self).__init__(QwtColorMap.RGB)
- self.__data = QwtAlphaColorMap_PrivateData()
- self.setColor(color)
-
- def setColor(self, color):
- """
- Set the color of the map
-
- :param QColor color: color of the map
- """
- self.__data.color = color
- self.__data.rgb = color.rgb() & qRgba(255, 255, 255, 0)
- self.__data.rgbMax = self.__data.rgb | (255 << 24)
-
- def color(self):
- """
- :return: the color of the map
-
- .. seealso ::
-
- :py:meth:`QwtAlphaColorMap.setColor`
- """
- return self.__data.color
-
- def rgb(self, interval, value):
- if qIsNaN(value):
- return 0
- width = interval.width()
- if width <= 0.0:
- return 0
- if value <= interval.minValue():
- return self.__data.rgb
- if value >= interval.maxValue():
- return self.__data.rgbMax
- ratio = (value - interval.minValue()) / width
- return self.__data.rgb | (int(round(255 * ratio)) << 24)
-
- def colorIndex(self, interval, value):
- return 0
diff --git a/qwt/column_symbol.py b/qwt/column_symbol.py
deleted file mode 100644
index c4e46b0..0000000
--- a/qwt/column_symbol.py
+++ /dev/null
@@ -1,167 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the Qwt License
-# Copyright (c) 2002 Uwe Rathmann, for the original C++ code
-# Copyright (c) 2015 Pierre Raybaut, for the Python translation/optimization
-# (see LICENSE file for more details)
-
-from qtpy.QtCore import QLineF, QObject, QRectF, Qt
-from qtpy.QtGui import QPalette, QPolygonF
-
-from qwt.interval import QwtInterval
-
-
-def qwtDrawBox(p, rect, pal, lw):
- if lw > 0.0:
- if rect.width() == 0.0:
- p.setPen(pal.dark().color())
- p.drawLine(QLineF(rect.topLeft(), rect.bottomLeft()))
- return
- if rect.height() == 0.0:
- p.setPen(pal.dark().color())
- p.drawLine(QLineF(rect.topLeft(), rect.topRight()))
- return
- lw = min([lw, rect.height() / 2.0 - 1.0])
- lw = min([lw, rect.width() / 2.0 - 1.0])
- outerRect = rect.adjusted(0, 0, 1, 1)
- polygon = QPolygonF(outerRect)
- if outerRect.width() > 2 * lw and outerRect.height() > 2 * lw:
- innerRect = outerRect.adjusted(lw, lw, -lw, -lw)
- polygon = polygon.subtracted(innerRect)
- p.setPen(Qt.NoPen)
- p.setBrush(pal.dark())
- p.drawPolygon(polygon)
- windowRect = rect.adjusted(lw, lw, -lw + 1, -lw + 1)
- if windowRect.isValid():
- p.fillRect(windowRect, pal.window())
-
-
-def qwtDrawPanel(painter, rect, pal, lw):
- if lw > 0.0:
- if rect.width() == 0.0:
- painter.setPen(pal.window().color())
- painter.drawLine(QLineF(rect.topLeft(), rect.bottomLeft()))
- return
- if rect.height() == 0.0:
- painter.setPen(pal.window().color())
- painter.drawLine(QLineF(rect.topLeft(), rect.topRight()))
- return
- lw = min([lw, rect.height() / 2.0 - 1.0])
- lw = min([lw, rect.width() / 2.0 - 1.0])
- outerRect = rect.adjusted(0, 0, 1, 1)
- innerRect = outerRect.adjusted(lw, lw, -lw, -lw)
- lines = [QPolygonF(), QPolygonF()]
- lines[0] += outerRect.bottomLeft()
- lines[0] += outerRect.topLeft()
- lines[0] += outerRect.topRight()
- lines[0] += innerRect.topRight()
- lines[0] += innerRect.topLeft()
- lines[0] += innerRect.bottomLeft()
- lines[1] += outerRect.topRight()
- lines[1] += outerRect.bottomRight()
- lines[1] += outerRect.bottomLeft()
- lines[1] += innerRect.bottomLeft()
- lines[1] += innerRect.bottomRight()
- lines[1] += innerRect.topRight()
- painter.setPen(Qt.NoPen)
- painter.setBrush(pal.light())
- painter.drawPolygon(lines[0])
- painter.setBrush(pal.dark())
- painter.drawPolygon(lines[1])
- painter.fillRect(rect.adjusted(lw, lw, -lw + 1, -lw + 1), pal.window())
-
-
-class QwtColumnSymbol_PrivateData(QObject):
- def __init__(self):
- QObject.__init__(self)
-
- self.style = QwtColumnSymbol.Box
- self.frameStyle = QwtColumnSymbol.Raised
- self.lineWidth = 2
- self.palette = QPalette(Qt.gray)
-
-
-class QwtColumnSymbol(object):
- # enum Style
- NoStyle = -1
- Box = 0
- UserStyle = 1000
-
- # enum FrameStyle
- NoFrame, Plain, Raised = list(range(3))
-
- def __init__(self, style):
- self.__data = QwtColumnSymbol_PrivateData()
- self.__data.style = style
-
- def setStyle(self, style):
- self.__data.style = style
-
- def style(self):
- return self.__data.style
-
- def setPalette(self, palette):
- self.__data.palette = palette
-
- def palette(self):
- return self.__data.palette
-
- def setFrameStyle(self, frameStyle):
- self.__data.frameStyle = frameStyle
-
- def frameStyle(self):
- return self.__data.frameStyle
-
- def setLineWidth(self, width):
- self.__data.lineWidth = width
-
- def lineWidth(self):
- return self.__data.lineWidth
-
- def draw(self, painter, rect):
- painter.save()
- if self.__data.style == QwtColumnSymbol.Box:
- self.drawBox(painter, rect)
- painter.restore()
-
- def drawBox(self, painter, rect):
- r = rect.toRect()
- if self.__data.frameStyle == QwtColumnSymbol.Raised:
- qwtDrawPanel(painter, r, self.__data.palette, self.__data.lineWidth)
- elif self.__data.frameStyle == QwtColumnSymbol.Plain:
- qwtDrawBox(painter, r, self.__data.palette, self.__data.lineWidth)
- else:
- painter.fillRect(r.adjusted(0, 0, 1, 1), self.__data.palette.window())
-
-
-class QwtColumnRect(object):
- # enum Direction
- LeftToRight, RightToLeft, BottomToTop, TopToBottom = list(range(4))
-
- def __init__(self):
- self.hInterval = QwtInterval()
- self.vInterval = QwtInterval()
- self.direction = 0
-
- def toRect(self):
- r = QRectF(
- self.hInterval.minValue(),
- self.vInterval.minValue(),
- self.hInterval.maxValue() - self.hInterval.minValue(),
- self.vInterval.maxValue() - self.vInterval.minValue(),
- )
- r = r.normalized()
- if self.hInterval.borderFlags() & QwtInterval.ExcludeMinimum:
- r.adjust(1, 0, 0, 0)
- if self.hInterval.borderFlags() & QwtInterval.ExcludeMaximum:
- r.adjust(0, 0, -1, 0)
- if self.vInterval.borderFlags() & QwtInterval.ExcludeMinimum:
- r.adjust(0, 1, 0, 0)
- if self.vInterval.borderFlags() & QwtInterval.ExcludeMaximum:
- r.adjust(0, 0, 0, -1)
- return r
-
- def orientation(self):
- if self.direction in (self.LeftToRight, self.RightToLeft):
- return Qt.Horizontal
- return Qt.Vertical
diff --git a/qwt/dyngrid_layout.py b/qwt/dyngrid_layout.py
deleted file mode 100644
index 45850da..0000000
--- a/qwt/dyngrid_layout.py
+++ /dev/null
@@ -1,359 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the Qwt License
-# Copyright (c) 2002 Uwe Rathmann, for the original C++ code
-# Copyright (c) 2015 Pierre Raybaut, for the Python translation/optimization
-# (see LICENSE file for more details)
-
-"""
-qwt.dyngrid_layout
-------------------
-
-The `dyngrid_layout` module provides the `QwtDynGridLayout` class.
-
-.. autoclass:: QwtDynGridLayout
- :members:
-"""
-
-from qtpy.QtCore import QObject, QRect, QSize, Qt
-from qtpy.QtWidgets import QLayout
-
-
-class QwtDynGridLayout_PrivateData(QObject):
- def __init__(self):
- QObject.__init__(self)
-
- self.isDirty = True
- self.maxColumns = 0
- self.numRows = 0
- self.numColumns = 0
- self.expanding = Qt.Horizontal
- self.itemSizeHints = []
- self.itemList = []
-
- def updateLayoutCache(self):
- self.itemSizeHints = [it.sizeHint() for it in self.itemList]
- self.isDirty = False
-
-
-class QwtDynGridLayout(QLayout):
- """
- The `QwtDynGridLayout` class lays out widgets in a grid,
- adjusting the number of columns and rows to the current size.
-
- `QwtDynGridLayout` takes the space it gets, divides it up into rows and
- columns, and puts each of the widgets it manages into the correct cell(s).
- It lays out as many number of columns as possible (limited by
- :py:meth:`maxColumns()`).
-
- .. py:class:: QwtDynGridLayout(parent, margin, [spacing=-1])
-
- :param QWidget parent: parent widget
- :param int margin: margin
- :param int spacing: spacing
-
- .. py:class:: QwtDynGridLayout(spacing)
- :noindex:
-
- :param int spacing: spacing
-
- .. py:class:: QwtDynGridLayout()
- :noindex:
-
- Initialize the layout with default values.
-
- :param int spacing: spacing
- """
-
- def __init__(self, *args):
- self.__data = None
- parent = None
- margin = 0
- spacing = -1
- if len(args) in (2, 3):
- parent, margin = args[:2]
- if len(args) == 3:
- spacing = args[-1]
- elif len(args) == 1:
- if isinstance(args[0], int):
- (spacing,) = args
- else:
- (parent,) = args
- elif len(args) != 0:
- raise TypeError(
- "%s() takes 0, 1, 2 or 3 argument(s) (%s given)"
- % (self.__class__.__name__, len(args))
- )
- QLayout.__init__(self, parent)
- self.__data = QwtDynGridLayout_PrivateData()
- self.setSpacing(spacing)
- self.setContentsMargins(margin, margin, margin, margin)
-
- def invalidate(self):
- """Invalidate all internal caches"""
- self.__data.isDirty = True
- QLayout.invalidate(self)
-
- def setMaxColumns(self, maxColumns):
- """Limit the number of columns"""
- self.__data.maxColumns = maxColumns
-
- def maxColumns(self):
- """Return the upper limit for the number of columns"""
- return self.__data.maxColumns
-
- def addItem(self, item):
- """Add an item to the next free position"""
- self.__data.itemList.append(item)
- self.invalidate()
-
- def isEmpty(self):
- """Return true if this layout is empty"""
- return self.count() == 0
-
- def itemCount(self):
- """Return number of layout items"""
- return self.count()
-
- def itemAt(self, index):
- """Find the item at a specific index"""
- if index < 0 or index >= len(self.__data.itemList):
- return
- return self.__data.itemList[index]
-
- def takeAt(self, index):
- """Find the item at a specific index and remove it from the layout"""
- if index < 0 or index >= len(self.__data.itemList):
- return
- self.__data.isDirty = True
- return self.__data.itemList.pop(index)
-
- def count(self):
- """Return Number of items in the layout"""
- return len(self.__data.itemList)
-
- def setExpandingDirections(self, expanding):
- """
- Set whether this layout can make use of more space than sizeHint().
- A value of Qt.Vertical or Qt.Horizontal means that it wants to grow in
- only one dimension, while Qt.Vertical | Qt.Horizontal means that it
- wants to grow in both dimensions. The default value is 0.
- """
- self.__data.expanding = expanding
-
- def expandingDirections(self):
- """
- Returns whether this layout can make use of more space than sizeHint().
- A value of Qt.Vertical or Qt.Horizontal means that it wants to grow in
- only one dimension, while Qt.Vertical | Qt.Horizontal means that it
- wants to grow in both dimensions.
- """
- return self.__data.expanding
-
- def setGeometry(self, rect):
- """
- Reorganizes columns and rows and resizes managed items within a
- rectangle.
- """
- QLayout.setGeometry(self, rect)
- if self.isEmpty():
- return
- self.__data.numColumns = self.columnsForWidth(rect.width())
- self.__data.numRows = self.itemCount() / self.__data.numColumns
- if self.itemCount() % self.__data.numColumns:
- self.__data.numRows += 1
- itemGeometries = self.layoutItems(rect, self.__data.numColumns)
- for it, geo in zip(self.__data.itemList, itemGeometries):
- it.setGeometry(geo)
-
- def columnsForWidth(self, width):
- """
- Calculate the number of columns for a given width.
-
- The calculation tries to use as many columns as possible
- ( limited by maxColumns() )
- """
- if self.isEmpty():
- return 0
- maxColumns = self.itemCount()
- if self.__data.maxColumns > 0:
- maxColumns = min([self.__data.maxColumns, maxColumns])
- if self.maxRowWidth(maxColumns) <= width:
- return maxColumns
- for numColumns in range(2, maxColumns + 1):
- rowWidth = self.maxRowWidth(numColumns)
- if rowWidth > width:
- return numColumns - 1
- return 1
-
- def maxRowWidth(self, numColumns):
- """Calculate the width of a layout for a given number of columns."""
- colWidth = [0] * numColumns
- if self.__data.isDirty:
- self.__data.updateLayoutCache()
- for index, hint in enumerate(self.__data.itemSizeHints):
- col = index % numColumns
- colWidth[col] = max([colWidth[col], hint.width()])
- margins = self.contentsMargins()
- margin_w = margins.left() + margins.right()
- return margin_w + (numColumns - 1) * self.spacing() + sum(colWidth)
-
- def maxItemWidth(self):
- """Return the maximum width of all layout items"""
- if self.isEmpty():
- return 0
- if self.__data.isDirty:
- self.__data.updateLayoutCache()
- return max([hint.width() for hint in self.__data.itemSizeHints])
-
- def layoutItems(self, rect, numColumns):
- """
- Calculate the geometries of the layout items for a layout
- with numColumns columns and a given rectangle.
- """
- itemGeometries = []
- if numColumns == 0 or self.isEmpty():
- return itemGeometries
- numRows = int(self.itemCount() / numColumns)
- if numColumns % self.itemCount():
- numRows += 1
- if numRows == 0:
- return itemGeometries
- rowHeight = [0] * numRows
- colWidth = [0] * numColumns
- self.layoutGrid(numColumns, rowHeight, colWidth)
- expandH = self.expandingDirections() == Qt.Horizontal
- expandV = self.expandingDirections() == Qt.Vertical
- if expandH or expandV:
- self.stretchGrid(rect, numColumns, rowHeight, colWidth)
- maxColumns = self.__data.maxColumns
- self.__data.maxColumns = numColumns
- alignedRect = self.alignmentRect(rect)
- self.__data.maxColumns = maxColumns
- xOffset = 0 if expandH else alignedRect.x()
- yOffset = 0 if expandV else alignedRect.y()
- colX = [0] * numColumns
- rowY = [0] * numRows
- xySpace = self.spacing()
- margins = self.contentsMargins()
- rowY[0] = yOffset + margins.bottom()
- for r in range(1, numRows):
- rowY[r] = rowY[r - 1] + rowHeight[r - 1] + xySpace
- colX[0] = xOffset + margins.left()
- for c in range(1, numColumns):
- colX[c] = colX[c - 1] + colWidth[c - 1] + xySpace
- itemCount = len(self.__data.itemList)
- for i in range(itemCount):
- row = int(i / numColumns)
- col = i % numColumns
- itemGeometry = QRect(colX[col], rowY[row], colWidth[col], rowHeight[row])
- itemGeometries.append(itemGeometry)
- return itemGeometries
-
- def layoutGrid(self, numColumns, rowHeight, colWidth):
- """
- Calculate the dimensions for the columns and rows for a grid
- of numColumns columns.
- """
- if numColumns <= 0:
- return
- if self.__data.isDirty:
- self.__data.updateLayoutCache()
- for index in range(len(self.__data.itemSizeHints)):
- row = int(index / numColumns)
- col = index % numColumns
- size = self.__data.itemSizeHints[index]
- if col == 0:
- rowHeight[row] = size.height()
- else:
- rowHeight[row] = max([rowHeight[row], size.height()])
- if row == 0:
- colWidth[col] = size.width()
- else:
- colWidth[col] = max([colWidth[col], size.width()])
-
- def hasHeightForWidth(self):
- """Return true: QwtDynGridLayout implements heightForWidth()."""
- return True
-
- def heightForWidth(self, width):
- """Return The preferred height for this layout, given a width."""
- if self.isEmpty():
- return 0
- numColumns = self.columnsForWidth(width)
- numRows = int(self.itemCount() / numColumns)
- if self.itemCount() % numColumns:
- numRows += 1
- rowHeight = [0] * numRows
- colWidth = [0] * numColumns
- self.layoutGrid(numColumns, rowHeight, colWidth)
- margins = self.contentsMargins()
- margin_h = margins.top() + margins.bottom()
- return margin_h + (numRows - 1) * self.spacing() + sum(rowHeight)
-
- def stretchGrid(self, rect, numColumns, rowHeight, colWidth):
- """
- Stretch columns in case of expanding() & QSizePolicy::Horizontal and
- rows in case of expanding() & QSizePolicy::Vertical to fill the entire
- rect. Rows and columns are stretched with the same factor.
- """
- if numColumns == 0 or self.isEmpty():
- return
- expandH = self.expandingDirections() & Qt.Horizontal
- expandV = self.expandingDirections() & Qt.Vertical
- margins = self.contentsMargins()
- wmargins = margins.left() + margins.right()
- hmargins = margins.top() + margins.bottom()
- if expandH:
- xDelta = rect.width() - wmargins - (numColumns - 1) * self.spacing()
- for col in range(numColumns):
- xDelta -= colWidth[col]
- if xDelta > 0:
- for col in range(numColumns):
- space = xDelta // (numColumns - col)
- colWidth[col] += space
- xDelta -= space
- if expandV:
- numRows = self.itemCount() / numColumns
- if self.itemCount() % numColumns:
- numRows += 1
- yDelta = rect.height() - hmargins - (numRows - 1) * self.spacing()
- for row in range(numRows):
- yDelta -= rowHeight[row]
- if yDelta > 0:
- for row in range(numRows):
- space = yDelta // (numRows - row)
- rowHeight[row] += space
- yDelta -= space
-
- def sizeHint(self):
- """
- Return the size hint. If maxColumns() > 0 it is the size for
- a grid with maxColumns() columns, otherwise it is the size for
- a grid with only one row.
- """
- if self.isEmpty():
- return QSize()
- numColumns = self.itemCount()
- if self.__data.maxColumns > 0:
- numColumns = min([self.__data.maxColumns, numColumns])
- numRows = int(self.itemCount() / numColumns)
- if self.itemCount() % numColumns:
- numRows += 1
- rowHeight = [0] * numRows
- colWidth = [0] * numColumns
- self.layoutGrid(numColumns, rowHeight, colWidth)
- margins = self.contentsMargins()
- margin_h = margins.top() + margins.bottom()
- margin_w = margins.left() + margins.right()
- h = margin_h + (numRows - 1) * self.spacing() + sum(rowHeight)
- w = margin_w + (numColumns - 1) * self.spacing() + sum(colWidth)
- return QSize(w, h)
-
- def numRows(self):
- """Return Number of rows of the current layout."""
- return self.__data.numRows
-
- def numColumns(self):
- """Return Number of columns of the current layout."""
- return self.__data.numColumns
diff --git a/qwt/graphic.py b/qwt/graphic.py
deleted file mode 100644
index 9e18a4b..0000000
--- a/qwt/graphic.py
+++ /dev/null
@@ -1,781 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the Qwt License
-# Copyright (c) 2002 Uwe Rathmann, for the original C++ code
-# Copyright (c) 2015 Pierre Raybaut, for the Python translation/optimization
-# (see LICENSE file for more details)
-
-"""
-QwtGraphic
-----------
-
-.. autoclass:: QwtGraphic
- :members:
-"""
-
-import math
-
-from qtpy.QtCore import QObject, QPointF, QRect, QRectF, QSize, QSizeF, Qt
-from qtpy.QtGui import (
- QImage,
- QPaintEngine,
- QPainter,
- QPainterPathStroker,
- QPixmap,
- QTransform,
-)
-
-from qwt.null_paintdevice import QwtNullPaintDevice
-from qwt.painter_command import QwtPainterCommand, _flag_int
-
-# See painter_command.py for the rationale: cache the QPaintEngine.DirtyXxx
-# flags as plain ints so the State-replay branch below does plain int bitwise
-# tests instead of going through Python's enum.Flag.__and__ on PyQt6.
-_DIRTY_PEN = _flag_int(QPaintEngine.DirtyPen)
-_DIRTY_BRUSH = _flag_int(QPaintEngine.DirtyBrush)
-_DIRTY_BRUSH_ORIGIN = _flag_int(QPaintEngine.DirtyBrushOrigin)
-_DIRTY_FONT = _flag_int(QPaintEngine.DirtyFont)
-_DIRTY_BACKGROUND = _flag_int(QPaintEngine.DirtyBackground)
-_DIRTY_TRANSFORM = _flag_int(QPaintEngine.DirtyTransform)
-_DIRTY_CLIP_ENABLED = _flag_int(QPaintEngine.DirtyClipEnabled)
-_DIRTY_CLIP_REGION = _flag_int(QPaintEngine.DirtyClipRegion)
-_DIRTY_CLIP_PATH = _flag_int(QPaintEngine.DirtyClipPath)
-_DIRTY_HINTS = _flag_int(QPaintEngine.DirtyHints)
-_DIRTY_COMPOSITION_MODE = _flag_int(QPaintEngine.DirtyCompositionMode)
-_DIRTY_OPACITY = _flag_int(QPaintEngine.DirtyOpacity)
-
-
-def qwtHasScalablePen(painter):
- pen = painter.pen()
- scalablePen = False
- if pen.style() != Qt.NoPen and pen.brush().style() != Qt.NoBrush:
- scalablePen = not pen.isCosmetic()
- return scalablePen
-
-
-def qwtStrokedPathRect(painter, path):
- stroker = QPainterPathStroker()
- stroker.setWidth(painter.pen().widthF())
- stroker.setCapStyle(painter.pen().capStyle())
- stroker.setJoinStyle(painter.pen().joinStyle())
- stroker.setMiterLimit(painter.pen().miterLimit())
- rect = QRectF()
- if qwtHasScalablePen(painter):
- stroke = stroker.createStroke(path)
- rect = painter.transform().map(stroke).boundingRect()
- else:
- mappedPath = painter.transform().map(path)
- mappedPath = stroker.createStroke(mappedPath)
- rect = mappedPath.boundingRect()
- return rect
-
-
-def qwtExecCommand(painter, cmd, renderHints, transform, initialTransform):
- if cmd.type() == QwtPainterCommand.Path:
- doMap = False
- if (
- bool(renderHints & QwtGraphic.RenderPensUnscaled)
- and painter.transform().isScaling()
- ):
- isCosmetic = painter.pen().isCosmetic()
- doMap = not isCosmetic
- if doMap:
- tr = painter.transform()
- painter.resetTransform()
- path = tr.map(cmd.path())
- if initialTransform:
- painter.setTransform(initialTransform)
- invt, _ok = initialTransform.inverted()
- path = invt.map(path)
- painter.drawPath(path)
- painter.setTransform(tr)
- else:
- painter.drawPath(cmd.path())
- elif cmd.type() == QwtPainterCommand.Pixmap:
- data = cmd.pixmapData()
- painter.drawPixmap(data.rect, data.pixmap, data.subRect)
- elif cmd.type() == QwtPainterCommand.Image:
- data = cmd.imageData()
- painter.drawImage(data.rect, data.image, data.subRect, data.flags)
- elif cmd.type() == QwtPainterCommand.State:
- data = cmd.stateData()
- flags = _flag_int(data.flags)
- if flags & _DIRTY_PEN:
- painter.setPen(data.pen)
- if flags & _DIRTY_BRUSH:
- painter.setBrush(data.brush)
- if flags & _DIRTY_BRUSH_ORIGIN:
- painter.setBrushOrigin(data.brushOrigin)
- if flags & _DIRTY_FONT:
- painter.setFont(data.font)
- if flags & _DIRTY_BACKGROUND:
- painter.setBackgroundMode(data.backgroundMode)
- painter.setBackground(data.backgroundBrush)
- if flags & _DIRTY_TRANSFORM:
- painter.setTransform(data.transform)
- if flags & _DIRTY_CLIP_ENABLED:
- painter.setClipping(data.isClipEnabled)
- if flags & _DIRTY_CLIP_REGION:
- painter.setClipRegion(data.clipRegion, data.clipOperation)
- if flags & _DIRTY_CLIP_PATH:
- painter.setClipPath(data.clipPath, data.clipOperation)
- if flags & _DIRTY_HINTS:
- for hint in (
- QPainter.Antialiasing,
- QPainter.TextAntialiasing,
- QPainter.SmoothPixmapTransform,
- ):
- painter.setRenderHint(hint, bool(data.renderHints & hint))
- if flags & _DIRTY_COMPOSITION_MODE:
- painter.setCompositionMode(data.compositionMode)
- if flags & _DIRTY_OPACITY:
- painter.setOpacity(data.opacity)
-
-
-class PathInfo(object):
- def __init__(self, *args):
- if len(args) == 0:
- self.__scalablePen = False
- elif len(args) == 3:
- pointRect, boundingRect, scalablePen = args
- self.__pointRect = pointRect
- self.__boundingRect = boundingRect
- self.__scalablePen = scalablePen
- else:
- raise TypeError(
- "%s() takes 0 or 3 argument(s) (%s given)"
- % (self.__class__.__name__, len(args))
- )
-
- def scaledBoundingRect(self, sx, sy, scalePens):
- if sx == 1.0 and sy == 1.0:
- return self.__boundingRect
- transform = QTransform()
- transform.scale(sx, sy)
- if scalePens and self.__scalablePen:
- rect = transform.mapRect(self.__boundingRect)
- else:
- rect = transform.mapRect(self.__pointRect)
- left_diff = abs(self.__pointRect.left() - self.__boundingRect.left())
- right_diff = abs(self.__pointRect.right() - self.__boundingRect.right())
- top_diff = abs(self.__pointRect.top() - self.__boundingRect.top())
- bottom_diff = abs(self.__pointRect.bottom() - self.__boundingRect.bottom())
- rect.adjust(-left_diff, -top_diff, right_diff, bottom_diff)
- return rect
-
- def scaleFactorX(self, pathRect, targetRect, scalePens):
- if pathRect.width() <= 0.0:
- return 0.0
- p0 = self.__pointRect.center()
- left_diff = abs(pathRect.left() - p0.x())
- r = abs(pathRect.right() - p0.x())
- w = 2.0 * min([left_diff, r]) * targetRect.width() / pathRect.width()
- if scalePens and self.__scalablePen:
- sx = w / self.__boundingRect.width()
- else:
- pw = max(
- [
- abs(self.__boundingRect.left() - self.__pointRect.left()),
- abs(self.__boundingRect.right() - self.__pointRect.right()),
- ]
- )
- sx = (w - 2 * pw) / self.__pointRect.width()
- return sx
-
- def scaleFactorY(self, pathRect, targetRect, scalePens):
- if pathRect.height() <= 0.0:
- return 0.0
- p0 = self.__pointRect.center()
- t = abs(pathRect.top() - p0.y())
- b = abs(pathRect.bottom() - p0.y())
- h = 2.0 * min([t, b]) * targetRect.height() / pathRect.height()
- if scalePens and self.__scalablePen:
- sy = h / self.__boundingRect.height()
- else:
- pw = max(
- [
- abs(self.__boundingRect.top() - self.__pointRect.top()),
- abs(self.__boundingRect.bottom() - self.__pointRect.bottom()),
- ]
- )
- sy = (h - 2 * pw) / self.__pointRect.height()
- return sy
-
-
-class QwtGraphic_PrivateData(QObject):
- def __init__(self):
- QObject.__init__(self)
- self.boundingRect = QRectF(0.0, 0.0, -1.0, -1.0)
- self.pointRect = QRectF(0.0, 0.0, -1.0, -1.0)
- self.initialTransform = None
- self.defaultSize = QSizeF()
- self.commands = []
- self.pathInfos = []
- self.renderHints = 0
-
-
-class QwtGraphic(QwtNullPaintDevice):
- """
- A paint device for scalable graphics
-
- `QwtGraphic` is the representation of a graphic that is tailored for
- scalability. Like `QPicture` it will be initialized by `QPainter`
- operations and can be replayed later to any target paint device.
-
- While the usual image representations `QImage` and `QPixmap` are not
- scalable `Qt` offers two paint devices, that might be candidates
- for representing a vector graphic:
-
- - `QPicture`:
-
- Unfortunately `QPicture` had been forgotten, when Qt4
- introduced floating point based render engines. Its API
- is still on integers, what make it unusable for proper scaling.
-
- - `QSvgRenderer`, `QSvgGenerator`:
-
- Unfortunately `QSvgRenderer` hides to much information about
- its nodes in internal APIs, that are necessary for proper
- layout calculations. Also it is derived from `QObject` and
- can't be copied like `QImage`/`QPixmap`.
-
- `QwtGraphic` maps all scalable drawing primitives to a `QPainterPath`
- and stores them together with the painter state changes
- ( pen, brush, transformation ... ) in a list of `QwtPaintCommands`.
- For being a complete `QPaintDevice` it also stores pixmaps or images,
- what is somehow against the idea of the class, because these objects
- can't be scaled without a loss in quality.
-
- The main issue about scaling a `QwtGraphic` object are the pens used for
- drawing the outlines of the painter paths. While non cosmetic pens
- ( `QPen.isCosmetic()` ) are scaled with the same ratio as the path,
- cosmetic pens have a fixed width. A graphic might have paths with
- different pens - cosmetic and non-cosmetic.
-
- `QwtGraphic` caches 2 different rectangles:
-
- - control point rectangle:
-
- The control point rectangle is the bounding rectangle of all
- control point rectangles of the painter paths, or the target
- rectangle of the pixmaps/images.
-
- - bounding rectangle:
-
- The bounding rectangle extends the control point rectangle by
- what is needed for rendering the outline with an unscaled pen.
-
- Because the offset for drawing the outline depends on the shape
- of the painter path ( the peak of a triangle is different than the flat side )
- scaling with a fixed aspect ratio always needs to be calculated from the
- control point rectangle.
-
- .. py:class:: QwtGraphic()
-
- Initializes a null graphic
-
- .. py:class:: QwtGraphic(other)
- :noindex:
-
- Copy constructor
-
- :param qwt.graphic.QwtGraphic other: Source
- """
-
- # enum RenderHint
- RenderPensUnscaled = 0x1
-
- def __init__(self, *args):
- QwtNullPaintDevice.__init__(self)
- if len(args) == 0:
- self.setMode(QwtNullPaintDevice.PathMode)
- self.__data = QwtGraphic_PrivateData()
- elif len(args) == 1:
- (other,) = args
- self.setMode(other.mode())
- self.__data = other.__data
- else:
- raise TypeError(
- "%s() takes 0 or 1 argument(s) (%s given)"
- % (self.__class__.__name__, len(args))
- )
-
- def reset(self):
- """Clear all stored commands"""
- self.__data.commands = []
- self.__data.pathInfos = []
- self.__data.boundingRect = QRectF(0.0, 0.0, -1.0, -1.0)
- self.__data.pointRect = QRectF(0.0, 0.0, -1.0, -1.0)
- self.__data.defaultSize = QSizeF()
-
- def isNull(self):
- """Return True, when no painter commands have been stored"""
- return len(self.__data.commands) == 0
-
- def isEmpty(self):
- """Return True, when the bounding rectangle is empty"""
- return self.__data.boundingRect.isEmpty()
-
- def setRenderHint(self, hint, on=True):
- """Toggle an render hint"""
- if on:
- self.__data.renderHints |= hint
- else:
- self.__data.renderHints &= ~hint
-
- def testRenderHint(self, hint):
- """Test a render hint"""
- return bool(self.__data.renderHints & hint)
-
- def boundingRect(self):
- """
- The bounding rectangle is the :py:meth:`controlPointRect`
- extended by the areas needed for rendering the outlines
- with unscaled pens.
-
- :return: Bounding rectangle of the graphic
-
- .. seealso::
-
- :py:meth:`controlPointRect`, :py:meth:`scaledBoundingRect`
- """
- if self.__data.boundingRect.width() < 0:
- return QRectF()
- return self.__data.boundingRect
-
- def controlPointRect(self):
- """
- The control point rectangle is the bounding rectangle
- of all control points of the paths and the target
- rectangles of the images/pixmaps.
-
- :return: Control point rectangle
-
- .. seealso::
-
- :py:meth:`boundingRect()`, :py:meth:`scaledBoundingRect()`
- """
- if self.__data.pointRect.width() < 0:
- return QRectF()
- return self.__data.pointRect
-
- def scaledBoundingRect(self, sx, sy):
- """
- Calculate the target rectangle for scaling the graphic
-
- :param float sx: Horizontal scaling factor
- :param float sy: Vertical scaling factor
- :return: Scaled bounding rectangle
-
- .. note::
-
- In case of paths that are painted with a cosmetic pen
- (see :py:meth:`QPen.isCosmetic()`) the target rectangle is
- different to multiplying the bounding rectangle.
-
- .. seealso::
-
- :py:meth:`boundingRect()`, :py:meth:`controlPointRect()`
- """
- if sx == 1.0 and sy == 1.0:
- return self.__data.boundingRect
- transform = QTransform()
- transform.scale(sx, sy)
- rect = transform.mapRect(self.__data.pointRect)
- for pathInfo in self.__data.pathInfos:
- rect |= pathInfo.scaledBoundingRect(
- sx, sy, not bool(self.__data.renderHints & self.RenderPensUnscaled)
- )
- return rect
-
- def sizeMetrics(self):
- """Return Ceiled :py:meth:`defaultSize()`"""
- sz = self.defaultSize()
- return QSize(math.ceil(sz.width()), math.ceil(sz.height()))
-
- def setDefaultSize(self, size):
- """
- The default size is used in all methods rendering the graphic,
- where no size is explicitly specified. Assigning an empty size
- means, that the default size will be calculated from the bounding
- rectangle.
-
- :param QSizeF size: Default size
-
- .. seealso::
-
- :py:meth:`defaultSize()`, :py:meth:`boundingRect()`
- """
- w = max([0.0, size.width()])
- h = max([0.0, size.height()])
- self.__data.defaultSize = QSizeF(w, h)
-
- def defaultSize(self):
- """
- When a non empty size has been assigned by setDefaultSize() this
- size will be returned. Otherwise the default size is the size
- of the bounding rectangle.
-
- The default size is used in all methods rendering the graphic,
- where no size is explicitly specified.
-
- :return: Default size
-
- .. seealso::
-
- :py:meth:`setDefaultSize()`, :py:meth:`boundingRect()`
- """
- if not self.__data.defaultSize.isEmpty():
- return self.__data.defaultSize
- return self.boundingRect().size()
-
- def render(self, *args):
- """
- .. py:method:: render(painter)
- :noindex:
-
- Replay all recorded painter commands
-
- :param QPainter painter: Qt painter
-
- .. py:method:: render(painter, size, aspectRatioMode)
- :noindex:
-
- Replay all recorded painter commands
-
- The graphic is scaled to fit into the rectangle
- of the given size starting at ( 0, 0 ).
-
- :param QPainter painter: Qt painter
- :param QSizeF size: Size for the scaled graphic
- :param Qt.AspectRatioMode aspectRatioMode: Mode how to scale
-
- .. py:method:: render(painter, rect, aspectRatioMode)
- :noindex:
-
- Replay all recorded painter commands
-
- The graphic is scaled to fit into the given rectangle
-
- :param QPainter painter: Qt painter
- :param QRectF rect: Rectangle for the scaled graphic
- :param Qt.AspectRatioMode aspectRatioMode: Mode how to scale
-
- .. py:method:: render(painter, pos, aspectRatioMode)
- :noindex:
-
- Replay all recorded painter commands
-
- The graphic is scaled to the :py:meth:`defaultSize()` and aligned
- to a position.
-
- :param QPainter painter: Qt painter
- :param QPointF pos: Reference point, where to render
- :param Qt.AspectRatioMode aspectRatioMode: Mode how to scale
- """
- if len(args) == 1:
- (painter,) = args
- if self.isNull():
- return
- transform = painter.transform()
- painter.save()
- for command in self.__data.commands:
- qwtExecCommand(
- painter,
- command,
- self.__data.renderHints,
- transform,
- self.__data.initialTransform,
- )
- painter.restore()
- elif len(args) in (2, 3) and isinstance(args[1], QSizeF):
- painter, size = args[:2]
- aspectRatioMode = Qt.IgnoreAspectRatio
- if len(args) == 3:
- aspectRatioMode = args[-1]
- r = QRectF(0.0, 0.0, size.width(), size.height())
- self.render(painter, r, aspectRatioMode)
- elif len(args) in (2, 3) and isinstance(args[1], QRectF):
- painter, rect = args[:2]
- aspectRatioMode = Qt.IgnoreAspectRatio
- if len(args) == 3:
- aspectRatioMode = args[-1]
- if self.isEmpty() or rect.isEmpty():
- return
- sx = 1.0
- sy = 1.0
- if self.__data.pointRect.width() > 0.0:
- sx = rect.width() / self.__data.pointRect.width()
- if self.__data.pointRect.height() > 0.0:
- sy = rect.height() / self.__data.pointRect.height()
- scalePens = not bool(self.__data.renderHints & self.RenderPensUnscaled)
- for info in self.__data.pathInfos:
- ssx = info.scaleFactorX(self.__data.pointRect, rect, scalePens)
- if ssx > 0.0:
- sx = min([sx, ssx])
- ssy = info.scaleFactorY(self.__data.pointRect, rect, scalePens)
- if ssy > 0.0:
- sy = min([sy, ssy])
- if aspectRatioMode == Qt.KeepAspectRatio:
- s = min([sx, sy])
- sx = s
- sy = s
- elif aspectRatioMode == Qt.KeepAspectRatioByExpanding:
- s = max([sx, sy])
- sx = s
- sy = s
- tr = QTransform()
- tr.translate(
- rect.center().x() - 0.5 * sx * self.__data.pointRect.width(),
- rect.center().y() - 0.5 * sy * self.__data.pointRect.height(),
- )
- tr.scale(sx, sy)
- tr.translate(-self.__data.pointRect.x(), -self.__data.pointRect.y())
- transform = painter.transform()
- if not scalePens and transform.isScaling():
- # we don't want to scale pens according to sx/sy,
- # but we want to apply the scaling from the
- # painter transformation later
- self.__data.initialTransform = QTransform()
- self.__data.initialTransform.scale(transform.m11(), transform.m22())
- painter.setTransform(tr, True)
- self.render(painter)
- painter.setTransform(transform)
- self.__data.initialTransform = None
- elif len(args) in (2, 3) and isinstance(args[1], QPointF):
- painter, pos = args[:2]
- alignment = Qt.AlignTop | Qt.AlignLeft
- if len(args) == 3:
- alignment = args[-1]
- r = QRectF(pos, self.defaultSize())
- if alignment & Qt.AlignLeft:
- r.moveLeft(pos.x())
- elif alignment & Qt.AlignHCenter:
- r.moveCenter(QPointF(pos.x(), r.center().y()))
- elif alignment & Qt.AlignRight:
- r.moveRight(pos.x())
- if alignment & Qt.AlignTop:
- r.moveTop(pos.y())
- elif alignment & Qt.AlignVCenter:
- r.moveCenter(QPointF(r.center().x(), pos.y()))
- elif alignment & Qt.AlignBottom:
- r.moveBottom(pos.y())
- self.render(painter, r)
- else:
- raise TypeError(
- "%s().render() takes 1, 2 or 3 argument(s) (%s "
- "given)" % (self.__class__.__name__, len(args))
- )
-
- def toPixmap(self, *args):
- """
- Convert the graphic to a `QPixmap`
-
- All pixels of the pixmap get initialized by `Qt.transparent`
- before the graphic is scaled and rendered on it.
-
- The size of the pixmap is the default size ( ceiled to integers )
- of the graphic.
-
- :return: The graphic as pixmap in default size
-
- .. seealso::
-
- :py:meth:`defaultSize()`, :py:meth:`toImage()`, :py:meth:`render()`
- """
- if len(args) == 0:
- if self.isNull():
- return QPixmap()
- sz = self.defaultSize()
- w = math.ceil(sz.width())
- h = math.ceil(sz.height())
- pixmap = QPixmap(w, h)
- pixmap.fill(Qt.transparent)
- r = QRectF(0.0, 0.0, sz.width(), sz.height())
- painter = QPainter(pixmap)
- self.render(painter, r, Qt.KeepAspectRatio)
- painter.end()
- return pixmap
- elif len(args) in (1, 2):
- size = args[0]
- aspectRatioMode = Qt.IgnoreAspectRatio
- if len(args) == 2:
- aspectRatioMode = args[-1]
- pixmap = QPixmap(size)
- pixmap.fill(Qt.transparent)
- r = QRect(0, 0, size.width(), size.height())
- painter = QPainter(pixmap)
- self.render(painter, r, aspectRatioMode)
- painter.end()
- return pixmap
-
- def toImage(self, *args):
- """
- .. py:method:: toImage()
- :noindex:
-
- Convert the graphic to a `QImage`
-
- All pixels of the image get initialized by 0 ( transparent )
- before the graphic is scaled and rendered on it.
-
- The format of the image is `QImage.Format_ARGB32_Premultiplied`.
-
- The size of the image is the default size ( ceiled to integers )
- of the graphic.
-
- :return: The graphic as image in default size
-
- .. py:method:: toImage(size, [aspectRatioMode=Qt.IgnoreAspectRatio])
- :noindex:
-
- Convert the graphic to a `QImage`
-
- All pixels of the image get initialized by 0 ( transparent )
- before the graphic is scaled and rendered on it.
-
- The format of the image is `QImage.Format_ARGB32_Premultiplied`.
-
- :param QSize size: Size of the image
- :param `Qt.AspectRatioMode` aspectRatioMode: Aspect ratio how to scale the graphic
- :return: The graphic as image
-
- .. seealso::
-
- :py:meth:`toPixmap()`, :py:meth:`render()`
- """
- if len(args) == 0:
- if self.isNull():
- return QImage()
- sz = self.defaultSize()
- w = math.ceil(sz.width())
- h = math.ceil(sz.height())
- image = QImage(w, h, QImage.Format_ARGB32)
- image.fill(0)
- r = QRect(0, 0, sz.width(), sz.height())
- painter = QPainter(image)
- self.render(painter, r, Qt.KeepAspectRatio)
- painter.end()
- return image
- elif len(args) in (1, 2):
- size = args[0]
- aspectRatioMode = Qt.IgnoreAspectRatio
- if len(args) == 2:
- aspectRatioMode = args[-1]
- image = QImage(size, QImage.Format_ARGB32_Premultiplied)
- image.fill(0)
- r = QRect(0, 0, size.width(), size.height())
- painter = QPainter(image)
- self.render(painter, r, aspectRatioMode)
- return image
-
- def drawPath(self, path):
- """
- Store a path command in the command list
-
- :param QPainterPath path: Painter path
-
- .. seealso::
-
- :py:meth:`QPaintEngine.drawPath()`
- """
- painter = self.paintEngine().painter()
- if painter is None:
- return
- self.__data.commands += [QwtPainterCommand(path)]
- if not path.isEmpty():
- scaledPath = painter.transform().map(path)
- pointRect = scaledPath.boundingRect()
- boundingRect = QRectF(pointRect)
- if (
- painter.pen().style() != Qt.NoPen
- and painter.pen().brush().style() != Qt.NoBrush
- ):
- boundingRect = qwtStrokedPathRect(painter, path)
- self.updateControlPointRect(pointRect)
- self.updateBoundingRect(boundingRect)
- self.__data.pathInfos += [
- PathInfo(pointRect, boundingRect, qwtHasScalablePen(painter))
- ]
-
- def drawPixmap(self, rect, pixmap, subRect):
- """
- Store a pixmap command in the command list
-
- :param QRectF rect: target rectangle
- :param QPixmap pixmap: Pixmap to be painted
- :param QRectF subRect: Reactangle of the pixmap to be painted
-
- .. seealso::
-
- :py:meth:`QPaintEngine.drawPixmap()`
- """
- painter = self.paintEngine().painter()
- if painter is None:
- return
- self.__data.commands += [QwtPainterCommand(rect, pixmap, subRect)]
- r = painter.transform().mapRect(rect)
- self.updateControlPointRect(r)
- self.updateBoundingRect(r)
-
- def drawImage(self, rect, image, subRect, flags):
- """
- Store a image command in the command list
-
- :param QRectF rect: target rectangle
- :param QImage image: Pixmap to be painted
- :param QRectF subRect: Reactangle of the pixmap to be painted
- :param Qt.ImageConversionFlags flags: Pixmap to be painted
-
- .. seealso::
-
- :py:meth:`QPaintEngine.drawImage()`
- """
- painter = self.paintEngine().painter()
- if painter is None:
- return
- self.__data.commands += [QwtPainterCommand(rect, image, subRect, flags)]
- r = painter.transform().mapRect(rect)
- self.updateControlPointRect(r)
- self.updateBoundingRect(r)
-
- def updateState(self, state):
- """
- Store a state command in the command list
-
- :param QPaintEngineState state: State to be stored
-
- .. seealso::
-
- :py:meth:`QPaintEngine.updateState()`
- """
- # XXX: shall we call the parent's implementation of updateState?
- self.__data.commands += [QwtPainterCommand(state)]
-
- def updateBoundingRect(self, rect):
- br = QRectF(rect)
- painter = self.paintEngine().painter()
- if painter and painter.hasClipping():
- cr = painter.clipRegion().boundingRect()
- cr = painter.transform().mapRect(cr)
- br &= cr
- if self.__data.boundingRect.width() < 0:
- self.__data.boundingRect = br
- else:
- self.__data.boundingRect |= br
-
- def updateControlPointRect(self, rect):
- if self.__data.pointRect.width() < 0.0:
- self.__data.pointRect = rect
- else:
- self.__data.pointRect |= rect
-
- def commands(self):
- return self.__data.commands
-
- def setCommands(self, commands):
- self.reset()
- painter = QPainter(self)
- for cmd in commands:
- qwtExecCommand(painter, cmd, 0, QTransform(), None)
- painter.end()
diff --git a/qwt/interval.py b/qwt/interval.py
deleted file mode 100644
index bd2a5df..0000000
--- a/qwt/interval.py
+++ /dev/null
@@ -1,398 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the Qwt License
-# Copyright (c) 2002 Uwe Rathmann, for the original C++ code
-# Copyright (c) 2015 Pierre Raybaut, for the Python translation/optimization
-# (see LICENSE file for more details)
-
-"""
-QwtInterval
------------
-
-.. autoclass:: QwtInterval
- :members:
-"""
-
-
-class QwtInterval(object):
- """
- A class representing an interval
-
- The interval is represented by 2 doubles, the lower and the upper limit.
-
- .. py:class:: QwtInterval(minValue=0., maxValue=-1., borderFlags=None)
-
- Build an interval with from min/max values
-
- :param float minValue: Minimum value
- :param float maxValue: Maximum value
- :param int borderFlags: Include/Exclude borders
- """
-
- # enum BorderFlag
- IncludeBorders = 0x00
- ExcludeMinimum = 0x01
- ExcludeMaximum = 0x02
- ExcludeBorders = ExcludeMinimum | ExcludeMaximum
-
- def __init__(self, minValue=0.0, maxValue=-1.0, borderFlags=None):
- assert not isinstance(minValue, QwtInterval)
- assert not isinstance(maxValue, QwtInterval)
- self.__minValue = None
- self.__maxValue = None
- self.__borderFlags = None
- self.setInterval(minValue, maxValue, borderFlags)
-
- def setInterval(self, minValue, maxValue, borderFlags=None):
- """
- Assign the limits of the interval
-
- :param float minValue: Minimum value
- :param float maxValue: Maximum value
- :param int borderFlags: Include/Exclude borders
- """
- self.__minValue = float(minValue) # avoid overflows with NumPy scalars
- self.__maxValue = float(maxValue) # avoid overflows with NumPy scalars
- if borderFlags is None:
- self.__borderFlags = self.IncludeBorders
- else:
- self.__borderFlags = borderFlags
-
- def setBorderFlags(self, borderFlags):
- """
- Change the border flags
-
- :param int borderFlags: Include/Exclude borders
-
- .. seealso::
-
- :py:meth:`borderFlags()`
- """
- self.__borderFlags = borderFlags
-
- def borderFlags(self):
- """
- :return: Border flags
-
- .. seealso::
-
- :py:meth:`setBorderFlags()`
- """
- return self.__borderFlags
-
- def setMinValue(self, minValue):
- """
- Assign the lower limit of the interval
-
- :param float minValue: Minimum value
- """
- self.__minValue = float(minValue) # avoid overflows with NumPy scalars
-
- def setMaxValue(self, maxValue):
- """
- Assign the upper limit of the interval
-
- :param float maxValue: Maximum value
- """
- self.__maxValue = float(maxValue) # avoid overflows with NumPy scalars
-
- def minValue(self):
- """
- :return: Lower limit of the interval
- """
- return self.__minValue
-
- def maxValue(self):
- """
- :return: Upper limit of the interval
- """
- return self.__maxValue
-
- def isValid(self):
- """
- A interval is valid when minValue() <= maxValue().
- In case of `QwtInterval.ExcludeBorders` it is true
- when minValue() < maxValue()
-
- :return: True, when the interval is valid
- """
- if (self.__borderFlags & self.ExcludeBorders) == 0:
- return self.__minValue <= self.__maxValue
- else:
- return self.__minValue < self.__maxValue
-
- def width(self):
- """
- The width of invalid intervals is 0.0, otherwise the result is
- maxValue() - minValue().
-
- :return: the width of an interval
- """
- if self.isValid():
- return self.__maxValue - self.__minValue
- else:
- return 0.0
-
- def __and__(self, other):
- return self.intersect(other)
-
- def __iand__(self, other):
- self = self & other
- return self
-
- def __or__(self, other):
- if isinstance(other, QwtInterval):
- return self.unite(other)
- else:
- return self.extend(other)
-
- def __ior__(self, other):
- self = self | other
- return self
-
- def __eq__(self, other):
- return (
- self.__minValue == other.__minValue
- and self.__maxValue == other.__maxValue
- and self.__borderFlags == other.__borderFlags
- )
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
- def isNull(self):
- """
- :return: true, if isValid() && (minValue() >= maxValue())
- """
- return self.isValid() and self.__minValue >= self.__maxValue
-
- def invalidate(self):
- """
- The limits are set to interval [0.0, -1.0]
-
- .. seealso::
-
- :py:meth:`isValid()`
- """
- self.__minValue = 0.0
- self.__maxValue = -1.0
-
- def normalized(self):
- """
- Normalize the limits of the interval
-
- If maxValue() < minValue() the limits will be inverted.
-
- :return: Normalized interval
-
- .. seealso::
-
- :py:meth:`isValid()`, :py:meth:`inverted()`
- """
- if self.__minValue > self.__maxValue:
- return self.inverted()
- elif (
- self.__minValue == self.__maxValue
- and self.__borderFlags == self.ExcludeMinimum
- ):
- return self.inverted()
- else:
- return self
-
- def inverted(self):
- """
- Invert the limits of the interval
-
- :return: Inverted interval
-
- .. seealso::
-
- :py:meth:`normalized()`
- """
- borderFlags = self.IncludeBorders
- if self.__borderFlags & self.ExcludeMinimum:
- borderFlags |= self.ExcludeMaximum
- if self.__borderFlags & self.ExcludeMaximum:
- borderFlags |= self.ExcludeMinimum
- return QwtInterval(self.__maxValue, self.__minValue, borderFlags)
-
- def contains(self, value):
- """
- Test if a value is inside an interval
-
- :param float value: Value
- :return: true, if value >= minValue() && value <= maxValue()
- """
- if not self.isValid():
- return False
- elif value < self.__minValue or value > self.__maxValue:
- return False
- elif value == self.__minValue and self.__borderFlags & self.ExcludeMinimum:
- return False
- elif value == self.__maxValue and self.__borderFlags & self.ExcludeMaximum:
- return False
- else:
- return True
-
- def unite(self, other):
- """
- Unite two intervals
-
- :param qwt.interval.QwtInterval other: other interval to united with
- :return: united interval
- """
- if not self.isValid():
- if not other.isValid():
- return QwtInterval()
- else:
- return other
- elif not other.isValid():
- return self
-
- united = QwtInterval()
- flags = self.IncludeBorders
-
- # minimum
- if self.__minValue < other.minValue():
- united.setMinValue(self.__minValue)
- flags &= self.__borderFlags & self.ExcludeMinimum
- elif other.minValue() < self.__minValue:
- united.setMinValue(other.minValue())
- flags &= other.borderFlags() & self.ExcludeMinimum
- else:
- united.setMinValue(self.__minValue)
- flags &= (self.__borderFlags & other.borderFlags()) & self.ExcludeMinimum
-
- # maximum
- if self.__maxValue > other.maxValue():
- united.setMaxValue(self.__maxValue)
- flags &= self.__borderFlags & self.ExcludeMaximum
- elif other.maxValue() > self.__maxValue:
- united.setMaxValue(other.maxValue())
- flags &= other.borderFlags() & self.ExcludeMaximum
- else:
- united.setMaxValue(self.__maxValue)
- flags &= self.__borderFlags & other.borderFlags() & self.ExcludeMaximum
-
- united.setBorderFlags(flags)
- return united
-
- def intersect(self, other):
- """
- Intersect two intervals
-
- :param qwt.interval.QwtInterval other: other interval to intersect with
- :return: intersected interval
- """
- if not other.isValid() or not self.isValid():
- return QwtInterval()
-
- i1 = self
- i2 = other
-
- if i1.minValue() > i2.minValue():
- i1, i2 = i2, i1
- elif i1.minValue() == i2.minValue():
- if i1.borderFlags() & self.ExcludeMinimum:
- i1, i2 = i2, i1
-
- if i1.maxValue() < i2.maxValue():
- return QwtInterval()
-
- if i1.maxValue() == i2.minValue():
- if (
- i1.borderFlags() & self.ExcludeMaximum
- or i2.borderFlags() & self.ExcludeMinimum
- ):
- return QwtInterval()
-
- intersected = QwtInterval()
- flags = self.IncludeBorders
-
- intersected.setMinValue(i2.minValue())
- flags |= i2.borderFlags() & self.ExcludeMinimum
-
- if i1.maxValue() < i2.maxValue():
- intersected.setMaxValue(i1.maxValue())
- flags |= i1.borderFlags() & self.ExcludeMaximum
- elif i2.maxValue() < i1.maxValue():
- intersected.setMaxValue(i2.maxValue())
- flags |= i2.borderFlags() & self.ExcludeMaximum
- else: # i1.maxValue() == i2.maxValue()
- intersected.setMaxValue(i1.maxValue())
- flags |= i1.borderFlags() & i2.borderFlags() & self.ExcludeMaximum
-
- intersected.setBorderFlags(flags)
- return intersected
-
- def intersects(self, other):
- """
- Test if two intervals overlap
-
- :param qwt.interval.QwtInterval other: other interval
- :return: True, when the intervals are intersecting
- """
- if not other.isValid() or not self.isValid():
- return False
-
- i1 = self
- i2 = other
-
- if i1.minValue() > i2.minValue():
- i1, i2 = i2, i1
- elif i1.minValue() == i2.minValue() and i1.borderFlags() & self.ExcludeMinimum:
- i1, i2 = i2, i1
-
- if i1.maxValue() > i2.minValue():
- return True
- elif i1.maxValue() == i2.minValue():
- return (
- i1.borderFlags() & self.ExcludeMaximum
- and i2.borderFlags() & self.ExcludeMinimum
- )
- return False
-
- def symmetrize(self, value):
- """
- Adjust the limit that is closer to value, so that value becomes
- the center of the interval.
-
- :param float value: Center
- :return: Interval with value as center
- """
- if not self.isValid():
- return self
- delta = max([abs(value - self.__maxValue), abs(value - self.__minValue)])
- return QwtInterval(value - delta, value + delta)
-
- def limited(self, lowerBound, upperBound):
- """
- Limit the interval, keeping the border modes
-
- :param float lowerBound: Lower limit
- :param float upperBound: Upper limit
- :return: Limited interval
- """
- if not self.isValid() or lowerBound > upperBound:
- return QwtInterval()
- minValue = max([self.__minValue, lowerBound])
- minValue = min([minValue, upperBound])
- maxValue = max([self.__maxValue, lowerBound])
- maxValue = min([maxValue, upperBound])
- return QwtInterval(minValue, maxValue, self.__borderFlags)
-
- def extend(self, value):
- """
- Extend the interval
-
- If value is below minValue(), value becomes the lower limit.
- If value is above maxValue(), value becomes the upper limit.
-
- extend() has no effect for invalid intervals
-
- :param float value: Value
- :return: extended interval
- """
- if not self.isValid():
- return self
- return QwtInterval(min([value, self.__minValue]), max([value, self.__maxValue]))
diff --git a/qwt/legend.py b/qwt/legend.py
deleted file mode 100644
index a117370..0000000
--- a/qwt/legend.py
+++ /dev/null
@@ -1,1001 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the Qwt License
-# Copyright (c) 2002 Uwe Rathmann, for the original C++ code
-# Copyright (c) 2015 Pierre Raybaut, for the Python translation/optimization
-# (see LICENSE file for more details)
-
-"""
-QwtLegend
----------
-
-.. autoclass:: QwtLegendData
- :members:
-
-.. autoclass:: QwtLegendLabel
- :members:
-
-.. autoclass:: QwtLegend
- :members:
-"""
-
-import math
-
-from qtpy.QtCore import QEvent, QObject, QPoint, QRect, QRectF, QSize, Qt, Signal
-
-# qDrawWinButton,
-from qtpy.QtGui import QPainter, QPalette, QPixmap
-from qtpy.QtWidgets import (
- QApplication,
- QFrame,
- QScrollArea,
- QStyle,
- QStyleOption,
- QVBoxLayout,
- QWidget,
-)
-
-from qwt.dyngrid_layout import QwtDynGridLayout
-from qwt.painter import QwtPainter
-from qwt.text import QwtText, QwtTextLabel
-
-
-class QwtLegendData(object):
- """
- Attributes of an entry on a legend
-
- `QwtLegendData` is an abstract container ( like `QAbstractModel` )
- to exchange attributes, that are only known between to
- the plot item and the legend.
-
- By overloading `QwtPlotItem.legendData()` any other set of attributes
- could be used, that can be handled by a modified ( or completely
- different ) implementation of a legend.
-
- .. seealso::
-
- :py:class:`qwt.legend.QwtLegend`
-
- .. note::
-
- The stockchart example implements a legend as a tree
- with checkable items
- """
-
- # enum Mode
- ReadOnly, Clickable, Checkable = list(range(3))
-
- # enum Role
- ModeRole, TitleRole, IconRole = list(range(3))
- UserRole = 32
-
- def __init__(self):
- self.__map = {}
-
- def setValues(self, map_):
- """
- Set the legend attributes
-
- :param dict map_: Values
-
- .. seealso::
-
- :py:meth:`values()`
- """
- self.__map = map_
-
- def values(self):
- """
- :return: Legend attributes
-
- .. seealso::
-
- :py:meth:`setValues()`
- """
- return self.__map
-
- def hasRole(self, role):
- """
- :param int role: Attribute role
- :return: True, when the internal map has an entry for role
- """
- return role in self.__map
-
- def setValue(self, role, data):
- """
- Set an attribute value
-
- :param int role: Attribute role
- :param QVariant data: Attribute value
-
- .. seealso::
-
- :py:meth:`value()`
- """
- self.__map[role] = data
-
- def value(self, role):
- """
- :param int role: Attribute role
- :return: Attribute value for a specific role
-
- .. seealso::
-
- :py:meth:`setValue()`
- """
- return self.__map.get(role)
-
- def isValid(self):
- """
- :return: True, when the internal map is empty
- """
- return len(self.__map) != 0
-
- def title(self):
- """
- :return: Value of the TitleRole attribute
- """
- titleValue = self.value(QwtLegendData.TitleRole)
- if isinstance(titleValue, QwtText):
- text = titleValue
- else:
- text = QwtText(titleValue)
- return text
-
- def icon(self):
- """
- :return: Value of the IconRole attribute
- """
- return self.value(QwtLegendData.IconRole)
-
- def mode(self):
- """
- :return: Value of the ModeRole attribute
- """
- modeValue = self.value(QwtLegendData.ModeRole)
- if isinstance(modeValue, int):
- return modeValue
- return QwtLegendData.ReadOnly
-
-
-BUTTONFRAME = 2
-MARGIN = 2
-
-
-def buttonShift(w):
- option = QStyleOption()
- option.initFrom(w)
- ph = w.style().pixelMetric(QStyle.PM_ButtonShiftHorizontal, option, w)
- pv = w.style().pixelMetric(QStyle.PM_ButtonShiftVertical, option, w)
- return QSize(ph, pv)
-
-
-class QwtLegendLabel_PrivateData(QObject):
- def __init__(self):
- QObject.__init__(self)
-
- self.itemMode = QwtLegendData.ReadOnly
- self.isDown = False
- self.spacing = MARGIN
- self.legendData = QwtLegendData()
- self.icon = QPixmap()
-
-
-class QwtLegendLabel(QwtTextLabel):
- """A widget representing something on a QwtLegend."""
-
- clicked = Signal()
- pressed = Signal()
- released = Signal()
- checked = Signal(bool)
-
- def __init__(self, parent=None):
- QwtTextLabel.__init__(self, parent)
- self.__data = QwtLegendLabel_PrivateData()
- self.setMargin(MARGIN)
- self.setIndent(MARGIN)
-
- def setData(self, legendData):
- """
- Set the attributes of the legend label
-
- :param QwtLegendData legendData: Attributes of the label
-
- .. seealso::
-
- :py:meth:`data()`
- """
- self.__data.legendData = legendData
- doUpdate = self.updatesEnabled()
- self.setUpdatesEnabled(False)
- self.setText(legendData.title())
- icon = legendData.icon()
- if icon is not None:
- self.setIcon(icon.toPixmap())
- if legendData.hasRole(QwtLegendData.ModeRole):
- self.setItemMode(legendData.mode())
- if doUpdate:
- self.setUpdatesEnabled(True)
- self.update()
-
- def data(self):
- """
- :return: Attributes of the label
-
- .. seealso::
-
- :py:meth:`setData()`, :py:meth:`qwt.plot.QwtPlotItem.legendData()`
- """
- return self.__data.legendData
-
- def setText(self, text):
- """
- Set the text to the legend item
-
- :param qwt.text.QwtText text: Text label
-
- .. seealso::
-
- :py:meth:`text()`
- """
- flags = Qt.AlignLeft | Qt.AlignVCenter | Qt.TextExpandTabs | Qt.TextWordWrap
- text.setRenderFlags(flags)
- QwtTextLabel.setText(self, text)
-
- def setItemMode(self, mode):
- """
- Set the item mode.
- The default is `QwtLegendData.ReadOnly`.
-
- :param int mode: Item mode
-
- .. seealso::
-
- :py:meth:`itemMode()`
- """
- if mode != self.__data.itemMode:
- self.__data.itemMode = mode
- self.__data.isDown = False
- self.setFocusPolicy(
- Qt.TabFocus if mode != QwtLegendData.ReadOnly else Qt.NoFocus
- )
- self.setMargin(BUTTONFRAME + MARGIN)
- self.updateGeometry()
-
- def itemMode(self):
- """
- :return: Item mode
-
- .. seealso::
-
- :py:meth:`setItemMode()`
- """
- return self.__data.itemMode
-
- def setIcon(self, icon):
- """
- Assign the icon
-
- :param QPixmap icon: Pixmap representing a plot item
-
- .. seealso::
-
- :py:meth:`icon()`, :py:meth:`qwt.plot.QwtPlotItem.legendIcon()`
- """
- self.__data.icon = icon
- indent = self.margin() + self.__data.spacing
- if icon.width() > 0:
- indent += icon.width() + self.__data.spacing
- self.setIndent(indent)
-
- def icon(self):
- """
- :return: Pixmap representing a plot item
-
- .. seealso::
-
- :py:meth:`setIcon()`
- """
- return self.__data.icon
-
- def setSpacing(self, spacing):
- """
- Change the spacing between icon and text
-
- :param int spacing: Spacing
-
- .. seealso::
-
- :py:meth:`spacing()`, :py:meth:`qwt.text.QwtTextLabel.margin()`
- """
- spacing = max([spacing, 0])
- if spacing != self.__data.spacing:
- self.__data.spacing = spacing
- mgn = self.contentsMargins()
- margin = max([mgn.left(), mgn.top(), mgn.right(), mgn.bottom()])
- indent = margin + self.__data.spacing
- if self.__data.icon.width() > 0:
- indent += self.__data.icon.width() + self.__data.spacing
- self.setIndent(indent)
-
- def spacing(self):
- """
- :return: Spacing between icon and text
-
- .. seealso::
-
- :py:meth:`setSpacing()`
- """
- return self.__data.spacing
-
- def setChecked(self, on):
- """
- Check/Uncheck a the item
-
- :param bool on: check/uncheck
-
- .. seealso::
-
- :py:meth:`isChecked()`, :py:meth:`setItemMode()`
- """
- if self.__data.itemMode == QwtLegendData.Checkable:
- isBlocked = self.signalsBlocked()
- self.blockSignals(True)
- self.setDown(on)
- self.blockSignals(isBlocked)
-
- def isChecked(self):
- """
- :return: true, if the item is checked
-
- .. seealso::
-
- :py:meth:`setChecked()`
- """
- return self.__data.itemMode == QwtLegendData.Checkable and self.isDown()
-
- def setDown(self, down):
- """
- Set the item being down
-
- :param bool on: true, if the item is down
-
- .. seealso::
-
- :py:meth:`isDown()`
- """
- if down == self.__data.isDown:
- return
- self.__data.isDown = down
- self.update()
- if self.__data.itemMode == QwtLegendData.Clickable:
- if self.__data.isDown:
- self.pressed.emit()
- else:
- self.released.emit()
- self.clicked.emit()
- if self.__data.itemMode == QwtLegendData.Checkable:
- self.checked.emit(self.__data.isDown)
-
- def isDown(self):
- """
- :return: true, if the item is down
-
- .. seealso::
-
- :py:meth:`setDown()`
- """
- return self.__data.isDown
-
- def sizeHint(self):
- """
- :return: a size hint
- """
- sz = QwtTextLabel.sizeHint(self)
- sz.setHeight(max([sz.height(), self.__data.icon.height() + 4]))
- if self.__data.itemMode != QwtLegendData.ReadOnly:
- sz += buttonShift(self)
- return sz
-
- def paintEvent(self, e):
- cr = self.contentsRect()
- painter = QPainter(self)
- painter.setClipRegion(e.region())
- # if self.__data.isDown:
- # qDrawWinButton(
- # painter, 0, 0, self.width(), self.height(), self.palette(), True
- # )
- painter.save()
- if self.__data.isDown:
- shiftSize = buttonShift(self)
- painter.translate(shiftSize.width(), shiftSize.height())
- painter.setClipRect(cr)
- self.drawContents(painter)
- if not self.__data.icon.isNull():
- iconRect = QRect(cr)
- iconRect.setX(iconRect.x() + self.margin())
- if self.__data.itemMode != QwtLegendData.ReadOnly:
- iconRect.setX(iconRect.x() + BUTTONFRAME)
- iconRect.setSize(self.__data.icon.size())
- iconRect.moveCenter(QPoint(iconRect.center().x(), cr.center().y()))
- painter.drawPixmap(iconRect, self.__data.icon)
- painter.restore()
-
- def mousePressEvent(self, e):
- if e.button() == Qt.LeftButton:
- if self.__data.itemMode == QwtLegendData.Clickable:
- self.setDown(True)
- return
- elif self.__data.itemMode == QwtLegendData.Checkable:
- self.setDown(not self.isDown())
- return
- QwtTextLabel.mousePressEvent(self, e)
-
- def mouseReleaseEvent(self, e):
- if e.button() == Qt.LeftButton:
- if self.__data.itemMode == QwtLegendData.Clickable:
- self.setDown(False)
- return
- elif self.__data.itemMode == QwtLegendData.Checkable:
- return
- QwtTextLabel.mouseReleaseEvent(self, e)
-
- def keyPressEvent(self, e):
- if e.key() == Qt.Key_Space:
- if self.__data.itemMode == QwtLegendData.Clickable:
- if not e.isAutoRepeat():
- self.setDown(True)
- return
- elif self.__data.itemMode == QwtLegendData.Checkable:
- if not e.isAutoRepeat():
- self.setDown(not self.isDown())
- return
- QwtTextLabel.keyPressEvent(self, e)
-
- def keyReleaseEvent(self, e):
- if e.key() == Qt.Key_Space:
- if self.__data.itemMode == QwtLegendData.Clickable:
- if not e.isAutoRepeat():
- self.setDown(False)
- return
- elif self.__data.itemMode == QwtLegendData.Checkable:
- return
- QwtTextLabel.keyReleaseEvent(self, e)
-
-
-class QwtAbstractLegend(QFrame):
- def __init__(self, parent):
- QFrame.__init__(self, parent)
-
- def renderLegend(self, painter, rect, fillBackground):
- raise NotImplementedError
-
- def isEmpty(self):
- return 0
-
- def scrollExtent(self, orientation):
- return 0
-
- def updateLegend(self, itemInfo, data):
- raise NotImplementedError
-
-
-class Entry(object):
- def __init__(self):
- self.itemInfo = None
- self.widgets = []
-
-
-class QwtLegendMap(object):
- def __init__(self):
- self.__entries = []
-
- def isEmpty(self):
- return len(self.__entries) == 0
-
- def insert(self, itemInfo, widgets):
- for entry in self.__entries:
- if entry.itemInfo == itemInfo:
- entry.widgets = widgets
- return
- newEntry = Entry()
- newEntry.itemInfo = itemInfo
- newEntry.widgets = widgets
- self.__entries += [newEntry]
-
- def remove(self, itemInfo):
- for entry in self.__entries[:]:
- if entry.itemInfo == itemInfo:
- self.__entries.remove(entry)
- return
-
- def removeWidget(self, widget):
- for entry in self.__entries:
- while widget in entry.widgets:
- entry.widgets.remove(widget)
-
- def itemInfo(self, widget):
- if widget is not None:
- for entry in self.__entries:
- if widget in entry.widgets:
- return entry.itemInfo
-
- def legendWidgets(self, itemInfo):
- if itemInfo is not None:
- for entry in self.__entries:
- if entry.itemInfo == itemInfo:
- return entry.widgets
- return []
-
-
-class LegendView(QScrollArea):
- def __init__(self, parent):
- QScrollArea.__init__(self, parent)
- self.contentsWidget = QWidget(self)
- self.contentsWidget.setObjectName("QwtLegendViewContents")
- self.setWidget(self.contentsWidget)
- self.setWidgetResizable(False)
- self.viewport().setObjectName("QwtLegendViewport")
- self.contentsWidget.setAutoFillBackground(False)
- self.viewport().setAutoFillBackground(False)
-
- def event(self, event):
- if event.type() == QEvent.PolishRequest:
- self.setFocusPolicy(Qt.NoFocus)
- if event.type() == QEvent.Resize:
- cr = self.contentsRect()
- w = cr.width()
- h = self.contentsWidget.heightForWidth(cr.width())
- if h > w:
- w -= self.verticalScrollBar().sizeHint().width()
- h = self.contentsWidget.heightForWidth(w)
- self.contentsWidget.resize(w, h)
- return QScrollArea.event(self, event)
-
- def viewportEvent(self, event):
- ok = QScrollArea.viewportEvent(self, event)
- if event.type() == QEvent.Resize:
- self.layoutContents()
- return ok
-
- def viewportSize(self, w, h):
- sbHeight = self.horizontalScrollBar().sizeHint().height()
- sbWidth = self.verticalScrollBar().sizeHint().width()
- cw = self.contentsRect().width()
- ch = self.contentsRect().height()
- vw = cw
- vh = ch
- if w > vw:
- vh -= sbHeight
- if h > vh:
- vw -= sbWidth
- if w > vw and vh == ch:
- vh -= sbHeight
- return QSize(vw, vh)
-
- def layoutContents(self):
- layout = self.contentsWidget.layout()
- if layout is None:
- return
- visibleSize = self.viewport().contentsRect().size()
- margins = layout.contentsMargins()
- margin_w = margins.left() + margins.right()
- minW = int(layout.maxItemWidth() + margin_w)
- w = max([visibleSize.width(), minW])
- h = max([layout.heightForWidth(w), visibleSize.height()])
- vpWidth = self.viewportSize(w, h).width()
- if w > vpWidth:
- w = max([vpWidth, minW])
- h = max([layout.heightForWidth(w), visibleSize.height()])
- self.contentsWidget.resize(w, h)
-
-
-class QwtLegend_PrivateData(QObject):
- def __init__(self):
- QObject.__init__(self)
-
- self.itemMode = QwtLegendData.ReadOnly
- self.view = QwtDynGridLayout()
- self.itemMap = QwtLegendMap()
-
-
-class QwtLegend(QwtAbstractLegend):
- """
- The legend widget
-
- The QwtLegend widget is a tabular arrangement of legend items. Legend
- items might be any type of widget, but in general they will be
- a QwtLegendLabel.
-
- .. seealso ::
-
- :py:class`qwt.legend.QwtLegendLabel`,
- :py:class`qwt.plot.QwtPlotItem`,
- :py:class`qwt.plot.QwtPlot`
-
- .. py:class:: QwtLegend([parent=None])
-
- Constructor
-
- :param QWidget parent: Parent widget
-
- .. py:data:: clicked
-
- A signal which is emitted when the user has clicked on
- a legend label, which is in `QwtLegendData.Clickable` mode.
-
- :param itemInfo: Info for the item item of the selected legend item
- :param index: Index of the legend label in the list of widgets that are associated with the plot item
-
- .. note::
-
- Clicks are disabled as default
-
- .. py:data:: checked
-
- A signal which is emitted when the user has clicked on
- a legend label, which is in `QwtLegendData.Checkable` mode
-
- :param itemInfo: Info for the item of the selected legend label
- :param index: Index of the legend label in the list of widgets that are associated with the plot item
- :param on: True when the legend label is checked
-
- .. note::
-
- Clicks are disabled as default
- """
-
- clicked = Signal(object, int)
- checked = Signal(object, bool, int)
-
- def __init__(self, parent=None):
- QwtAbstractLegend.__init__(self, parent)
- self.setFrameStyle(QFrame.NoFrame)
- self.__data = QwtLegend_PrivateData()
- self.__data.view = LegendView(self)
- self.__data.view.setObjectName("QwtLegendView")
- self.__data.view.setFrameStyle(QFrame.NoFrame)
- gridLayout = QwtDynGridLayout(self.__data.view.contentsWidget)
- gridLayout.setAlignment(Qt.AlignHCenter | Qt.AlignTop)
- self.__data.view.gridLayout = gridLayout
- self.__data.view.contentsWidget.installEventFilter(self)
- layout = QVBoxLayout(self)
- layout.setContentsMargins(0, 0, 0, 0)
- layout.addWidget(self.__data.view)
-
- def setMaxColumns(self, numColumns):
- """
- Set the maximum number of entries in a row
-
- F.e when the maximum is set to 1 all items are aligned
- vertically. 0 means unlimited
-
- :param int numColumns: Maximum number of entries in a row
-
- .. seealso::
-
- :py:meth:`maxColumns()`,
- :py:meth:`QwtDynGridLayout.setMaxColumns()`
- """
- tl = self.__data.view.gridLayout
- if tl is not None:
- tl.setMaxColumns(numColumns)
- self.updateGeometry()
-
- def maxColumns(self):
- """
- :return: Maximum number of entries in a row
-
- .. seealso::
-
- :py:meth:`setMaxColumns()`,
- :py:meth:`QwtDynGridLayout.maxColumns()`
- """
- tl = self.__data.view.gridLayout
- if tl is not None:
- return tl.maxColumns()
- return 0
-
- def setDefaultItemMode(self, mode):
- """
- Set the default mode for legend labels
-
- Legend labels will be constructed according to the
- attributes in a `QwtLegendData` object. When it doesn't
- contain a value for the `QwtLegendData.ModeRole` the
- label will be initialized with the default mode of the legend.
-
- :param int mode: Default item mode
-
- .. seealso::
-
- :py:meth:`itemMode()`,
- :py:meth:`QwtLegendData.value()`,
- :py:meth:`QwtPlotItem::legendData()`
-
- ... note::
-
- Changing the mode doesn't have any effect on existing labels.
- """
- self.__data.itemMode = mode
-
- def defaultItemMode(self):
- """
- :return: Default item mode
-
- .. seealso::
-
- :py:meth:`setDefaultItemMode()`
- """
- return self.__data.itemMode
-
- def contentsWidget(self):
- """
- The contents widget is the only child of the viewport of
- the internal `QScrollArea` and the parent widget of all legend
- items.
-
- :return: Container widget of the legend items
- """
- return self.__data.view.contentsWidget
-
- def horizontalScrollBar(self):
- """
- :return: Horizontal scrollbar
-
- .. seealso::
-
- :py:meth:`verticalScrollBar()`
- """
- return self.__data.view.horizontalScrollBar()
-
- def verticalScrollBar(self):
- """
- :return: Vertical scrollbar
-
- .. seealso::
-
- :py:meth:`horizontalScrollBar()`
- """
- return self.__data.view.verticalScrollBar()
-
- def updateLegend(self, itemInfo, data):
- """
- Update the entries for an item
-
- :param QVariant itemInfo: Info for an item
- :param list data: Default item mode
- """
- widgetList = self.legendWidgets(itemInfo)
- if len(widgetList) != len(data):
- contentsLayout = self.__data.view.gridLayout
- while len(widgetList) > len(data):
- w = widgetList.pop(-1)
- contentsLayout.removeWidget(w)
- w.hide()
- w.deleteLater()
- for i in range(len(widgetList), len(data)):
- widget = self.createWidget(data[i])
- if contentsLayout is not None:
- contentsLayout.addWidget(widget)
- if self.isVisible():
- widget.setVisible(True)
- widgetList.append(widget)
- if not widgetList:
- self.__data.itemMap.remove(itemInfo)
- else:
- self.__data.itemMap.insert(itemInfo, widgetList)
- self.updateTabOrder()
- for i in range(len(data)):
- self.updateWidget(widgetList[i], data[i])
-
- def createWidget(self, data):
- """
- Create a widget to be inserted into the legend
-
- The default implementation returns a `QwtLegendLabel`.
-
- :param QwtLegendData data: Attributes of the legend entry
- :return: Widget representing data on the legend
-
- ... note::
-
- updateWidget() will called soon after createWidget()
- with the same attributes.
- """
- label = QwtLegendLabel()
- label.setItemMode(self.defaultItemMode())
- label.clicked.connect(lambda: self.itemClicked(label))
- label.checked.connect(lambda state: self.itemChecked(state, label))
- return label
-
- def updateWidget(self, widget, data):
- """
- Update the widget
-
- :param QWidget widget: Usually a QwtLegendLabel
- :param QwtLegendData data: Attributes to be displayed
-
- .. seealso::
-
- :py:meth:`createWidget()`
-
- ... note::
-
- When widget is no QwtLegendLabel updateWidget() does nothing.
- """
- label = widget # TODO: cast to QwtLegendLabel!
- if label is not None:
- label.setData(data)
- if data.value(QwtLegendData.ModeRole) is None:
- label.setItemMode(self.defaultItemMode())
-
- def updateTabOrder(self):
- contentsLayout = self.__data.view.gridLayout
- if contentsLayout is not None:
- w = None
- for i in range(contentsLayout.count()):
- item = contentsLayout.itemAt(i)
- if w is not None and item.widget():
- QWidget.setTabOrder(w, item.widget())
- w = item.widget()
-
- def sizeHint(self):
- """Return a size hint"""
- hint = self.__data.view.contentsWidget.sizeHint()
- hint += QSize(2 * self.frameWidth(), 2 * self.frameWidth())
- return hint
-
- def heightForWidth(self, width):
- """
- :param int width: Width
- :return: The preferred height, for a width.
- """
- width -= 2 * self.frameWidth()
- h = self.__data.view.contentsWidget.heightForWidth(width)
- if h >= 0:
- h += 2 * self.frameWidth()
- return h
-
- def eventFilter(self, object_, event):
- """
- Handle QEvent.ChildRemoved andQEvent.LayoutRequest events
- for the contentsWidget().
-
- :param QObject object: Object to be filtered
- :param QEvent event: Event
- :return: Forwarded to QwtAbstractLegend.eventFilter()
- """
- if object_ is self.__data.view.contentsWidget:
- if event.type() == QEvent.ChildRemoved:
- ce = event # TODO: cast to QChildEvent
- if ce.child().isWidgetType():
- w = ce.child() # TODO: cast to QWidget
- self.__data.itemMap.removeWidget(w)
- elif event.type() == QEvent.LayoutRequest:
- self.__data.view.layoutContents()
- if self.parentWidget() and self.parentWidget().layout() is None:
- QApplication.postEvent(
- self.parentWidget(), QEvent(QEvent.LayoutRequest)
- )
- return QwtAbstractLegend.eventFilter(self, object_, event)
-
- def itemClicked(self, widget):
- # w = self.sender() #TODO: cast to QWidget
- w = widget
- if w is not None:
- itemInfo = self.__data.itemMap.itemInfo(w)
- if itemInfo is not None:
- widgetList = self.__data.itemMap.legendWidgets(itemInfo)
- if w in widgetList:
- index = widgetList.index(w)
- self.clicked.emit(itemInfo, index)
-
- def itemChecked(self, on, widget):
- # w = self.sender() #TODO: cast to QWidget
- w = widget
- if w is not None:
- itemInfo = self.__data.itemMap.itemInfo(w)
- if itemInfo is not None:
- widgetList = self.__data.itemMap.legendWidgets(itemInfo)
- if w in widgetList:
- index = widgetList.index(w)
- self.checked.emit(itemInfo, on, index)
-
- def renderLegend(self, painter, rect, fillBackground):
- """
- Render the legend into a given rectangle.
-
- :param QPainter painter: Painter
- :param QRectF rect: Bounding rectangle
- :param bool fillBackground: When true, fill rect with the widget background
- """
- if self.__data.itemMap.isEmpty():
- return
- if fillBackground:
- if self.autoFillBackground() or self.testAttribute(Qt.WA_StyledBackground):
- QwtPainter.drawBackground(painter, rect, self)
- legendLayout = self.__data.view.contentsWidget.layout()
- if legendLayout is None:
- return
- margins = self.layout().contentsMargins()
- layoutRect = QRect()
- layoutRect.setLeft(math.ceil(rect.left()) + margins.left())
- layoutRect.setTop(math.ceil(rect.top()) + margins.top())
- layoutRect.setRight(math.ceil(rect.right()) - margins.right())
- layoutRect.setBottom(math.ceil(rect.bottom()) - margins.bottom())
- numCols = legendLayout.columnsForWidth(layoutRect.width())
- itemRects = legendLayout.layoutItems(layoutRect, numCols)
- index = 0
- for i in range(legendLayout.count()):
- item = legendLayout.itemAt(i)
- w = item.widget()
- if w is not None:
- painter.save()
- painter.setClipRect(itemRects[index], Qt.IntersectClip)
- self.renderItem(painter, w, itemRects[index], fillBackground)
- index += 1
- painter.restore()
-
- def renderItem(self, painter, widget, rect, fillBackground):
- """
- Render a legend entry into a given rectangle.
-
- :param QPainter painter: Painter
- :param QWidget widget: Widget representing a legend entry
- :param QRectF rect: Bounding rectangle
- :param bool fillBackground: When true, fill rect with the widget background
- """
- if fillBackground:
- if widget.autoFillBackground() or widget.testAttribute(
- Qt.WA_StyledBackground
- ):
- QwtPainter.drawBackground(painter, rect, widget)
- label = widget # TODO: cast to QwtLegendLabel
- if label is not None:
- icon = label.data().icon()
- sz = icon.defaultSize()
- mgn = label.contentsMargins()
- margin = max([mgn.left(), mgn.top(), mgn.right(), mgn.bottom()])
- iconRect = QRectF(
- rect.x() + margin,
- rect.center().y() - 0.5 * sz.height(),
- sz.width(),
- sz.height(),
- )
- icon.render(painter, iconRect, Qt.KeepAspectRatio)
- titleRect = QRectF(rect)
- titleRect.setX(iconRect.right() + 2 * label.spacing())
- painter.setFont(label.font())
- painter.setPen(label.palette().color(QPalette.Text))
- label.drawText(painter, titleRect) # TODO: cast label to QwtLegendLabel
-
- def legendWidgets(self, itemInfo):
- """
- List of widgets associated to a item
-
- :param QVariant itemInfo: Info about an item
- """
- return self.__data.itemMap.legendWidgets(itemInfo)
-
- def legendWidget(self, itemInfo):
- """
- First widget in the list of widgets associated to an item
-
- :param QVariant itemInfo: Info about an item
- """
- list_ = self.__data.itemMap.legendWidgets(itemInfo)
- if list_:
- return list_[0]
-
- def itemInfo(self, widget):
- """
- Find the item that is associated to a widget
-
- :param QWidget widget: Widget on the legend
- :return: Associated item info
- """
- return self.__data.itemMap.itemInfo(widget)
-
- def isEmpty(self):
- return self.__data.itemMap.isEmpty()
diff --git a/qwt/null_paintdevice.py b/qwt/null_paintdevice.py
deleted file mode 100644
index 80d0877..0000000
--- a/qwt/null_paintdevice.py
+++ /dev/null
@@ -1,314 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the Qwt License
-# Copyright (c) 2002 Uwe Rathmann, for the original C++ code
-# Copyright (c) 2015 Pierre Raybaut, for the Python translation/optimization
-# (see LICENSE file for more details)
-
-"""
-QwtNullPaintDevice
-------------------
-
-.. autoclass:: QwtNullPaintDevice
- :members:
-"""
-
-import os
-
-from qtpy.QtCore import QObject
-from qtpy.QtGui import QPaintDevice, QPaintEngine, QPainterPath
-
-QT_API = os.environ["QT_API"]
-
-
-class QwtNullPaintDevice_PrivateData(QObject):
- def __init__(self):
- QObject.__init__(self)
-
- self.mode = QwtNullPaintDevice.NormalMode
-
-
-class QwtNullPaintDevice_PaintEngine(QPaintEngine):
- def __init__(self, paintdevice):
- super(QwtNullPaintDevice_PaintEngine, self).__init__(QPaintEngine.AllFeatures)
- self.__paintdevice = paintdevice
-
- def begin(self, paintdevice):
- self.setActive(True)
- return True
-
- def end(self):
- self.setActive(False)
- return True
-
- def type(self):
- return QPaintEngine.User
-
- def drawRects(self, rects, rectCount=None):
- if rectCount is None:
- rectCount = len(rects)
- device = self.nullDevice()
- if device is None:
- return
- if device.mode() != QwtNullPaintDevice.NormalMode:
- try:
- QPaintEngine.drawRects(self, rects, rectCount)
- except TypeError:
- QPaintEngine.drawRects(self, rects)
- return
- device.drawRects(rects, rectCount)
-
- def drawLines(self, lines, lineCount=None):
- if lineCount is None:
- lineCount = len(lines)
- device = self.nullDevice()
- if device is None:
- return
- if device.mode() != QwtNullPaintDevice.NormalMode and QT_API.startswith("pyqt"):
- try:
- QPaintEngine.drawLines(self, lines, lineCount)
- except TypeError:
- QPaintEngine.drawLines(self, lines)
- return
- device.drawLines(lines, lineCount)
-
- def drawEllipse(self, rect):
- device = self.nullDevice()
- if device is None:
- return
- if device.mode() != QwtNullPaintDevice.NormalMode:
- QPaintEngine.drawEllipse(self, rect)
- return
- device.drawEllipse(rect)
-
- def drawPath(self, path):
- device = self.nullDevice()
- if device is None:
- return
- device.drawPath(path)
-
- def drawPoints(self, points, pointCount=None):
- if pointCount is None:
- pointCount = len(points)
- device = self.nullDevice()
- if device is None:
- return
- if device.mode() != QwtNullPaintDevice.NormalMode:
- try:
- QPaintEngine.drawPoints(self, points, pointCount)
- except TypeError:
- QPaintEngine.drawPoints(self, points)
- return
- device.drawPoints(points, pointCount)
-
- def drawPolygon(self, *args):
- if len(args) == 3:
- points, pointCount, mode = args
- elif len(args) == 2:
- points, mode = args
- pointCount = len(points)
- else:
- raise TypeError("Unexpected arguments")
- device = self.nullDevice()
- if device is None:
- return
- if device.mode() == QwtNullPaintDevice.PathMode:
- path = QPainterPath()
- if pointCount > 0:
- path.moveTo(points[0])
- for i in range(1, pointCount):
- path.lineTo(points[i])
- if mode != QPaintEngine.PolylineMode:
- path.closeSubpath()
- device.drawPath(path)
- return
- device.drawPolygon(points, pointCount, mode)
-
- def drawPixmap(self, rect, pm, subRect):
- device = self.nullDevice()
- if device is None:
- return
- device.drawPixmap(rect, pm, subRect)
-
- def drawTextItem(self, pos, textItem):
- device = self.nullDevice()
- if device is None:
- return
- if device.mode() != QwtNullPaintDevice.NormalMode:
- QPaintEngine.drawTextItem(self, pos, textItem)
- return
- device.drawTextItem(pos, textItem)
-
- def drawTiledPixmap(self, rect, pixmap, subRect):
- device = self.nullDevice()
- if device is None:
- return
- if device.mode() != QwtNullPaintDevice.NormalMode:
- QPaintEngine.drawTiledPixmap(self, rect, pixmap, subRect)
- return
- device.drawTiledPixmap(rect, pixmap, subRect)
-
- def drawImage(self, rect, image, subRect, flags):
- device = self.nullDevice()
- if device is None:
- return
- device.drawImage(rect, image, subRect, flags)
-
- def updateState(self, state):
- device = self.nullDevice()
- if device is None:
- return
- device.updateState(state)
-
- def nullDevice(self):
- if not self.isActive():
- return
- return self.__paintdevice
-
-
-class QwtNullPaintDevice(QPaintDevice):
- """
- A null paint device doing nothing
-
- Sometimes important layout/rendering geometries are not
- available or changeable from the public Qt class interface.
- ( f.e hidden in the style implementation ).
-
- `QwtNullPaintDevice` can be used to manipulate or filter out
- this information by analyzing the stream of paint primitives.
-
- F.e. `QwtNullPaintDevice` is used by `QwtPlotCanvas` to identify
- styled backgrounds with rounded corners.
-
- Modes:
-
- * `NormalMode`:
-
- All vector graphic primitives are painted by
- the corresponding draw methods
-
- * `PolygonPathMode`:
-
- Vector graphic primitives ( beside polygons ) are mapped to a
- `QPainterPath` and are painted by `drawPath`. In `PolygonPathMode`
- mode only a few draw methods are called:
-
- - `drawPath()`
- - `drawPixmap()`
- - `drawImage()`
- - `drawPolygon()`
-
- * `PathMode`:
-
- Vector graphic primitives are mapped to a `QPainterPath`
- and are painted by `drawPath`. In `PathMode` mode
- only a few draw methods are called:
-
- - `drawPath()`
- - `drawPixmap()`
- - `drawImage()`
- """
-
- # enum Mode
- NormalMode, PolygonPathMode, PathMode = list(range(3))
-
- def __init__(self):
- super(QwtNullPaintDevice, self).__init__()
- self.__engine = None
- self.__data = QwtNullPaintDevice_PrivateData()
-
- def setMode(self, mode):
- """
- Set the render mode
-
- :param int mode: New mode
-
- .. seealso::
-
- :py:meth:`mode()`
- """
- self.__data.mode = mode
-
- def mode(self):
- """
- :return: Render mode
-
- .. seealso::
-
- :py:meth:`setMode()`
- """
- return self.__data.mode
-
- def paintEngine(self):
- if self.__engine is None:
- self.__engine = QwtNullPaintDevice_PaintEngine(self)
- return self.__engine
-
- def metric(self, deviceMetric):
- if deviceMetric == QPaintDevice.PdmWidth:
- value = self.sizeMetrics().width()
- elif deviceMetric == QPaintDevice.PdmHeight:
- value = self.sizeMetrics().height()
- elif deviceMetric == QPaintDevice.PdmNumColors:
- value = 0xFFFFFFFF
- elif deviceMetric == QPaintDevice.PdmDepth:
- value = 32
- elif deviceMetric in (
- QPaintDevice.PdmPhysicalDpiX,
- QPaintDevice.PdmPhysicalDpiY,
- QPaintDevice.PdmDpiY,
- QPaintDevice.PdmDpiX,
- ):
- value = 72
- elif deviceMetric == QPaintDevice.PdmWidthMM:
- value = round(
- self.metric(QPaintDevice.PdmWidth)
- * 25.4
- / self.metric(QPaintDevice.PdmDpiX)
- )
- elif deviceMetric == QPaintDevice.PdmHeightMM:
- value = round(
- self.metric(QPaintDevice.PdmHeight)
- * 25.4
- / self.metric(QPaintDevice.PdmDpiY)
- )
- elif deviceMetric == QPaintDevice.PdmDevicePixelRatio:
- value = 1
- elif deviceMetric == QPaintDevice.PdmDevicePixelRatioScaled:
- value = 1
- else:
- value = super(QwtNullPaintDevice, self).metric(deviceMetric)
- return value
-
- def drawRects(self, rects, rectCount):
- pass
-
- def drawLines(self, lines, lineCount):
- pass
-
- def drawEllipse(self, rect):
- pass
-
- def drawPath(self, path):
- pass
-
- def drawPoints(self, points, pointCount):
- pass
-
- def drawPolygon(self, points, pointCount, mode):
- pass
-
- def drawPixmap(self, rect, pm, subRect):
- pass
-
- def drawTextItem(self, pos, textItem):
- pass
-
- def drawTiledPixmap(self, rect, pm, subRect):
- pass
-
- def drawImage(self, rect, image, subRect, flags):
- pass
-
- def updateState(self, state):
- pass
diff --git a/qwt/painter.py b/qwt/painter.py
deleted file mode 100644
index 6179dfc..0000000
--- a/qwt/painter.py
+++ /dev/null
@@ -1,419 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the Qwt License
-# Copyright (c) 2002 Uwe Rathmann, for the original C++ code
-# Copyright (c) 2015 Pierre Raybaut, for the Python translation/optimization
-# (see LICENSE file for more details)
-
-"""
-QwtPainterClass
----------------
-
-.. autoclass:: QwtPainterClass
- :members:
-"""
-
-from qtpy.QtCore import QLineF, QPoint, QRect, Qt
-from qtpy.QtGui import (
- QColor,
- QLinearGradient,
- QPaintEngine,
- QPainter,
- QPainterPath,
- QPalette,
- QPen,
- QPixmap,
- QRegion,
-)
-from qtpy.QtWidgets import (
- QApplication,
- QFrame,
- QStyle,
- QStyleOption,
- QStyleOptionFocusRect,
-)
-
-from qwt.color_map import QwtColorMap
-from qwt.scale_map import QwtScaleMap
-
-QWIDGETSIZE_MAX = (1 << 24) - 1
-
-
-def isX11GraphicsSystem():
- pm = QPixmap(1, 1)
- painter = QPainter(pm)
- isX11 = painter.paintEngine().type() == QPaintEngine.X11
- del painter
- return isX11
-
-
-def qwtFillRect(widget, painter, rect, brush):
- if brush.style() == Qt.TexturePattern:
- painter.save()
- painter.setClipRect(rect)
- painter.drawTiledPixmap(rect, brush.texture(), rect.topLeft())
- painter.restore()
- elif brush.gradient():
- painter.save()
- painter.setClipRect(rect)
- painter.fillRect(0, 0, widget.width(), widget.height(), brush)
- painter.restore()
- else:
- painter.fillRect(rect, brush)
-
-
-class QwtPainterClass(object):
- """A collection of `QPainter` workarounds"""
-
- def drawFocusRect(self, *args):
- if len(args) == 2:
- painter, widget = args
- self.drawFocusRect(painter, widget, widget.rect())
- elif len(args) == 3:
- painter, widget, rect = args
- opt = QStyleOptionFocusRect()
- opt.initFrom(widget)
- opt.rect = rect
- opt.state |= QStyle.State_HasFocus
- palette = widget.palette()
- opt.backgroundColor = palette.color(widget.backgroundRole())
- widget.style().drawPrimitive(QStyle.PE_FrameFocusRect, opt, painter, widget)
- else:
- raise TypeError(
- "QwtPainter.drawFocusRect() takes 2 or 3 argument"
- "(s) (%s given)" % len(args)
- )
-
- def drawFrame(
- self,
- painter,
- rect,
- palette,
- foregroundRole,
- frameWidth,
- midLineWidth,
- frameStyle,
- ):
- """
- Draw a rectangular frame
-
- :param QPainter painter: Painter
- :param QRectF rect: Frame rectangle
- :param QPalette palette: Palette
- :param QPalette.ColorRole foregroundRole: Palette
- :param int frameWidth: Frame width
- :param int midLineWidth: Used for `QFrame.Box`
- :param int frameStyle: bitwise OR´ed value of `QFrame.Shape` and `QFrame.Shadow`
- """
- if frameWidth <= 0 or rect.isEmpty():
- return
- shadow = frameStyle & QFrame.Shadow_Mask
- painter.save()
- if shadow == QFrame.Plain:
- outerRect = rect.adjusted(0.0, 0.0, -1.0, -1.0)
- innerRect = outerRect.adjusted(
- frameWidth, frameWidth, -frameWidth, -frameWidth
- )
- path = QPainterPath()
- path.addRect(outerRect)
- path.addRect(innerRect)
- painter.setPen(Qt.NoPen)
- painter.setBrush(palette.color(foregroundRole))
- painter.drawPath(path)
- else:
- shape = frameStyle & QFrame.Shape_Mask
- if shape == QFrame.Box:
- outerRect = rect.adjusted(0.0, 0.0, -1.0, -1.0)
- midRect1 = outerRect.adjusted(
- frameWidth, frameWidth, -frameWidth, -frameWidth
- )
- midRect2 = midRect1.adjusted(
- midLineWidth, midLineWidth, -midLineWidth, -midLineWidth
- )
- innerRect = midRect2.adjusted(
- frameWidth, frameWidth, -frameWidth, -frameWidth
- )
- path1 = QPainterPath()
- path1.moveTo(outerRect.bottomLeft())
- path1.lineTo(outerRect.topLeft())
- path1.lineTo(outerRect.topRight())
- path1.lineTo(midRect1.topRight())
- path1.lineTo(midRect1.topLeft())
- path1.lineTo(midRect1.bottomLeft())
- path2 = QPainterPath()
- path2.moveTo(outerRect.bottomLeft())
- path2.lineTo(outerRect.bottomRight())
- path2.lineTo(outerRect.topRight())
- path2.lineTo(midRect1.topRight())
- path2.lineTo(midRect1.bottomRight())
- path2.lineTo(midRect1.bottomLeft())
- path3 = QPainterPath()
- path3.moveTo(midRect2.bottomLeft())
- path3.lineTo(midRect2.topLeft())
- path3.lineTo(midRect2.topRight())
- path3.lineTo(innerRect.topRight())
- path3.lineTo(innerRect.topLeft())
- path3.lineTo(innerRect.bottomLeft())
- path4 = QPainterPath()
- path4.moveTo(midRect2.bottomLeft())
- path4.lineTo(midRect2.bottomRight())
- path4.lineTo(midRect2.topRight())
- path4.lineTo(innerRect.topRight())
- path4.lineTo(innerRect.bottomRight())
- path4.lineTo(innerRect.bottomLeft())
- path5 = QPainterPath()
- path5.addRect(midRect1)
- path5.addRect(midRect2)
- painter.setPen(Qt.NoPen)
- brush1 = palette.dark().color()
- brush2 = palette.light().color()
- if shadow == QFrame.Raised:
- brush1, brush2 = brush2, brush1
- painter.setBrush(brush1)
- painter.drawPath(path1)
- painter.drawPath(path4)
- painter.setBrush(brush2)
- painter.drawPath(path2)
- painter.drawPath(path3)
- painter.setBrush(palette.mid())
- painter.drawPath(path5)
- else:
- outerRect = rect.adjusted(0.0, 0.0, -1.0, -1.0)
- innerRect = outerRect.adjusted(
- frameWidth - 1.0,
- frameWidth - 1.0,
- -(frameWidth - 1.0),
- -(frameWidth - 1.0),
- )
- path1 = QPainterPath()
- path1.moveTo(outerRect.bottomLeft())
- path1.lineTo(outerRect.topLeft())
- path1.lineTo(outerRect.topRight())
- path1.lineTo(innerRect.topRight())
- path1.lineTo(innerRect.topLeft())
- path1.lineTo(innerRect.bottomLeft())
- path2 = QPainterPath()
- path2.moveTo(outerRect.bottomLeft())
- path2.lineTo(outerRect.bottomRight())
- path2.lineTo(outerRect.topRight())
- path2.lineTo(innerRect.topRight())
- path2.lineTo(innerRect.bottomRight())
- path2.lineTo(innerRect.bottomLeft())
- painter.setPen(Qt.NoPen)
- brush1 = palette.dark().color()
- brush2 = palette.light().color()
- if shadow == QFrame.Raised:
- brush1, brush2 = brush2, brush1
- painter.setBrush(brush1)
- painter.drawPath(path1)
- painter.setBrush(brush2)
- painter.drawPath(path2)
- painter.restore()
-
- def drawRoundedFrame(
- self, painter, rect, xRadius, yRadius, palette, lineWidth, frameStyle
- ):
- """
- Draw a rectangular frame with rounded borders
-
- :param QPainter painter: Painter
- :param QRectF rect: Frame rectangle
- :param float xRadius: x-radius of the ellipses defining the corners
- :param float yRadius: y-radius of the ellipses defining the corners
- :param QPalette palette: `QPalette.WindowText` is used for plain borders, `QPalette.Dark` and `QPalette.Light` for raised or sunken borders
- :param int lineWidth: Line width
- :param int frameStyle: bitwise OR´ed value of `QFrame.Shape` and `QFrame.Shadow`
- """
- painter.save()
- painter.setRenderHint(QPainter.Antialiasing, True)
- painter.setBrush(Qt.NoBrush)
- lw2 = lineWidth * 0.5
- r = rect.adjusted(lw2, lw2, -lw2, -lw2)
- path = QPainterPath()
- path.addRoundedRect(r, xRadius, yRadius)
- Plain, Sunken, Raised = list(range(3))
- style = Plain
- if (frameStyle & QFrame.Sunken) == QFrame.Sunken:
- style = Sunken
- if (frameStyle & QFrame.Raised) == QFrame.Raised:
- style = Raised
- if style != Plain and path.elementCount() == 17:
- pathList = [QPainterPath() for _i in range(8)]
- for i in range(4):
- j = i * 4 + 1
- pathList[2 * i].moveTo(path.elementAt(j - 1).x, path.elementAt(j - 1).y)
- pathList[2 * i].cubicTo(
- path.elementAt(j + 0).x,
- path.elementAt(j + 0).y,
- path.elementAt(j + 1).x,
- path.elementAt(j + 1).y,
- path.elementAt(j + 2).x,
- path.elementAt(j + 2).y,
- )
- pathList[2 * i + 1].moveTo(
- path.elementAt(j + 2).x, path.elementAt(j + 2).y
- )
- pathList[2 * i + 1].lineTo(
- path.elementAt(j + 3).x, path.elementAt(j + 3).y
- )
- c1 = QColor(palette.color(QPalette.Dark))
- c2 = QColor(palette.color(QPalette.Light))
- if style == Raised:
- c1, c2 = c2, c1
- for i in range(4):
- r = pathList[2 * i].controlPointRect()
- arcPen = QPen()
- arcPen.setCapStyle(Qt.FlatCap)
- arcPen.setWidth(lineWidth)
- linePen = QPen()
- linePen.setCapStyle(Qt.FlatCap)
- linePen.setWidth(lineWidth)
- if i == 0:
- arcPen.setColor(c1)
- linePen.setColor(c1)
- elif i == 1:
- gradient = QLinearGradient()
- gradient.setStart(r.topLeft())
- gradient.setFinalStop(r.bottomRight())
- gradient.setColorAt(0.0, c1)
- gradient.setColorAt(1.0, c2)
- arcPen.setBrush(gradient)
- linePen.setColor(c2)
- elif i == 2:
- arcPen.setColor(c2)
- linePen.setColor(c2)
- elif i == 3:
- gradient = QLinearGradient()
- gradient.setStart(r.bottomRight())
- gradient.setFinalStop(r.topLeft())
- gradient.setColorAt(0.0, c2)
- gradient.setColorAt(1.0, c1)
- arcPen.setBrush(gradient)
- linePen.setColor(c1)
- painter.setPen(arcPen)
- painter.drawPath(pathList[2 * i])
- painter.setPen(linePen)
- painter.drawPath(pathList[2 * i + 1])
- else:
- pen = QPen(palette.color(QPalette.WindowText), lineWidth)
- painter.setPen(pen)
- painter.drawPath(path)
- painter.restore()
-
- def drawColorBar(self, painter, colorMap, interval, scaleMap, orientation, rect):
- """
- Draw a color bar into a rectangle
-
- :param QPainter painter: Painter
- :param qwt.color_map.QwtColorMap colorMap: Color map
- :param qwt.interval.QwtInterval interval: Value range
- :param qwt.scalemap.QwtScaleMap scaleMap: Scale map
- :param Qt.Orientation orientation: Orientation
- :param QRectF rect: Target rectangle
- """
- colorTable = []
- if colorMap.format() == QwtColorMap.Indexed:
- colorTable = colorMap.colorTable(interval)
- c = QColor()
- devRect = rect.toAlignedRect()
- pixmap = QPixmap(devRect.size())
- pixmap.fill(Qt.transparent)
- pmPainter = QPainter(pixmap)
- pmPainter.translate(-devRect.x(), -devRect.y())
- if orientation == Qt.Horizontal:
- sMap = QwtScaleMap(scaleMap)
- sMap.setPaintInterval(rect.left(), rect.right())
- for x in range(devRect.left(), devRect.right() + 1):
- value = sMap.invTransform(x)
- if colorMap.format() == QwtColorMap.RGB:
- c.setRgba(colorMap.rgb(interval, value))
- else:
- c = colorTable[colorMap.colorIndex(interval, value)]
- pmPainter.setPen(c)
- pmPainter.drawLine(QLineF(x, devRect.top(), x, devRect.bottom()))
- else:
- sMap = QwtScaleMap(scaleMap)
- sMap.setPaintInterval(rect.bottom(), rect.top())
- for y in range(devRect.top(), devRect.bottom() + 1):
- value = sMap.invTransform(y)
- if colorMap.format() == QwtColorMap.RGB:
- c.setRgba(colorMap.rgb(interval, value))
- else:
- c = colorTable[colorMap.colorIndex(interval, value)]
- pmPainter.setPen(c)
- pmPainter.drawLine(QLineF(devRect.left(), y, devRect.right(), y))
- pmPainter.end()
- painter.drawPixmap(devRect, pixmap)
-
- def fillPixmap(self, widget, pixmap, offset=None):
- """
- Fill a pixmap with the content of a widget
-
- In Qt >= 5.0 `QPixmap.fill()` is a nop, in Qt 4.x it is buggy
- for backgrounds with gradients. Thus `fillPixmap()` offers
- an alternative implementation.
-
- :param QWidget widget: Widget
- :param QPixmap pixmap: Pixmap to be filled
- :param QPoint offset: Offset
-
- .. seealso::
-
- :py:meth:`QPixmap.fill()`
- """
- if offset is None:
- offset = QPoint()
- rect = QRect(offset, pixmap.size())
- painter = QPainter(pixmap)
- painter.translate(-offset)
- autoFillBrush = widget.palette().brush(widget.backgroundRole())
- if not (widget.autoFillBackground() and autoFillBrush.isOpaque()):
- bg = widget.palette().brush(QPalette.Window)
- qwtFillRect(widget, painter, rect, bg)
- if widget.autoFillBackground():
- qwtFillRect(widget, painter, rect, autoFillBrush)
- if widget.testAttribute(Qt.WA_StyledBackground):
- painter.setClipRegion(QRegion(rect))
- opt = QStyleOption()
- opt.initFrom(widget)
- widget.style().drawPrimitive(QStyle.PE_Widget, opt, painter, widget)
-
- def drawBackground(self, painter, rect, widget):
- """
- Fill rect with the background of a widget
-
- :param QPainter painter: Painter
- :param QRectF rect: Rectangle to be filled
- :param QWidget widget: Widget
-
- .. seealso::
-
- :py:data:`QStyle.PE_Widget`, :py:meth:`QWidget.backgroundRole()`
- """
- if widget.testAttribute(Qt.WA_StyledBackground):
- opt = QStyleOption()
- opt.initFrom(widget)
- opt.rect = rect.toRect()
- widget.style().drawPrimitive(QStyle.PE_Widget, opt, painter, widget)
- else:
- brush = widget.palette().brush(widget.backgroundRole())
- painter.fillRect(rect, brush)
-
- def backingStore(self, widget, size):
- """
- :param QWidget widget: Widget, for which the backinstore is intended
- :param QSize size: Size of the pixmap
- :return: A pixmap that can be used as backing store
- """
- pixelRatio = 1.0
- if widget and widget.windowHandle():
- pixelRatio = widget.windowHandle().devicePixelRatio()
- else:
- qapp = QApplication.instance()
- pixelRatio = qapp.devicePixelRatio()
- pm = QPixmap(size * pixelRatio)
- pm.setDevicePixelRatio(pixelRatio)
- return pm
-
-
-QwtPainter = QwtPainterClass()
diff --git a/qwt/painter_command.py b/qwt/painter_command.py
deleted file mode 100644
index d923be9..0000000
--- a/qwt/painter_command.py
+++ /dev/null
@@ -1,240 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the Qwt License
-# Copyright (c) 2002 Uwe Rathmann, for the original C++ code
-# Copyright (c) 2015 Pierre Raybaut, for the Python translation/optimization
-# (see LICENSE file for more details)
-
-"""
-QwtPainterCommand
------------------
-
-.. autoclass:: QwtPainterCommand
- :members:
-"""
-
-import copy
-
-from qtpy.QtGui import QPaintEngine, QPainterPath
-
-
-def _flag_int(flag):
- """Return the integer value of a Qt enum/flag (PyQt5 and PyQt6).
-
- PyQt5 exposes Qt enums as plain ints (``int(flag)`` works). PyQt6 wraps
- them as ``enum.Flag`` instances which are not ``int`` subclasses, so
- ``int(flag)`` raises -- the value must be read from ``flag.value``.
- """
- try:
- return flag.value
- except AttributeError:
- return int(flag)
-
-
-# Cache QPaintEngine.DirtyXxx flags as plain Python ints once at import time.
-# On PyQt6, Qt enums are full ``enum.Flag`` instances and every ``flags &
-# Member`` test goes through Python's ``enum.__and__`` machinery (~6 us each).
-# In ``QwtPainterCommand.__init__`` below, the State branch performs twelve
-# successive flag tests per painter command -- on PyQt6 alone this accounted
-# for ~20 ms of the residual perf gap on the load test. Casting once to int
-# and bitwise-testing against int constants brings each test back to ~50 ns.
-_DIRTY_PEN = _flag_int(QPaintEngine.DirtyPen)
-_DIRTY_BRUSH = _flag_int(QPaintEngine.DirtyBrush)
-_DIRTY_BRUSH_ORIGIN = _flag_int(QPaintEngine.DirtyBrushOrigin)
-_DIRTY_FONT = _flag_int(QPaintEngine.DirtyFont)
-_DIRTY_BACKGROUND = _flag_int(QPaintEngine.DirtyBackground)
-_DIRTY_TRANSFORM = _flag_int(QPaintEngine.DirtyTransform)
-_DIRTY_CLIP_ENABLED = _flag_int(QPaintEngine.DirtyClipEnabled)
-_DIRTY_CLIP_REGION = _flag_int(QPaintEngine.DirtyClipRegion)
-_DIRTY_CLIP_PATH = _flag_int(QPaintEngine.DirtyClipPath)
-_DIRTY_HINTS = _flag_int(QPaintEngine.DirtyHints)
-_DIRTY_COMPOSITION_MODE = _flag_int(QPaintEngine.DirtyCompositionMode)
-_DIRTY_OPACITY = _flag_int(QPaintEngine.DirtyOpacity)
-
-
-class PixmapData(object):
- def __init__(self):
- self.rect = None
- self.pixmap = None
- self.subRect = None
-
-
-class ImageData(object):
- def __init__(self):
- self.rect = None
- self.image = None
- self.subRect = None
- self.flags = None
-
-
-class StateData(object):
- def __init__(self):
- self.flags = None
- self.pen = None
- self.brush = None
- self.brushOrigin = None
- self.backgroundBrush = None
- self.backgroundMode = None
- self.font = None
- self.matrix = None
- self.transform = None
- self.clipOperation = None
- self.clipRegion = None
- self.clipPath = None
- self.isClipEnabled = None
- self.renderHints = None
- self.compositionMode = None
- self.opacity = None
-
-
-class QwtPainterCommand(object):
- """
- `QwtPainterCommand` represents the attributes of a paint operation
- how it is used between `QPainter` and `QPaintDevice`
-
- It is used by :py:class:`qwt.graphic.QwtGraphic` to record and replay
- paint operations
-
- .. seealso::
-
- :py:meth:`qwt.graphic.QwtGraphic.commands()`
-
-
- .. py:class:: QwtPainterCommand()
-
- Construct an invalid command
-
- .. py:class:: QwtPainterCommand(path)
- :noindex:
-
- Copy constructor
-
- :param QPainterPath path: Source
-
- .. py:class:: QwtPainterCommand(rect, pixmap, subRect)
- :noindex:
-
- Constructor for Pixmap paint operation
-
- :param QRectF rect: Target rectangle
- :param QPixmap pixmap: Pixmap
- :param QRectF subRect: Rectangle inside the pixmap
-
- .. py:class:: QwtPainterCommand(rect, image, subRect, flags)
- :noindex:
-
- Constructor for Image paint operation
-
- :param QRectF rect: Target rectangle
- :param QImage image: Image
- :param QRectF subRect: Rectangle inside the image
- :param Qt.ImageConversionFlags flags: Conversion flags
-
- .. py:class:: QwtPainterCommand(state)
- :noindex:
-
- Constructor for State paint operation
-
- :param QPaintEngineState state: Paint engine state
- """
-
- # enum Type
- Invalid = -1
- Path, Pixmap, Image, State = list(range(4))
-
- def __init__(self, *args):
- if len(args) == 0:
- self.__type = self.Invalid
- elif len(args) == 1:
- (arg,) = args
- if isinstance(arg, QPainterPath):
- path = arg
- self.__type = self.Path
- self.__path = QPainterPath(path)
- elif isinstance(arg, QwtPainterCommand):
- other = arg
- self.copy(other)
- else:
- state = arg
- self.__type = self.State
- self.__stateData = StateData()
- self.__stateData.flags = state.state()
- # Cast to int once: subsequent bitwise tests are done against
- # the cached _DIRTY_* int constants (see top of module).
- flags = _flag_int(self.__stateData.flags)
- if flags & _DIRTY_PEN:
- self.__stateData.pen = state.pen()
- if flags & _DIRTY_BRUSH:
- self.__stateData.brush = state.brush()
- if flags & _DIRTY_BRUSH_ORIGIN:
- self.__stateData.brushOrigin = state.brushOrigin()
- if flags & _DIRTY_FONT:
- self.__stateData.font = state.font()
- if flags & _DIRTY_BACKGROUND:
- self.__stateData.backgroundMode = state.backgroundMode()
- self.__stateData.backgroundBrush = state.backgroundBrush()
- if flags & _DIRTY_TRANSFORM:
- self.__stateData.transform = state.transform()
- if flags & _DIRTY_CLIP_ENABLED:
- self.__stateData.isClipEnabled = state.isClipEnabled()
- if flags & _DIRTY_CLIP_REGION:
- self.__stateData.clipRegion = state.clipRegion()
- self.__stateData.clipOperation = state.clipOperation()
- if flags & _DIRTY_CLIP_PATH:
- self.__stateData.clipPath = state.clipPath()
- self.__stateData.clipOperation = state.clipOperation()
- if flags & _DIRTY_HINTS:
- self.__stateData.renderHints = state.renderHints()
- if flags & _DIRTY_COMPOSITION_MODE:
- self.__stateData.compositionMode = state.compositionMode()
- if flags & _DIRTY_OPACITY:
- self.__stateData.opacity = state.opacity()
- elif len(args) == 3:
- rect, pixmap, subRect = args
- self.__type = self.Pixmap
- self.__pixmapData = PixmapData()
- self.__pixmapData.rect = rect
- self.__pixmapData.pixmap = pixmap
- self.__pixmapData.subRect = subRect
- elif len(args) == 4:
- rect, image, subRect, flags = args
- self.__type = self.Image
- self.__imageData = ImageData()
- self.__imageData.rect = rect
- self.__imageData.image = image
- self.__imageData.subRect = subRect
- self.__imageData.flags = flags
- else:
- raise TypeError(
- "%s() takes 0, 1, 3 or 4 argument(s) (%s given)"
- % (self.__class__.__name__, len(args))
- )
-
- def copy(self, other):
- self.__type = other.__type
- if other.__type == self.Path:
- self.__path = QPainterPath(other.__path)
- elif other.__type == self.Pixmap:
- self.__pixmapData = copy.deepcopy(other.__pixmapData)
- elif other.__type == self.Image:
- self.__imageData = copy.deepcopy(other.__imageData)
- elif other.__type == self.State:
- self.__stateData == copy.deepcopy(other.__stateData)
-
- def reset(self):
- self.__type = self.Invalid
-
- def type(self):
- return self.__type
-
- def path(self):
- return self.__path
-
- def pixmapData(self):
- return self.__pixmapData
-
- def imageData(self):
- return self.__imageData
-
- def stateData(self):
- return self.__stateData
diff --git a/qwt/plot.py b/qwt/plot.py
deleted file mode 100644
index ced78a4..0000000
--- a/qwt/plot.py
+++ /dev/null
@@ -1,2292 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the Qwt License
-# Copyright (c) 2002 Uwe Rathmann, for the original C++ code
-# Copyright (c) 2015 Pierre Raybaut, for the Python translation/optimization
-# (see LICENSE file for more details)
-
-"""
-QwtPlot
--------
-
-.. autoclass:: QwtPlot
- :members:
-
-QwtPlotItem
------------
-
-.. autoclass:: QwtPlotItem
- :members:
-"""
-
-import math
-
-import numpy as np
-from qtpy.QtCore import QEvent, QObject, QRectF, QSize, Qt, Signal
-from qtpy.QtGui import QBrush, QColor, QFont, QPainter, QPalette
-from qtpy.QtWidgets import QApplication, QFrame, QSizePolicy, QWidget
-
-from qwt.graphic import QwtGraphic
-from qwt.interval import QwtInterval
-from qwt.legend import QwtLegendData
-from qwt.plot_canvas import QwtPlotCanvas
-from qwt.scale_div import QwtScaleDiv
-from qwt.scale_draw import QwtScaleDraw
-from qwt.scale_engine import QwtLinearScaleEngine
-from qwt.scale_map import QwtScaleMap
-from qwt.scale_widget import QwtScaleWidget
-from qwt.text import QwtText, QwtTextLabel
-
-
-def qwtSetTabOrder(first, second, with_children):
- tab_chain = [first, second]
- if with_children:
- children = second.findChildren(QWidget)
- w = second.nextInFocusChain()
- while w in children:
- while w in children:
- children.remove(w)
- tab_chain += [w]
- w = w.nextInFocusChain()
- for idx in range(len(tab_chain) - 1):
- w_from = tab_chain[idx]
- w_to = tab_chain[idx + 1]
- policy1, policy2 = w_from.focusPolicy(), w_to.focusPolicy()
- proxy1, proxy2 = w_from.focusProxy(), w_to.focusProxy()
- for w in (w_from, w_to):
- w.setFocusPolicy(Qt.TabFocus)
- w.setFocusProxy(None)
- QWidget.setTabOrder(w_from, w_to)
- for w, pl, px in ((w_from, policy1, proxy1), (w_to, policy2, proxy2)):
- w.setFocusPolicy(pl)
- w.setFocusProxy(px)
-
-
-class ItemList(list):
- def sortItems(self):
- self.sort(key=lambda item: item.z())
-
- def insertItem(self, obj):
- self.append(obj)
- self.sortItems()
-
- def removeItem(self, obj):
- self.remove(obj)
- self.sortItems()
-
-
-class QwtPlot_PrivateData(QObject):
- def __init__(self):
- QObject.__init__(self)
-
- self.itemList = ItemList()
- self.titleLabel = None
- self.footerLabel = None
- self.canvas = None
- self.legend = None
- self.layout = None
- self.autoReplot = None
- self.flatStyle = None
-
-
-class AxisData(object):
- def __init__(self):
- self.isEnabled = None
- self.doAutoScale = None
- self.minValue = None
- self.maxValue = None
- self.stepSize = None
- self.maxMajor = None
- self.maxMinor = None
- self.isValid = None
- self.scaleDiv = None # QwtScaleDiv
- self.scaleEngine = None # QwtScaleEngine
- self.scaleWidget = None # QwtScaleWidget
- self.margin = None # Margin (float) in %
-
-
-class QwtPlot(QFrame):
- """
- A 2-D plotting widget
-
- QwtPlot is a widget for plotting two-dimensional graphs.
- An unlimited number of plot items can be displayed on its canvas.
- Plot items might be curves (:py:class:`qwt.plot_curve.QwtPlotCurve`),
- markers (:py:class:`qwt.plot_marker.QwtPlotMarker`),
- the grid (:py:class:`qwt.plot_grid.QwtPlotGrid`), or anything else
- derived from :py:class:`QwtPlotItem`.
-
- A plot can have up to four axes, with each plot item attached to an x- and
- a y axis. The scales at the axes can be explicitly set (`QwtScaleDiv`), or
- are calculated from the plot items, using algorithms (`QwtScaleEngine`)
- which can be configured separately for each axis.
-
- The following example is a good starting point to see how to set up a
- plot widget::
-
- from qtpy import QtWidgets as QW
- import qwt
- import numpy as np
-
- app = QW.QApplication([])
- x = np.linspace(-10, 10, 500)
- plot = qwt.QwtPlot("Trigonometric functions")
- plot.insertLegend(qwt.QwtLegend(), qwt.QwtPlot.BottomLegend)
- qwt.QwtPlotCurve.make(x, np.cos(x), "Cosine", plot, linecolor="red", antialiased=True)
- qwt.QwtPlotCurve.make(x, np.sin(x), "Sine", plot, linecolor="blue", antialiased=True)
- plot.resize(600, 300)
- plot.show()
-
- .. image:: /_static/QwtPlot_example.png
-
- .. py:class:: QwtPlot([title=""], [parent=None])
-
- :param str title: Title text
- :param QWidget parent: Parent widget
-
- .. py:data:: itemAttached
-
- A signal indicating, that an item has been attached/detached
-
- :param plotItem: Plot item
- :param on: Attached/Detached
-
- .. py:data:: legendDataChanged
-
- A signal with the attributes how to update
- the legend entries for a plot item.
-
- :param itemInfo: Info about a plot item, build from itemToInfo()
- :param data: Attributes of the entries (usually <= 1) for the plot item.
-
- """
-
- itemAttached = Signal(object, bool)
- legendDataChanged = Signal(object, object)
-
- # enum Axis
- AXES = yLeft, yRight, xBottom, xTop = list(range(4))
- axisCnt = len(AXES) # Not necessary but ensure compatibility with PyQwt
-
- # enum LegendPosition
- LeftLegend, RightLegend, BottomLegend, TopLegend = list(range(4))
-
- def __init__(self, *args):
- if len(args) == 0:
- title, parent = "", None
- elif len(args) == 1:
- if isinstance(args[0], QWidget) or args[0] is None:
- title = ""
- (parent,) = args
- else:
- (title,) = args
- parent = None
- elif len(args) == 2:
- title, parent = args
- else:
- raise TypeError(
- "%s() takes 0, 1 or 2 argument(s) (%s given)"
- % (self.__class__.__name__, len(args))
- )
- QFrame.__init__(self, parent)
-
- self.__layout_state = None
-
- self.__data = QwtPlot_PrivateData()
- from qwt.plot_layout import QwtPlotLayout
-
- self.__data.layout = QwtPlotLayout()
- self.__data.autoReplot = False
-
- self.setAutoReplot(False)
- self.setPlotLayout(self.__data.layout)
-
- # title
- self.__data.titleLabel = QwtTextLabel(self)
- self.__data.titleLabel.setObjectName("QwtPlotTitle")
- text = QwtText(title)
- text.setRenderFlags(Qt.AlignCenter | Qt.TextWordWrap)
- self.__data.titleLabel.setText(text)
-
- # footer
- self.__data.footerLabel = QwtTextLabel(self)
- self.__data.footerLabel.setObjectName("QwtPlotFooter")
- footer = QwtText()
- footer.setRenderFlags(Qt.AlignCenter | Qt.TextWordWrap)
- self.__data.footerLabel.setText(footer)
-
- # legend
- self.__data.legend = None
-
- # axis
- self.__axisData = []
- self.initAxesData()
-
- # canvas
- self.__data.canvas = QwtPlotCanvas(self)
- self.__data.canvas.setObjectName("QwtPlotCanvas")
- self.__data.canvas.installEventFilter(self)
-
- # plot style
- self.setFlatStyle(True)
-
- self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
-
- focusChain = [
- self,
- self.__data.titleLabel,
- self.axisWidget(self.xTop),
- self.axisWidget(self.yLeft),
- self.__data.canvas,
- self.axisWidget(self.yRight),
- self.axisWidget(self.xBottom),
- self.__data.footerLabel,
- ]
-
- for idx in range(len(focusChain) - 1):
- qwtSetTabOrder(focusChain[idx], focusChain[idx + 1], False)
-
- self.legendDataChanged.connect(self.updateLegendItems)
-
- def insertItem(self, item):
- """
- Insert a plot item
-
- :param qwt.plot.QwtPlotItem item: PlotItem
-
- .. seealso::
-
- :py:meth:`removeItem()`
-
- .. note::
-
- This was a member of QwtPlotDict in older versions.
- """
- self.__data.itemList.insertItem(item)
-
- def removeItem(self, item):
- """
- Remove a plot item
-
- :param qwt.plot.QwtPlotItem item: PlotItem
-
- .. seealso::
-
- :py:meth:`insertItem()`
-
- .. note::
-
- This was a member of QwtPlotDict in older versions.
- """
- self.__data.itemList.removeItem(item)
-
- def detachItems(self, rtti=None):
- """
- Detach items from the dictionary
-
- :param rtti: In case of `QwtPlotItem.Rtti_PlotItem` or None (default) detach all items otherwise only those items of the type rtti.
- :type rtti: int or None
-
- .. note::
-
- This was a member of QwtPlotDict in older versions.
- """
- for item in self.__data.itemList[:]:
- if rtti in (None, QwtPlotItem.Rtti_PlotItem) or item.rtti() == rtti:
- item.attach(None)
-
- def itemList(self, rtti=None):
- """
- A list of attached plot items.
-
- Use caution when iterating these lists, as removing/detaching an
- item will invalidate the iterator. Instead you can place pointers
- to objects to be removed in a removal list, and traverse that list
- later.
-
- :param int rtti: In case of `QwtPlotItem.Rtti_PlotItem` detach all items otherwise only those items of the type rtti.
- :return: List of all attached plot items of a specific type. If rtti is None, return a list of all attached plot items.
-
- .. note::
-
- This was a member of QwtPlotDict in older versions.
- """
- if rtti is None or rtti == QwtPlotItem.Rtti_PlotItem:
- return self.__data.itemList
- return [item for item in self.__data.itemList if item.rtti() == rtti]
-
- def setFlatStyle(self, state):
- """
- Set or reset the flatStyle option
-
- If the flatStyle option is set, the plot will be
- rendered without any margin (scales, canvas, layout).
-
- Enabling this option makes the plot look flat and compact.
-
- The flatStyle option is set to True by default.
-
- :param bool state: True or False.
-
- .. seealso::
-
- :py:meth:`flatStyle()`
- """
-
- def make_font(family=None, size=None, delta_size=None, weight=None):
- finfo = self.fontInfo()
- family = finfo.family() if family is None else family
- weight = -1 if weight is None else weight
- size = size if delta_size is None else finfo.pointSize() + delta_size
- return QFont(family, size, weight)
-
- if state:
- # New PythonQwt-exclusive flat style
- plot_title_font = make_font(size=12)
- axis_title_font = make_font(size=11)
- axis_label_font = make_font(size=10)
- tick_lighter_factors = (150, 125, 100)
- scale_margin = scale_spacing = 0
- canvas_frame_style = QFrame.NoFrame
- plot_layout_canvas_margin = plot_layout_spacing = 0
- ticks_color = Qt.darkGray
- labels_color = "#444444"
- else:
- # Old PyQwt / Qwt style
- plot_title_font = make_font(size=14, weight=QFont.Bold)
- axis_title_font = make_font(size=12, weight=QFont.Bold)
- axis_label_font = make_font(size=10)
- tick_lighter_factors = (100, 100, 100)
- scale_margin = scale_spacing = 2
- canvas_frame_style = QFrame.Panel | QFrame.Sunken
- plot_layout_canvas_margin = 4
- plot_layout_spacing = 5
- ticks_color = labels_color = Qt.black
- self.canvas().setFrameStyle(canvas_frame_style)
- self.plotLayout().setCanvasMargin(plot_layout_canvas_margin)
- self.plotLayout().setSpacing(plot_layout_spacing)
- palette = self.palette()
- palette.setColor(QPalette.WindowText, QColor(ticks_color))
- palette.setColor(QPalette.Text, QColor(labels_color))
- self.setPalette(palette)
- for axis_id in self.AXES:
- scale_widget = self.axisWidget(axis_id)
- scale_draw = self.axisScaleDraw(axis_id)
- scale_widget.setFont(axis_label_font)
- scale_widget.setMargin(scale_margin)
- scale_widget.setSpacing(scale_spacing)
- scale_title = scale_widget.title()
- scale_title.setFont(axis_title_font)
- scale_widget.setTitle(scale_title)
- for tick_type, factor in enumerate(tick_lighter_factors):
- scale_draw.setTickLighterFactor(tick_type, factor)
- plot_title = self.title()
- plot_title.setFont(plot_title_font)
- self.setTitle(plot_title)
- self.__data.flatStyle = state
-
- def flatStyle(self):
- """
- :return: True if the flatStyle option is set.
-
- .. seealso::
-
- :py:meth:`setFlatStyle()`
- """
- return self.__data.flatStyle
-
- def initAxesData(self):
- """Initialize axes"""
- self.__axisData = [AxisData() for axisId in self.AXES]
-
- self.__axisData[self.yLeft].scaleWidget = QwtScaleWidget(
- QwtScaleDraw.LeftScale, self
- )
- self.__axisData[self.yRight].scaleWidget = QwtScaleWidget(
- QwtScaleDraw.RightScale, self
- )
- self.__axisData[self.xTop].scaleWidget = QwtScaleWidget(
- QwtScaleDraw.TopScale, self
- )
- self.__axisData[self.xBottom].scaleWidget = QwtScaleWidget(
- QwtScaleDraw.BottomScale, self
- )
-
- self.__axisData[self.yLeft].scaleWidget.setObjectName("QwtPlotAxisYLeft")
- self.__axisData[self.yRight].scaleWidget.setObjectName("QwtPlotAxisYRight")
- self.__axisData[self.xTop].scaleWidget.setObjectName("QwtPlotAxisXTop")
- self.__axisData[self.xBottom].scaleWidget.setObjectName("QwtPlotAxisXBottom")
-
- for axisId in self.AXES:
- d = self.__axisData[axisId]
-
- d.scaleEngine = QwtLinearScaleEngine()
-
- d.scaleWidget.setTransformation(d.scaleEngine.transformation())
- d.scaleWidget.setMargin(2)
-
- text = d.scaleWidget.title()
- d.scaleWidget.setTitle(text)
-
- d.doAutoScale = True
- d.margin = 0.05
- d.minValue = 0.0
- d.maxValue = 1000.0
- d.stepSize = 0.0
- d.maxMinor = 5
- d.maxMajor = 8
- d.isValid = False
-
- self.__axisData[self.yLeft].isEnabled = True
- self.__axisData[self.yRight].isEnabled = False
- self.__axisData[self.xBottom].isEnabled = True
- self.__axisData[self.xTop].isEnabled = False
-
- def deleteAxesData(self):
- # XXX Is is really necessary in Python? (pure transcription of C++)
- for axisId in self.AXES:
- self.__axisData[axisId].scaleEngine = None
- self.__axisData[axisId] = None
-
- def axisWidget(self, axisId):
- """
- :param int axisId: Axis index
- :return: Scale widget of the specified axis, or None if axisId is invalid.
- """
- if self.axisValid(axisId):
- return self.__axisData[axisId].scaleWidget
-
- def setAxisScaleEngine(self, axisId, scaleEngine):
- """
- Change the scale engine for an axis
-
- :param int axisId: Axis index
- :param qwt.scale_engine.QwtScaleEngine scaleEngine: Scale engine
-
- .. seealso::
-
- :py:meth:`axisScaleEngine()`
- """
- if self.axisValid(axisId) and scaleEngine is not None:
- d = self.__axisData[axisId]
- d.scaleEngine = scaleEngine
- self.__axisData[axisId].scaleWidget.setTransformation(
- scaleEngine.transformation()
- )
- d.isValid = False
- self.autoRefresh()
-
- def axisScaleEngine(self, axisId):
- """
- :param int axisId: Axis index
- :return: Scale engine for a specific axis
-
- .. seealso::
-
- :py:meth:`setAxisScaleEngine()`
- """
- if self.axisValid(axisId):
- return self.__axisData[axisId].scaleEngine
-
- def axisAutoScale(self, axisId):
- """
- :param int axisId: Axis index
- :return: True, if autoscaling is enabled
- """
- if self.axisValid(axisId):
- return self.__axisData[axisId].doAutoScale
-
- def axisEnabled(self, axisId):
- """
- :param int axisId: Axis index
- :return: True, if a specified axis is enabled
- """
- if self.axisValid(axisId):
- return self.__axisData[axisId].isEnabled
-
- def axisFont(self, axisId):
- """
- :param int axisId: Axis index
- :return: The font of the scale labels for a specified axis
- """
- if self.axisValid(axisId):
- return self.axisWidget(axisId).font()
- else:
- return QFont()
-
- def axisMaxMajor(self, axisId):
- """
- :param int axisId: Axis index
- :return: The maximum number of major ticks for a specified axis
-
- .. seealso::
-
- :py:meth:`setAxisMaxMajor()`,
- :py:meth:`qwt.scale_engine.QwtScaleEngine.divideScale()`
- """
- if self.axisValid(axisId):
- return self.__axisData[axisId].maxMajor
- else:
- return 0
-
- def axisMaxMinor(self, axisId):
- """
- :param int axisId: Axis index
- :return: The maximum number of minor ticks for a specified axis
-
- .. seealso::
-
- :py:meth:`setAxisMaxMinor()`,
- :py:meth:`qwt.scale_engine.QwtScaleEngine.divideScale()`
- """
- if self.axisValid(axisId):
- return self.__axisData[axisId].maxMinor
- else:
- return 0
-
- def axisScaleDiv(self, axisId):
- """
- :param int axisId: Axis index
- :return: The scale division of a specified axis
-
- axisScaleDiv(axisId).lowerBound(), axisScaleDiv(axisId).upperBound()
- are the current limits of the axis scale.
-
- .. seealso::
-
- :py:class:`qwt.scale_div.QwtScaleDiv`,
- :py:meth:`setAxisScaleDiv()`,
- :py:meth:`qwt.scale_engine.QwtScaleEngine.divideScale()`
- """
- return self.__axisData[axisId].scaleDiv
-
- def axisScaleDraw(self, axisId):
- """
- :param int axisId: Axis index
- :return: Specified scaleDraw for axis, or NULL if axis is invalid.
- """
- if self.axisValid(axisId):
- return self.axisWidget(axisId).scaleDraw()
-
- def axisStepSize(self, axisId):
- """
- :param int axisId: Axis index
- :return: step size parameter value
-
- This doesn't need to be the step size of the current scale.
-
- .. seealso::
-
- :py:meth:`setAxisScale()`,
- :py:meth:`qwt.scale_engine.QwtScaleEngine.divideScale()`
- """
- if self.axisValid(axisId):
- return self.__axisData[axisId].stepSize
- else:
- return 0
-
- def axisMargin(self, axisId):
- """
- :param int axisId: Axis index
- :return: Relative margin of the axis, as a fraction of the full axis range
-
- .. seealso::
-
- :py:meth:`setAxisMargin()`
- """
- if self.axisValid(axisId):
- return self.__axisData[axisId].margin
- return 0.0
-
- def axisInterval(self, axisId):
- """
- :param int axisId: Axis index
- :return: The current interval of the specified axis
-
- This is only a convenience function for axisScaleDiv(axisId).interval()
-
- .. seealso::
-
- :py:class:`qwt.scale_div.QwtScaleDiv`,
- :py:meth:`axisScaleDiv()`
- """
- if self.axisValid(axisId):
- return self.axisScaleDiv(axisId).interval()
- else:
- return QwtInterval()
-
- def axisTitle(self, axisId):
- """
- :param int axisId: Axis index
- :return: Title of a specified axis
- """
- if self.axisValid(axisId):
- return self.axisWidget(axisId).title()
- else:
- return QwtText()
-
- def enableAxis(self, axisId, tf=True):
- """
- Enable or disable a specified axis
-
- When an axis is disabled, this only means that it is not
- visible on the screen. Curves, markers and can be attached
- to disabled axes, and transformation of screen coordinates
- into values works as normal.
-
- Only xBottom and yLeft are enabled by default.
-
- :param int axisId: Axis index
- :param bool tf: True (enabled) or False (disabled)
- """
- if self.axisValid(axisId) and tf != self.__axisData[axisId].isEnabled:
- self.__axisData[axisId].isEnabled = tf
- self.updateLayout()
-
- def invTransform(self, axisId, pos):
- """
- Transform the x or y coordinate of a position in the
- drawing region into a value.
-
- :param int axisId: Axis index
- :param int pos: position
-
- .. warning::
-
- The position can be an x or a y coordinate,
- depending on the specified axis.
- """
- if self.axisValid(axisId):
- return self.canvasMap(axisId).invTransform(pos)
- else:
- return 0.0
-
- def transform(self, axisId, value):
- """
- Transform a value into a coordinate in the plotting region
-
- :param int axisId: Axis index
- :param fload value: Value
- :return: X or Y coordinate in the plotting region corresponding to the value.
- """
- if self.axisValid(axisId):
- return self.canvasMap(axisId).transform(value)
- else:
- return 0.0
-
- def setAxisFont(self, axisId, font):
- """
- Change the font of an axis
-
- :param int axisId: Axis index
- :param QFont font: Font
-
- .. warning::
-
- This function changes the font of the tick labels,
- not of the axis title.
- """
- if self.axisValid(axisId):
- return self.axisWidget(axisId).setFont(font)
-
- def setAxisAutoScale(self, axisId, on=True):
- """
- Enable autoscaling for a specified axis
-
- This member function is used to switch back to autoscaling mode
- after a fixed scale has been set. Autoscaling is enabled by default.
-
- :param int axisId: Axis index
- :param bool on: On/Off
-
- .. seealso::
-
- :py:meth:`setAxisScale()`, :py:meth:`setAxisScaleDiv()`,
- :py:meth:`updateAxes()`
-
- .. note::
-
- The autoscaling flag has no effect until updateAxes() is executed
- ( called by replot() ).
- """
- if self.axisValid(axisId) and self.__axisData[axisId].doAutoScale != on:
- self.__axisData[axisId].doAutoScale = on
- self.autoRefresh()
-
- def setAxisScale(self, axisId, min_, max_, stepSize=0):
- """
- Disable autoscaling and specify a fixed scale for a selected axis.
-
- In updateAxes() the scale engine calculates a scale division from the
- specified parameters, that will be assigned to the scale widget. So
- updates of the scale widget usually happen delayed with the next replot.
-
- :param int axisId: Axis index
- :param float min_: Minimum of the scale
- :param float max_: Maximum of the scale
- :param float stepSize: Major step size. If step == 0, the step size is calculated automatically using the maxMajor setting.
-
- .. seealso::
-
- :py:meth:`setAxisMaxMajor()`, :py:meth:`setAxisAutoScale()`,
- :py:meth:`axisStepSize()`,
- :py:meth:`qwt.scale_engine.QwtScaleEngine.divideScale()`
- """
- if self.axisValid(axisId):
- d = self.__axisData[axisId]
- d.doAutoScale = False
- d.isValid = False
- d.minValue = min_
- d.maxValue = max_
- d.stepSize = stepSize
- self.autoRefresh()
-
- def setAxisScaleDiv(self, axisId, scaleDiv):
- """
- Disable autoscaling and specify a fixed scale for a selected axis.
-
- The scale division will be stored locally only until the next call
- of updateAxes(). So updates of the scale widget usually happen delayed with
- the next replot.
-
- :param int axisId: Axis index
- :param qwt.scale_div.QwtScaleDiv scaleDiv: Scale division
-
- .. seealso::
-
- :py:meth:`setAxisScale()`, :py:meth:`setAxisAutoScale()`
- """
- if self.axisValid(axisId):
- d = self.__axisData[axisId]
- d.doAutoScale = False
- d.scaleDiv = scaleDiv
- d.isValid = True
- self.autoRefresh()
-
- def setAxisScaleDraw(self, axisId, scaleDraw):
- """
- Set a scale draw
-
- :param int axisId: Axis index
- :param qwt.scale_draw.QwtScaleDraw scaleDraw: Object responsible for drawing scales.
-
- By passing scaleDraw it is possible to extend QwtScaleDraw
- functionality and let it take place in QwtPlot. Please note
- that scaleDraw has to be created with new and will be deleted
- by the corresponding QwtScale member ( like a child object ).
-
- .. seealso::
-
- :py:class:`qwt.scale_draw.QwtScaleDraw`,
- :py:class:`qwt.scale_widget.QwtScaleWigdet`
-
- .. warning::
-
- The attributes of scaleDraw will be overwritten by those of the
- previous QwtScaleDraw.
- """
- if self.axisValid(axisId):
- self.axisWidget(axisId).setScaleDraw(scaleDraw)
- self.autoRefresh()
-
- def setAxisLabelAlignment(self, axisId, alignment):
- """
- Change the alignment of the tick labels
-
- :param int axisId: Axis index
- :param Qt.Alignment alignment: Or'd Qt.AlignmentFlags
-
- .. seealso::
-
- :py:meth:`qwt.scale_draw.QwtScaleDraw.setLabelAlignment()`
- """
- if self.axisValid(axisId):
- self.axisWidget(axisId).setLabelAlignment(alignment)
-
- def setAxisLabelRotation(self, axisId, rotation):
- """
- Rotate all tick labels
-
- :param int axisId: Axis index
- :param float rotation: Angle in degrees. When changing the label rotation, the label alignment might be adjusted too.
-
- .. seealso::
-
- :py:meth:`setLabelRotation()`, :py:meth:`setAxisLabelAlignment()`
- """
- if self.axisValid(axisId):
- self.axisWidget(axisId).setLabelRotation(rotation)
-
- def setAxisLabelAutoSize(self, axisId, state):
- """
- Set tick labels automatic size option (default: on)
-
- :param int axisId: Axis index
- :param bool state: On/off
-
- .. seealso::
-
- :py:meth:`qwt.scale_draw.QwtScaleDraw.setLabelAutoSize()`
- """
- if self.axisValid(axisId):
- self.axisWidget(axisId).setLabelAutoSize(state)
-
- def setAxisMaxMinor(self, axisId, maxMinor):
- """
- Set the maximum number of minor scale intervals for a specified axis
-
- :param int axisId: Axis index
- :param int maxMinor: Maximum number of minor steps
-
- .. seealso::
-
- :py:meth:`axisMaxMinor()`
- """
- if self.axisValid(axisId):
- maxMinor = max([0, min([maxMinor, 100])])
- d = self.__axisData[axisId]
- if maxMinor != d.maxMinor:
- d.maxMinor = maxMinor
- d.isValid = False
- self.autoRefresh()
-
- def setAxisMaxMajor(self, axisId, maxMajor):
- """
- Set the maximum number of major scale intervals for a specified axis
-
- :param int axisId: Axis index
- :param int maxMajor: Maximum number of major steps
-
- .. seealso::
-
- :py:meth:`axisMaxMajor()`
- """
- if self.axisValid(axisId):
- maxMajor = max([1, min([maxMajor, 10000])])
- d = self.__axisData[axisId]
- if maxMajor != d.maxMajor:
- d.maxMajor = maxMajor
- d.isValid = False
- self.autoRefresh()
-
- def setAxisMargin(self, axisId, margin):
- """
- Set the relative margin of the axis, as a fraction of the full axis range
-
- :param int axisId: Axis index
- :param float margin: Relative margin (float between 0 and 1)
-
- .. seealso::
-
- :py:meth:`axisMargin()`
- """
- if not isinstance(margin, float) or margin < 0.0 or margin > 1.0:
- raise ValueError("margin must be a float between 0 and 1")
- if self.axisValid(axisId):
- d = self.__axisData[axisId]
- if margin != d.margin:
- d.margin = margin
- d.isValid = False
- self.autoRefresh()
-
- def setAxisTitle(self, axisId, title):
- """
- Change the title of a specified axis
-
- :param int axisId: Axis index
- :param title: axis title
- :type title: qwt.text.QwtText or str
- """
- if self.axisValid(axisId):
- self.axisWidget(axisId).setTitle(title)
- self.updateLayout()
-
- def updateAxes(self):
- """
- Rebuild the axes scales
-
- In case of autoscaling the boundaries of a scale are calculated
- from the bounding rectangles of all plot items, having the
- `QwtPlotItem.AutoScale` flag enabled (`QwtScaleEngine.autoScale()`).
- Then a scale division is calculated (`QwtScaleEngine.didvideScale()`)
- and assigned to scale widget.
-
- When the scale boundaries have been assigned with `setAxisScale()` a
- scale division is calculated (`QwtScaleEngine.didvideScale()`)
- for this interval and assigned to the scale widget.
-
- When the scale has been set explicitly by `setAxisScaleDiv()` the
- locally stored scale division gets assigned to the scale widget.
-
- The scale widget indicates modifications by emitting a
- `QwtScaleWidget.scaleDivChanged()` signal.
-
- `updateAxes()` is usually called by `replot()`.
-
- .. seealso::
-
- :py:meth:`setAxisAutoScale()`, :py:meth:`setAxisScale()`,
- :py:meth:`setAxisScaleDiv()`, :py:meth:`replot()`,
- :py:meth:`QwtPlotItem.boundingRect()`
- """
- intv = [QwtInterval() for _i in self.AXES]
- itmList = self.itemList()
- for item in itmList:
- if not item.testItemAttribute(QwtPlotItem.AutoScale):
- continue
- if not item.isVisible():
- continue
- if self.axisAutoScale(item.xAxis()) or self.axisAutoScale(item.yAxis()):
- rect = item.boundingRect()
- if rect.width() >= 0.0:
- intv[item.xAxis()] |= QwtInterval(rect.left(), rect.right())
- if rect.height() >= 0.0:
- intv[item.yAxis()] |= QwtInterval(rect.top(), rect.bottom())
-
- for axisId in self.AXES:
- d = self.__axisData[axisId]
- minValue = d.minValue
- maxValue = d.maxValue
- stepSize = d.stepSize
- if d.doAutoScale and intv[axisId].isValid():
- d.isValid = False
- minValue = intv[axisId].minValue()
- maxValue = intv[axisId].maxValue()
- minValue, maxValue, stepSize = d.scaleEngine.autoScale(
- d.maxMajor, minValue, maxValue, stepSize, d.margin
- )
- if not d.isValid:
- d.scaleDiv = d.scaleEngine.divideScale(
- minValue, maxValue, d.maxMajor, d.maxMinor, stepSize
- )
- d.isValid = True
- scaleWidget = self.axisWidget(axisId)
- scaleWidget.setScaleDiv(d.scaleDiv)
-
- # It is *really* necessary to update border dist!
- # Otherwise, when tick labels are large enough, the ticks
- # may not be aligned with canvas grid.
- # See the following issues for more details:
- # https://github.com/PlotPyStack/guiqwt/issues/57
- # https://github.com/PlotPyStack/PythonQwt/issues/30
- startDist, endDist = scaleWidget.getBorderDistHint()
- scaleWidget.setBorderDist(startDist, endDist)
-
- for item in itmList:
- if item.testItemInterest(QwtPlotItem.ScaleInterest):
- item.updateScaleDiv(
- self.axisScaleDiv(item.xAxis()), self.axisScaleDiv(item.yAxis())
- )
-
- def setCanvas(self, canvas):
- """
- Set the drawing canvas of the plot widget.
-
- The default canvas is a `QwtPlotCanvas`.
-
- :param QWidget canvas: Canvas Widget
-
- .. seealso::
-
- :py:meth:`canvas()`
- """
- if canvas == self.__data.canvas:
- return
- self.__data.canvas = canvas
- if canvas is not None:
- canvas.setParent(self)
- canvas.installEventFilter(self)
- if self.isVisible():
- canvas.show()
-
- def event(self, event):
- if event.type() == QEvent.LayoutRequest:
- self.updateLayout()
- elif event.type() == QEvent.PolishRequest:
- self.replot()
- return QFrame.event(self, event)
-
- def eventFilter(self, obj, event):
- if obj is self.__data.canvas:
- if event.type() == QEvent.Resize:
- self.updateCanvasMargins()
- elif event.type() == 178: # QEvent.ContentsRectChange:
- self.updateLayout()
- return QFrame.eventFilter(self, obj, event)
-
- def autoRefresh(self):
- """Replots the plot if :py:meth:`autoReplot()` is True."""
- if self.__data.autoReplot:
- self.replot()
-
- def setAutoReplot(self, tf=True):
- """
- Set or reset the autoReplot option
-
- If the autoReplot option is set, the plot will be
- updated implicitly by manipulating member functions.
- Since this may be time-consuming, it is recommended
- to leave this option switched off and call :py:meth:`replot()`
- explicitly if necessary.
-
- The autoReplot option is set to false by default, which
- means that the user has to call :py:meth:`replot()` in order
- to make changes visible.
-
- :param bool tf: True or False. Defaults to True.
-
- .. seealso::
-
- :py:meth:`autoReplot()`
- """
- self.__data.autoReplot = tf
-
- def autoReplot(self):
- """
- :return: True if the autoReplot option is set.
-
- .. seealso::
-
- :py:meth:`setAutoReplot()`
- """
- return self.__data.autoReplot
-
- def setTitle(self, title):
- """
- Change the plot's title
-
- :param title: New title
- :type title: str or qwt.text.QwtText
-
- .. seealso::
-
- :py:meth:`title()`
- """
- current_title = self.__data.titleLabel.text()
- if isinstance(title, QwtText) and current_title == title:
- return
- elif not isinstance(title, QwtText) and current_title.text() == title:
- return
- self.__data.titleLabel.setText(title)
- self.updateLayout()
-
- def title(self):
- """
- :return: Title of the plot
-
- .. seealso::
-
- :py:meth:`setTitle()`
- """
- return self.__data.titleLabel.text()
-
- def titleLabel(self):
- """
- :return: Title label widget.
- """
- return self.__data.titleLabel
-
- def setFooter(self, text):
- """
- Change the text the footer
-
- :param text: New text of the footer
- :type text: str or qwt.text.QwtText
-
- .. seealso::
-
- :py:meth:`footer()`
- """
- current_footer = self.__data.footerLabel.text()
- if isinstance(text, QwtText) and current_footer == text:
- return
- elif not isinstance(text, QwtText) and current_footer.text() == text:
- return
- self.__data.footerLabel.setText(text)
- self.updateLayout()
-
- def footer(self):
- """
- :return: Text of the footer
-
- .. seealso::
-
- :py:meth:`setFooter()`
- """
- return self.__data.footerLabel.text()
-
- def footerLabel(self):
- """
- :return: Footer label widget.
- """
- return self.__data.footerLabel
-
- def setPlotLayout(self, layout):
- """
- Assign a new plot layout
-
- :param layout: Layout
- :type layout: qwt.plot_layout.QwtPlotLayout
-
- .. seealso::
-
- :py:meth:`plotLayout()`
- """
- if layout != self.__data.layout:
- self.__data.layout = layout
- self.updateLayout()
-
- def plotLayout(self):
- """
- :return: the plot's layout
-
- .. seealso::
-
- :py:meth:`setPlotLayout()`
- """
- return self.__data.layout
-
- def legend(self):
- """
- :return: the plot's legend
-
- .. seealso::
-
- :py:meth:`insertLegend()`
- """
- return self.__data.legend
-
- def canvas(self):
- """
- :return: the plot's canvas
- """
- return self.__data.canvas
-
- def sizeHint(self):
- """
- :return: Size hint for the plot widget
-
- .. seealso::
-
- :py:meth:`minimumSizeHint()`
- """
- dw = dh = 0
- for axisId in self.AXES:
- if self.axisEnabled(axisId):
- niceDist = 40
- scaleWidget = self.axisWidget(axisId)
- scaleDiv = scaleWidget.scaleDraw().scaleDiv()
- majCnt = len(scaleDiv.ticks(QwtScaleDiv.MajorTick))
- if axisId in (self.yLeft, self.yRight):
- hDiff = (
- majCnt - 1
- ) * niceDist - scaleWidget.minimumSizeHint().height()
- if hDiff > dh:
- dh = hDiff
- else:
- wDiff = (
- majCnt - 1
- ) * niceDist - scaleWidget.minimumSizeHint().width()
- if wDiff > dw:
- dw = wDiff
- return self.minimumSizeHint() + QSize(dw, dh)
-
- def minimumSizeHint(self):
- """
- :return: Return a minimum size hint
- """
- hint = self.__data.layout.minimumSizeHint(self)
- hint += QSize(2 * self.frameWidth(), 2 * self.frameWidth())
- return hint
-
- def resizeEvent(self, e):
- QFrame.resizeEvent(self, e)
- self.updateLayout()
-
- def replot(self):
- """
- Redraw the plot
-
- If the `autoReplot` option is not set (which is the default)
- or if any curves are attached to raw data, the plot has to
- be refreshed explicitly in order to make changes visible.
-
- .. seealso::
-
- :py:meth:`updateAxes()`, :py:meth:`setAutoReplot()`
- """
- doAutoReplot = self.autoReplot()
- self.setAutoReplot(False)
- self.updateAxes()
-
- # Maybe the layout needs to be updated, because of changed
- # axes labels. We need to process them here before painting
- # to avoid that scales and canvas get out of sync.
- QApplication.sendPostedEvents(self, QEvent.LayoutRequest)
-
- if self.__data.canvas:
- try:
- self.__data.canvas.replot()
- except (AttributeError, TypeError):
- self.__data.canvas.update(self.__data.canvas.contentsRect())
-
- self.setAutoReplot(doAutoReplot)
-
- def get_layout_state(self):
- return (
- self.contentsRect(),
- self.__data.titleLabel.text(),
- self.__data.footerLabel.text(),
- [
- (self.axisEnabled(axisId), self.axisTitle(axisId).text())
- for axisId in self.AXES
- ],
- self.__data.legend,
- )
-
- def updateLayout(self):
- """
- Adjust plot content to its current size.
-
- .. seealso::
-
- :py:meth:`resizeEvent()`
- """
- # state = self.get_layout_state()
- # if self.__layout_state is not None and\
- # state == self.__layout_state:
- # return
- # self.__layout_state = state
-
- self.__data.layout.activate(self, self.contentsRect())
-
- titleRect = self.__data.layout.titleRect().toRect()
- footerRect = self.__data.layout.footerRect().toRect()
- scaleRect = [
- self.__data.layout.scaleRect(axisId).toRect() for axisId in self.AXES
- ]
- legendRect = self.__data.layout.legendRect().toRect()
- canvasRect = self.__data.layout.canvasRect().toRect()
-
- if self.__data.titleLabel.text():
- self.__data.titleLabel.setGeometry(titleRect)
- if not self.__data.titleLabel.isVisibleTo(self):
- self.__data.titleLabel.show()
- else:
- self.__data.titleLabel.hide()
-
- if self.__data.footerLabel.text():
- self.__data.footerLabel.setGeometry(footerRect)
- if not self.__data.footerLabel.isVisibleTo(self):
- self.__data.footerLabel.show()
- else:
- self.__data.footerLabel.hide()
-
- for axisId in self.AXES:
- scaleWidget = self.axisWidget(axisId)
- if self.axisEnabled(axisId):
- if scaleRect[axisId] != scaleWidget.geometry():
- scaleWidget.setGeometry(scaleRect[axisId])
- startDist, endDist = scaleWidget.getBorderDistHint()
- scaleWidget.setBorderDist(startDist, endDist)
-
- # -------------------------------------------------------------
- # XXX: The following was commented to fix issue #35
- # Note: the same code part in Qwt's original source code is
- # annotated with the mention "do we need this code any
- # longer ???"... I guess not :)
- # if axisId in (self.xBottom, self.xTop):
- # r = QRegion(scaleRect[axisId])
- # if self.axisEnabled(self.yLeft):
- # r = r.subtracted(QRegion(scaleRect[self.yLeft]))
- # if self.axisEnabled(self.yRight):
- # r = r.subtracted(QRegion(scaleRect[self.yRight]))
- # r.translate(-scaleRect[axisId].x(), -scaleRect[axisId].y())
- # scaleWidget.setMask(r)
- # -------------------------------------------------------------
-
- if not scaleWidget.isVisibleTo(self):
- scaleWidget.show()
- else:
- scaleWidget.hide()
-
- if self.__data.legend:
- if self.__data.legend.isEmpty():
- self.__data.legend.hide()
- else:
- self.__data.legend.setGeometry(legendRect)
- self.__data.legend.show()
-
- self.__data.canvas.setGeometry(canvasRect)
-
- def getCanvasMarginsHint(self, maps, canvasRect):
- """
- Calculate the canvas margins
-
- :param list maps: `QwtPlot.axisCnt` maps, mapping between plot and paint device coordinates
- :param QRectF canvasRect: Bounding rectangle where to paint
-
- Plot items might indicate, that they need some extra space
- at the borders of the canvas by the `QwtPlotItem.Margins` flag.
-
- .. seealso::
-
- :py:meth:`updateCanvasMargins()`, :py:meth:`getCanvasMarginHint()`
- """
- left = top = right = bottom = -1.0
-
- for item in self.itemList():
- if item.testItemAttribute(QwtPlotItem.Margins):
- m = item.getCanvasMarginHint(
- maps[item.xAxis()], maps[item.yAxis()], canvasRect
- )
- left = max([left, m[self.yLeft]])
- top = max([top, m[self.xTop]])
- right = max([right, m[self.yRight]])
- bottom = max([bottom, m[self.xBottom]])
-
- return left, top, right, bottom
-
- def updateCanvasMargins(self):
- """
- Update the canvas margins
-
- Plot items might indicate, that they need some extra space
- at the borders of the canvas by the `QwtPlotItem.Margins` flag.
-
- .. seealso::
-
- :py:meth:`getCanvasMarginsHint()`,
- :py:meth:`QwtPlotItem.getCanvasMarginHint()`
- """
- maps = [self.canvasMap(axisId) for axisId in self.AXES]
- margins = self.getCanvasMarginsHint(maps, self.canvas().contentsRect())
-
- doUpdate = False
-
- for axisId in self.AXES:
- if margins[axisId] >= 0.0:
- m = math.ceil(margins[axisId])
- self.plotLayout().setCanvasMargin(m, axisId)
- doUpdate = True
-
- if doUpdate:
- self.updateLayout()
-
- def drawCanvas(self, painter):
- """
- Redraw the canvas.
-
- :param QPainter painter: Painter used for drawing
-
- .. warning::
-
- drawCanvas calls drawItems what is also used
- for printing. Applications that like to add individual
- plot items better overload drawItems()
-
- .. seealso::
-
- :py:meth:`getCanvasMarginsHint()`,
- :py:meth:`QwtPlotItem.getCanvasMarginHint()`
- """
- maps = [self.canvasMap(axisId) for axisId in self.AXES]
- self.drawItems(painter, QRectF(self.__data.canvas.contentsRect()), maps)
-
- def drawItems(self, painter, canvasRect, maps):
- """
- Redraw the canvas.
-
- :param QPainter painter: Painter used for drawing
- :param QRectF canvasRect: Bounding rectangle where to paint
- :param list maps: `QwtPlot.axisCnt` maps, mapping between plot and paint device coordinates
-
- .. note::
-
- Usually canvasRect is `contentsRect()` of the plot canvas.
- Due to a bug in Qt this rectangle might be wrong for certain
- frame styles ( f.e `QFrame.Box` ) and it might be necessary to
- fix the margins manually using `QWidget.setContentsMargins()`
- """
- for item in self.itemList():
- if item and item.isVisible():
- painter.save()
- painter.setRenderHint(
- QPainter.Antialiasing,
- item.testRenderHint(QwtPlotItem.RenderAntialiased),
- )
- item.draw(painter, maps[item.xAxis()], maps[item.yAxis()], canvasRect)
- painter.restore()
-
- def canvasMap(self, axisId):
- """
- :param int axisId: Axis
- :return: Map for the axis on the canvas. With this map pixel coordinates can translated to plot coordinates and vice versa.
-
- .. seealso::
-
- :py:class:`qwt.scale_map.QwtScaleMap`,
- :py:meth:`transform()`, :py:meth:`invTransform()`
- """
- map_ = QwtScaleMap()
- if not self.__data.canvas:
- return map_
-
- map_.setTransformation(self.axisScaleEngine(axisId).transformation())
- sd = self.axisScaleDiv(axisId)
- if sd is None:
- return map_
- map_.setScaleInterval(sd.lowerBound(), sd.upperBound())
-
- if self.axisEnabled(axisId):
- s = self.axisWidget(axisId)
- if axisId in (self.yLeft, self.yRight):
- y = s.y() + s.startBorderDist() - self.__data.canvas.y()
- h = s.height() - s.startBorderDist() - s.endBorderDist()
- map_.setPaintInterval(y + h, y)
- else:
- x = s.x() + s.startBorderDist() - self.__data.canvas.x()
- w = s.width() - s.startBorderDist() - s.endBorderDist()
- map_.setPaintInterval(x, x + w)
- else:
- canvasRect = self.__data.canvas.contentsRect()
- if axisId in (self.yLeft, self.yRight):
- top = 0
- if not self.plotLayout().alignCanvasToScale(self.xTop):
- top = self.plotLayout().canvasMargin(self.xTop)
- bottom = 0
- if not self.plotLayout().alignCanvasToScale(self.xBottom):
- bottom = self.plotLayout().canvasMargin(self.xBottom)
- map_.setPaintInterval(
- canvasRect.bottom() - bottom, canvasRect.top() + top
- )
- else:
- left = 0
- if not self.plotLayout().alignCanvasToScale(self.yLeft):
- left = self.plotLayout().canvasMargin(self.yLeft)
- right = 0
- if not self.plotLayout().alignCanvasToScale(self.yRight):
- right = self.plotLayout().canvasMargin(self.yRight)
- map_.setPaintInterval(
- canvasRect.left() + left, canvasRect.right() - right
- )
- return map_
-
- def setCanvasBackground(self, brush):
- """
- Change the background of the plotting area
-
- Sets brush to `QPalette.Window` of all color groups of
- the palette of the canvas. Using `canvas().setPalette()`
- is a more powerful way to set these colors.
-
- :param QBrush brush: New background brush
-
- .. seealso::
-
- :py:meth:`canvasBackground()`
- """
- pal = self.__data.canvas.palette()
- pal.setBrush(QPalette.Window, QBrush(brush))
- self.canvas().setPalette(pal)
-
- def canvasBackground(self):
- """
- :return: Background brush of the plotting area.
-
- .. seealso::
-
- :py:meth:`setCanvasBackground()`
- """
- return self.canvas().palette().brush(QPalette.Active, QPalette.Window)
-
- def axisValid(self, axis_id):
- """
- :param int axis_id: Axis
- :return: True if the specified axis exists, otherwise False
- """
- return axis_id in QwtPlot.AXES
-
- def insertLegend(self, legend, pos=None, ratio=-1):
- """
- Insert a legend
-
- If the position legend is `QwtPlot.LeftLegend` or `QwtPlot.RightLegend`
- the legend will be organized in one column from top to down.
- Otherwise the legend items will be placed in a table
- with a best fit number of columns from left to right.
-
- insertLegend() will set the plot widget as parent for the legend.
- The legend will be deleted in the destructor of the plot or when
- another legend is inserted.
-
- Legends, that are not inserted into the layout of the plot widget
- need to connect to the legendDataChanged() signal. Calling updateLegend()
- initiates this signal for an initial update. When the application code
- wants to implement its own layout this also needs to be done for
- rendering plots to a document ( see QwtPlotRenderer ).
-
- :param qwt.legend.QwtAbstractLegend legend: Legend
- :param QwtPlot.LegendPosition pos: The legend's position.
- :param float ratio: Ratio between legend and the bounding rectangle of title, canvas and axes
-
- .. note::
-
- For top/left position the number of columns will be limited to 1,
- otherwise it will be set to unlimited.
-
- .. note::
-
- The legend will be shrunk if it would need more space than the
- given ratio. The ratio is limited to ]0.0 .. 1.0].
- In case of <= 0.0 it will be reset to the default ratio.
- The default vertical/horizontal ratio is 0.33/0.5.
-
- .. seealso::
-
- :py:meth:`legend()`,
- :py:meth:`qwt.plot_layout.QwtPlotLayout.legendPosition()`,
- :py:meth:`qwt.plot_layout.QwtPlotLayout.setLegendPosition()`
- """
- if pos is None:
- pos = self.RightLegend
- self.__data.layout.setLegendPosition(pos, ratio)
- if legend != self.__data.legend:
- if self.__data.legend and self.__data.legend.parent() is self:
- self.__data.legend.setParent(None)
- del self.__data.legend
- self.__data.legend = legend
- if self.__data.legend:
- self.legendDataChanged.connect(self.__data.legend.updateLegend)
- if self.__data.legend.parent() is not self:
- self.__data.legend.setParent(self)
-
- self.legendDataChanged.disconnect(self.updateLegendItems)
- self.updateLegend()
- self.legendDataChanged.connect(self.updateLegendItems)
-
- lpos = self.__data.layout.legendPosition()
-
- if legend is not None:
- if lpos in (self.LeftLegend, self.RightLegend):
- if legend.maxColumns() == 0:
- legend.setMaxColumns(1)
- elif lpos in (self.TopLegend, self.BottomLegend):
- legend.setMaxColumns(0)
-
- previousInChain = None
- if lpos == self.LeftLegend:
- previousInChain = self.axisWidget(QwtPlot.xTop)
- elif lpos == self.TopLegend:
- previousInChain = self
- elif lpos == self.RightLegend:
- previousInChain = self.axisWidget(QwtPlot.yRight)
- elif lpos == self.BottomLegend:
- previousInChain = self.footerLabel()
-
- if previousInChain is not None:
- qwtSetTabOrder(previousInChain, legend, True)
-
- self.updateLayout()
-
- def updateLegend(self, plotItem=None):
- """
- If plotItem is None, emit QwtPlot.legendDataChanged for all
- plot item. Otherwise, emit the signal for passed plot item.
-
- :param qwt.plot.QwtPlotItem plotItem: Plot item
-
- .. seealso::
-
- :py:meth:`QwtPlotItem.legendData()`, :py:data:`QwtPlot.legendDataChanged`
- """
- if plotItem is None:
- items = list(self.itemList())
- else:
- items = [plotItem]
- for plotItem in items:
- if plotItem is None:
- continue
- legendData = []
- if plotItem.testItemAttribute(QwtPlotItem.Legend):
- legendData = plotItem.legendData()
- self.legendDataChanged.emit(plotItem, legendData)
-
- def updateLegendItems(self, plotItem, legendData):
- """
- Update all plot items interested in legend attributes
-
- Call `QwtPlotItem.updateLegend()`, when the
- `QwtPlotItem.LegendInterest` flag is set.
-
- :param qwt.plot.QwtPlotItem plotItem: Plot item
- :param list legendData: Entries to be displayed for the plot item ( usually 1 )
-
- .. seealso::
-
- :py:meth:`QwtPlotItem.LegendInterest()`,
- :py:meth:`QwtPlotItem.updateLegend`
- """
- if plotItem is not None:
- for item in self.itemList():
- if item.testItemInterest(QwtPlotItem.LegendInterest):
- item.updateLegend(plotItem, legendData)
-
- def attachItem(self, plotItem, on):
- """
- Attach/Detach a plot item
-
- :param qwt.plot.QwtPlotItem plotItem: Plot item
- :param bool on: When true attach the item, otherwise detach it
- """
- if plotItem.testItemInterest(QwtPlotItem.LegendInterest):
- for item in self.itemList():
- legendData = []
- if on and item.testItemAttribute(QwtPlotItem.Legend):
- legendData = item.legendData()
- plotItem.updateLegend(item, legendData)
-
- if on:
- self.insertItem(plotItem)
- else:
- self.removeItem(plotItem)
-
- self.itemAttached.emit(plotItem, on)
-
- if plotItem.testItemAttribute(QwtPlotItem.Legend):
- if on:
- self.updateLegend(plotItem)
- else:
- self.legendDataChanged.emit(plotItem, [])
-
- self.autoRefresh()
-
- def print_(self, printer):
- """
- Print plot to printer
-
- :param printer: Printer
- :type printer: QPaintDevice or QPrinter or QSvgGenerator
- """
- from qwt.plot_renderer import QwtPlotRenderer
-
- renderer = QwtPlotRenderer(self)
- renderer.renderTo(self, printer)
-
- def exportTo(
- self, filename, size=(800, 600), size_mm=None, resolution=85, format_=None
- ):
- """
- Export plot to PDF or image file (SVG, PNG, ...)
-
- :param str filename: Filename
- :param tuple size: (width, height) size in pixels
- :param tuple size_mm: (width, height) size in millimeters
- :param int resolution: Resolution in dots per Inch (dpi)
- :param str format_: File format (PDF, SVG, PNG, ...)
- """
- if size_mm is None:
- size_mm = tuple(25.4 * np.array(size) / resolution)
- from qwt.plot_renderer import QwtPlotRenderer
-
- renderer = QwtPlotRenderer(self)
- renderer.renderDocument(self, filename, size_mm, resolution, format_)
-
-
-class QwtPlotItem_PrivateData(QObject):
- def __init__(self):
- QObject.__init__(self)
-
- self.plot = None
- self.isVisible = True
- self.attributes = 0
- self.interests = 0
- self.renderHints = 0
- self.z = 0.0
- self.xAxis = QwtPlot.xBottom
- self.yAxis = QwtPlot.yLeft
- self.legendIconSize = QSize(8, 8)
- self.title = None # QwtText
-
-
-class QwtPlotItem(object):
- """
- Base class for items on the plot canvas
-
- A plot item is "something", that can be painted on the plot canvas,
- or only affects the scales of the plot widget. They can be categorized as:
-
- - Representator
-
- A "Representator" is an item that represents some sort of data
- on the plot canvas. The different representator classes are organized
- according to the characteristics of the data:
-
- - :py:class:`qwt.plot_marker.QwtPlotMarker`: Represents a point or a
- horizontal/vertical coordinate
- - :py:class:`qwt.plot_curve.QwtPlotCurve`: Represents a series of
- points
-
- - Decorators
-
- A "Decorator" is an item, that displays additional information, that
- is not related to any data:
-
- - :py:class:`qwt.plot_grid.QwtPlotGrid`
-
- Depending on the `QwtPlotItem.ItemAttribute` flags, an item is included
- into autoscaling or has an entry on the legend.
-
- Before misusing the existing item classes it might be better to
- implement a new type of plot item
- ( don't implement a watermark as spectrogram ).
- Deriving a new type of `QwtPlotItem` primarily means to implement
- the `YourPlotItem.draw()` method.
-
- .. seealso::
-
- The cpuplot example shows the implementation of additional plot items.
-
- .. py:class:: QwtPlotItem([title=None])
-
- Constructor
-
- :param title: Title of the item
- :type title: qwt.text.QwtText or str
- """
-
- # enum RttiValues
- (
- Rtti_PlotItem,
- Rtti_PlotGrid,
- Rtti_PlotScale,
- Rtti_PlotLegend,
- Rtti_PlotMarker,
- Rtti_PlotCurve,
- Rtti_PlotSpectroCurve,
- Rtti_PlotIntervalCurve,
- Rtti_PlotHistogram,
- Rtti_PlotSpectrogram,
- Rtti_PlotSVG,
- Rtti_PlotTradingCurve,
- Rtti_PlotBarChart,
- Rtti_PlotMultiBarChart,
- Rtti_PlotShape,
- Rtti_PlotTextLabel,
- Rtti_PlotZone,
- ) = list(range(17))
- Rtti_PlotUserItem = 1000
-
- # enum ItemAttribute
- Legend = 0x01
- AutoScale = 0x02
- Margins = 0x04
-
- # enum ItemInterest
- ScaleInterest = 0x01
- LegendInterest = 0x02
-
- # enum RenderHint
- RenderAntialiased = 0x1
-
- def __init__(self, title=None, icon=None):
- """title: QwtText"""
- if title is None:
- title = QwtText("")
- if hasattr(title, "capitalize"): # avoids dealing with Py3K compat.
- title = QwtText(title)
- assert isinstance(title, QwtText)
- self.__data = QwtPlotItem_PrivateData()
- self.__data.title = title
- self.__data.icon = icon
-
- def attach(self, plot):
- """
- Attach the item to a plot.
-
- This method will attach a `QwtPlotItem` to the `QwtPlot` argument.
- It will first detach the `QwtPlotItem` from any plot from a previous
- call to attach (if necessary). If a None argument is passed, it will
- detach from any `QwtPlot` it was attached to.
-
- :param qwt.plot.QwtPlot plot: Plot widget
-
- .. seealso::
-
- :py:meth:`detach()`
- """
- if plot is self.__data.plot:
- return
-
- if self.__data.plot:
- self.__data.plot.attachItem(self, False)
-
- self.__data.plot = plot
-
- if self.__data.plot:
- self.__data.plot.attachItem(self, True)
-
- def detach(self):
- """
- Detach the item from a plot.
-
- This method detaches a `QwtPlotItem` from any `QwtPlot` it has been
- associated with.
-
- .. seealso::
-
- :py:meth:`attach()`
- """
- self.attach(None)
-
- def rtti(self):
- """
- Return rtti for the specific class represented. `QwtPlotItem` is
- simply a virtual interface class, and base classes will implement
- this method with specific rtti values so a user can differentiate
- them.
-
- :return: rtti value
- """
- return self.Rtti_PlotItem
-
- def plot(self):
- """
- :return: attached plot
- """
- return self.__data.plot
-
- def z(self):
- """
- Plot items are painted in increasing z-order.
-
- :return: item z order
-
- .. seealso::
-
- :py:meth:`setZ()`, :py:meth:`QwtPlotDict.itemList()`
- """
- return self.__data.z
-
- def setZ(self, z):
- """
- Set the z value
-
- Plot items are painted in increasing z-order.
-
- :param float z: Z-value
-
- .. seealso::
-
- :py:meth:`z()`, :py:meth:`QwtPlotDict.itemList()`
- """
- if self.__data.z != z:
- if self.__data.plot:
- self.__data.plot.attachItem(self, False)
- self.__data.z = z
- if self.__data.plot:
- self.__data.plot.attachItem(self, True)
- self.itemChanged()
-
- def setTitle(self, title):
- """
- Set a new title
-
- :param title: Title
- :type title: qwt.text.QwtText or str
-
- .. seealso::
-
- :py:meth:`title()`
- """
- if not isinstance(title, QwtText):
- title = QwtText(title)
- if self.__data.title != title:
- self.__data.title = title
- self.legendChanged()
-
- def title(self):
- """
- :return: Title of the item
-
- .. seealso::
-
- :py:meth:`setTitle()`
- """
- return self.__data.title
-
- def setItemAttribute(self, attribute, on=True):
- """
- Toggle an item attribute
-
- :param int attribute: Attribute type
- :param bool on: True/False
-
- .. seealso::
-
- :py:meth:`testItemAttribute()`
- """
- if bool(self.__data.attributes & attribute) != on:
- if on:
- self.__data.attributes |= attribute
- else:
- self.__data.attributes &= ~attribute
- if attribute == QwtPlotItem.Legend:
- self.legendChanged()
- self.itemChanged()
-
- def testItemAttribute(self, attribute):
- """
- Test an item attribute
-
- :param int attribute: Attribute type
- :return: True/False
-
- .. seealso::
-
- :py:meth:`setItemAttribute()`
- """
- return bool(self.__data.attributes & attribute)
-
- def setItemInterest(self, interest, on=True):
- """
- Toggle an item interest
-
- :param int attribute: Interest type
- :param bool on: True/False
-
- .. seealso::
-
- :py:meth:`testItemInterest()`
- """
- if bool(self.__data.interests & interest) != on:
- if on:
- self.__data.interests |= interest
- else:
- self.__data.interests &= ~interest
- self.itemChanged()
-
- def testItemInterest(self, interest):
- """
- Test an item interest
-
- :param int attribute: Interest type
- :return: True/False
-
- .. seealso::
-
- :py:meth:`setItemInterest()`
- """
- return bool(self.__data.interests & interest)
-
- def setRenderHint(self, hint, on=True):
- """
- Toggle a render hint
-
- :param int hint: Render hint
- :param bool on: True/False
-
- .. seealso::
-
- :py:meth:`testRenderHint()`
- """
- if bool(self.__data.renderHints & hint) != on:
- if on:
- self.__data.renderHints |= hint
- else:
- self.__data.renderHints &= ~hint
- self.itemChanged()
-
- def testRenderHint(self, hint):
- """
- Test a render hint
-
- :param int attribute: Render hint
- :return: True/False
-
- .. seealso::
-
- :py:meth:`setRenderHint()`
- """
- return bool(self.__data.renderHints & hint)
-
- def setLegendIconSize(self, size):
- """
- Set the size of the legend icon
-
- The default setting is 8x8 pixels
-
- :param QSize size: Size
-
- .. seealso::
-
- :py:meth:`legendIconSize()`, :py:meth:`legendIcon()`
- """
- if self.__data.legendIconSize != size:
- self.__data.legendIconSize = size
- self.legendChanged()
-
- def legendIconSize(self):
- """
- :return: Legend icon size
-
- .. seealso::
-
- :py:meth:`setLegendIconSize()`, :py:meth:`legendIcon()`
- """
- return self.__data.legendIconSize
-
- def legendIcon(self, index, size):
- """
- :param int index: Index of the legend entry (usually there is only one)
- :param QSizeF size: Icon size
- :return: Icon representing the item on the legend
-
- The default implementation returns an invalid icon
-
- .. seealso::
-
- :py:meth:`setLegendIconSize()`, :py:meth:`legendData()`
- """
- return QwtGraphic()
-
- def show(self):
- """Show the item"""
- self.setVisible(True)
-
- def hide(self):
- """Hide the item"""
- self.setVisible(False)
-
- def setVisible(self, on):
- """
- Show/Hide the item
-
- :param bool on: Show if True, otherwise hide
-
- .. seealso::
-
- :py:meth:`isVisible()`, :py:meth:`show()`, :py:meth:`hide()`
- """
- if on != self.__data.isVisible:
- self.__data.isVisible = on
- self.itemChanged()
-
- def isVisible(self):
- """
- :return: True if visible
-
- .. seealso::
-
- :py:meth:`setVisible()`, :py:meth:`show()`, :py:meth:`hide()`
- """
- return self.__data.isVisible
-
- def itemChanged(self):
- """
- Update the legend and call `QwtPlot.autoRefresh()` for the
- parent plot.
-
- .. seealso::
-
- :py:meth:`QwtPlot.legendChanged()`, :py:meth:`QwtPlot.autoRefresh()`
- """
- if self.__data.plot:
- self.__data.plot.autoRefresh()
-
- def legendChanged(self):
- """
- Update the legend of the parent plot.
-
- .. seealso::
-
- :py:meth:`QwtPlot.updateLegend()`, :py:meth:`itemChanged()`
- """
- if self.testItemAttribute(QwtPlotItem.Legend) and self.__data.plot:
- self.__data.plot.updateLegend(self)
-
- def setAxes(self, xAxis, yAxis):
- """
- Set X and Y axis
-
- The item will painted according to the coordinates of its Axes.
-
- :param int xAxis: X Axis (`QwtPlot.xBottom` or `QwtPlot.xTop`)
- :param int yAxis: Y Axis (`QwtPlot.yLeft` or `QwtPlot.yRight`)
-
- .. seealso::
-
- :py:meth:`setXAxis()`, :py:meth:`setYAxis()`,
- :py:meth:`xAxis()`, :py:meth:`yAxis()`
- """
- if xAxis == QwtPlot.xBottom or xAxis == QwtPlot.xTop:
- self.__data.xAxis = xAxis
- if yAxis == QwtPlot.yLeft or yAxis == QwtPlot.yRight:
- self.__data.yAxis = yAxis
- self.itemChanged()
-
- def setAxis(self, xAxis, yAxis):
- """
- Set X and Y axis
-
- .. warning::
-
- `setAxis` has been removed in Qwt6: please use
- :py:meth:`setAxes()` instead
- """
- import warnings
-
- warnings.warn(
- "`setAxis` has been removed in Qwt6: please use `setAxes` instead",
- RuntimeWarning,
- )
- self.setAxes(xAxis, yAxis)
-
- def setXAxis(self, axis):
- """
- Set the X axis
-
- The item will painted according to the coordinates its Axes.
-
- :param int axis: X Axis (`QwtPlot.xBottom` or `QwtPlot.xTop`)
-
- .. seealso::
-
- :py:meth:`setAxes()`, :py:meth:`setYAxis()`,
- :py:meth:`xAxis()`, :py:meth:`yAxis()`
- """
- if axis in (QwtPlot.xBottom, QwtPlot.xTop):
- self.__data.xAxis = axis
- self.itemChanged()
-
- def setYAxis(self, axis):
- """
- Set the Y axis
-
- The item will painted according to the coordinates its Axes.
-
- :param int axis: Y Axis (`QwtPlot.yLeft` or `QwtPlot.yRight`)
-
- .. seealso::
-
- :py:meth:`setAxes()`, :py:meth:`setXAxis()`,
- :py:meth:`xAxis()`, :py:meth:`yAxis()`
- """
- if axis in (QwtPlot.yLeft, QwtPlot.yRight):
- self.__data.yAxis = axis
- self.itemChanged()
-
- def xAxis(self):
- """
- :return: xAxis
- """
- return self.__data.xAxis
-
- def yAxis(self):
- """
- :return: yAxis
- """
- return self.__data.yAxis
-
- def boundingRect(self):
- """
- :return: An invalid bounding rect: QRectF(1.0, 1.0, -2.0, -2.0)
-
- .. note::
-
- A width or height < 0.0 is ignored by the autoscaler
- """
- return QRectF(1.0, 1.0, -2.0, -2.0)
-
- def getCanvasMarginHint(self, xMap, yMap, canvasRect):
- """
- Calculate a hint for the canvas margin
-
- When the QwtPlotItem::Margins flag is enabled the plot item
- indicates, that it needs some margins at the borders of the canvas.
- This is f.e. used by bar charts to reserve space for displaying
- the bars.
-
- The margins are in target device coordinates ( pixels on screen )
-
- :param qwt.scale_map.QwtScaleMap xMap: Maps x-values into pixel coordinates.
- :param qwt.scale_map.QwtScaleMap yMap: Maps y-values into pixel coordinates.
- :param QRectF canvasRect: Contents rectangle of the canvas in painter coordinates
-
- .. seealso::
-
- :py:meth:`QwtPlot.getCanvasMarginsHint()`,
- :py:meth:`QwtPlot.updateCanvasMargins()`,
- """
- left = top = right = bottom = 0.0
- return left, top, right, bottom
-
- def legendData(self):
- """
- Return all information, that is needed to represent
- the item on the legend
-
- `QwtLegendData` is basically a list of QVariants that makes it
- possible to overload and reimplement legendData() to
- return almost any type of information, that is understood
- by the receiver that acts as the legend.
-
- The default implementation returns one entry with
- the title() of the item and the legendIcon().
-
- :return: Data, that is needed to represent the item on the legend
-
- .. seealso::
-
- :py:meth:`title()`, :py:meth:`legendIcon()`,
- :py:class:`qwt.legend.QwtLegend`
- """
- data = QwtLegendData()
- label = self.title()
- label.setRenderFlags(label.renderFlags() & Qt.AlignLeft)
- data.setValue(QwtLegendData.TitleRole, label)
- graphic = self.legendIcon(0, self.legendIconSize())
- if not graphic.isNull():
- data.setValue(QwtLegendData.IconRole, graphic)
- return [data]
-
- def updateLegend(self, item, data):
- """
- Update the item to changes of the legend info
-
- Plot items that want to display a legend ( not those, that want to
- be displayed on a legend ! ) will have to implement updateLegend().
-
- updateLegend() is only called when the LegendInterest interest
- is enabled. The default implementation does nothing.
-
- :param qwt.plot.QwtPlotItem item: Plot item to be displayed on a legend
- :param list data: Attributes how to display item on the legend
-
- .. note::
-
- Plot items, that want to be displayed on a legend
- need to enable the `QwtPlotItem.Legend` flag and to implement
- legendData() and legendIcon()
- """
- pass
-
- def scaleRect(self, xMap, yMap):
- """
- Calculate the bounding scale rectangle of 2 maps
-
- :param qwt.scale_map.QwtScaleMap xMap: Maps x-values into pixel coordinates.
- :param qwt.scale_map.QwtScaleMap yMap: Maps y-values into pixel coordinates.
- :return: Bounding scale rect of the scale maps, not normalized
- """
- return QRectF(xMap.s1(), yMap.s1(), xMap.sDist(), yMap.sDist())
-
- def paintRect(self, xMap, yMap):
- """
- Calculate the bounding paint rectangle of 2 maps
-
- :param qwt.scale_map.QwtScaleMap xMap: Maps x-values into pixel coordinates.
- :param qwt.scale_map.QwtScaleMap yMap: Maps y-values into pixel coordinates.
- :return: Bounding paint rectangle of the scale maps, not normalized
- """
- return QRectF(xMap.p1(), yMap.p1(), xMap.pDist(), yMap.pDist())
diff --git a/qwt/plot_canvas.py b/qwt/plot_canvas.py
deleted file mode 100644
index a72d9b8..0000000
--- a/qwt/plot_canvas.py
+++ /dev/null
@@ -1,847 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the Qwt License
-# Copyright (c) 2002 Uwe Rathmann, for the original C++ code
-# Copyright (c) 2015 Pierre Raybaut, for the Python translation/optimization
-# (see LICENSE file for more details)
-
-"""
-QwtPlotCanvas
--------------
-
-.. autoclass:: QwtPlotCanvas
- :members:
-"""
-
-from collections.abc import Sequence
-
-from qtpy.QtCore import QEvent, QObject, QPoint, QPointF, QRect, QRectF, QSize, Qt
-from qtpy.QtGui import (
- QBrush,
- QGradient,
- QImage,
- QPaintEngine,
- QPainter,
- QPainterPath,
- QPen,
- QPixmap,
- QPolygonF,
- QRegion,
- qAlpha,
-)
-from qtpy.QtWidgets import QFrame, QStyle, QStyleOption, QStyleOptionFrame
-
-from qwt.null_paintdevice import QwtNullPaintDevice
-from qwt.painter import QwtPainter
-
-
-class Border(object):
- def __init__(self):
- self.pathlist = []
- self.rectList = []
- self.clipRegion = QRegion()
-
-
-class Background(object):
- def __init__(self):
- self.path = QPainterPath()
- self.brush = QBrush()
- self.origin = QPointF()
-
-
-class QwtStyleSheetRecorder(QwtNullPaintDevice):
- def __init__(self, size):
- super(QwtStyleSheetRecorder, self).__init__()
- self.__size = size
- self.__pen = QPen()
- self.__brush = QBrush()
- self.__origin = QPointF()
- self.clipRects = []
- self.border = Border()
- self.background = Background()
-
- def updateState(self, state):
- if state.state() & QPaintEngine.DirtyPen:
- self.__pen = state.pen()
- if state.state() & QPaintEngine.DirtyBrush:
- self.__brush = state.brush()
- if state.state() & QPaintEngine.DirtyBrushOrigin:
- self.__origin = state.brushOrigin()
-
- def drawRects(self, rects, count):
- if isinstance(rects, (QRect, QRectF)):
- self.border.rectList = [QRectF(rects)]
- elif isinstance(rects, Sequence):
- self.border.rectList.extend(QRectF(rects[i]) for i in range(count))
- else:
- raise TypeError(
- "drawRects() expects a QRect, QRectF or a sequence of them, "
- f"but got {type(rects).__name__}"
- )
-
- def drawPath(self, path):
- rect = QRectF(QPointF(0.0, 0.0), self.__size)
- if path.controlPointRect().contains(rect.center()):
- self.setCornerRects(path)
- self.alignCornerRects(rect)
- self.background.path = path
- self.background.brush = self.__brush
- self.background.origin = self.__origin
- else:
- self.border.pathlist += [path]
-
- def setCornerRects(self, path):
- pos = QPointF(0.0, 0.0)
- for i in range(path.elementCount()):
- el = path.elementAt(i)
- if el.type in (QPainterPath.MoveToElement, QPainterPath.LineToElement):
- pos.setX(el.x)
- pos.setY(el.y)
- elif el.type == QPainterPath.CurveToElement:
- r = QRectF(pos, QPointF(el.x, el.y))
- self.clipRects += [r.normalized()]
- pos.setX(el.x)
- pos.setY(el.y)
- elif el.type == QPainterPath.CurveToDataElement:
- if self.clipRects:
- r = self.clipRects[-1]
- r.setCoords(
- min([r.left(), el.x]),
- min([r.top(), el.y]),
- max([r.right(), el.x]),
- max([r.bottom(), el.y]),
- )
- self.clipRects[-1] = r.normalized()
-
- def sizeMetrics(self):
- return self.__size
-
- def alignCornerRects(self, rect):
- for r in self.clipRects:
- if r.center().x() < rect.center().x():
- r.setLeft(rect.left())
- else:
- r.setRight(rect.right())
- if r.center().y() < rect.center().y():
- r.setTop(rect.top())
- else:
- r.setBottom(rect.bottom())
-
-
-def qwtDrawBackground(painter, canvas):
- painter.save()
- borderClip = canvas.borderPath(canvas.rect())
- if not borderClip.isEmpty():
- painter.setClipPath(borderClip, Qt.IntersectClip)
- brush = canvas.palette().brush(canvas.backgroundRole())
- if brush.style() == Qt.TexturePattern:
- pm = QPixmap(canvas.size())
- QwtPainter.fillPixmap(canvas, pm)
- painter.drawPixmap(0, 0, pm)
- elif brush.gradient():
- rects = []
- if brush.gradient().coordinateMode() == QGradient.ObjectBoundingMode:
- rects += [canvas.rect()]
- else:
- rects += [painter.clipRegion().boundingRect()]
- useRaster = False
- if painter.paintEngine().type() == QPaintEngine.X11:
- useRaster = True
- if useRaster:
- format_ = QImage.Format_RGB32
- stops = brush.gradient().stops()
- for stop in stops:
- if stop.second.alpha() != 255:
- format_ = QImage.Format_ARGB32
- break
- image = QImage(canvas.size(), format_)
- pntr = QPainter(image)
- pntr.setPen(Qt.NoPen)
- pntr.setBrush(brush)
- for rect in rects:
- pntr.drawRect(rect)
- pntr.end()
- painter.drawImage(0, 0, image)
- else:
- painter.setPen(Qt.NoPen)
- painter.setBrush(brush)
- for rect in rects:
- painter.drawRect(rect)
- else:
- painter.setPen(Qt.NoPen)
- painter.setBrush(brush)
- painter.drawRect(painter.clipRegion().boundingRect())
-
- painter.restore()
-
-
-def qwtRevertPath(path):
- if path.elementCount() == 4:
- el0 = path.elementAt(0)
- el3 = path.elementAt(3)
- path.setElementPositionAt(0, el3.x, el3.y)
- path.setElementPositionAt(3, el0.x, el0.y)
-
-
-def qwtCombinePathList(rect, pathList):
- if not pathList:
- return QPainterPath()
-
- ordered = [None] * 8
- for subPath in pathList:
- index = -1
- br = subPath.controlPointRect()
- if br.center().x() < rect.center().x():
- if br.center().y() < rect.center().y():
- if abs(br.top() - rect.top()) < abs(br.left() - rect.left()):
- index = 1
- else:
- index = 0
- else:
- if abs(br.bottom() - rect.bottom) < abs(br.left() - rect.left()):
- index = 6
- else:
- index = 7
- if subPath.currentPosition().y() > br.center().y():
- qwtRevertPath(subPath)
- else:
- if br.center().y() < rect.center().y():
- if abs(br.top() - rect.top()) < abs(br.right() - rect.right()):
- index = 2
- else:
- index = 3
- else:
- if abs(br.bottom() - rect.bottom()) < abs(br.right() - rect.right()):
- index = 5
- else:
- index = 4
- if subPath.currentPosition().y() < br.center().y():
- qwtRevertPath(subPath)
- ordered[index] = subPath
- for i in range(4):
- if ordered[2 * i].isEmpty() != ordered[2 * i + 1].isEmpty():
- return QPainterPath()
- corners = QPolygonF(rect)
- path = QPainterPath()
- for i in range(4):
- if ordered[2 * i].isEmpty():
- path.lineTo(corners[i])
- else:
- path.connectPath(ordered[2 * i])
- path.connectPath(ordered[2 * i + 1])
- path.closeSubpath()
- return path
-
-
-def qwtDrawStyledBackground(w, painter):
- opt = QStyleOption()
- opt.initFrom(w)
- w.style().drawPrimitive(QStyle.PE_Widget, opt, painter, w)
-
-
-def qwtBackgroundWidget(w):
- if w.parentWidget() is None:
- return w
- if w.autoFillBackground():
- brush = w.palette().brush(w.backgroundRole())
- if brush.color().alpha() > 0:
- return w
- if w.testAttribute(Qt.WA_StyledBackground):
- image = QImage(1, 1, QImage.Format_ARGB32)
- image.fill(Qt.transparent)
- painter = QPainter(image)
- painter.translate(-w.rect().center())
- qwtDrawStyledBackground(w, painter)
- painter.end()
- if qAlpha(image.pixel(0, 0)) != 0:
- return w
- return qwtBackgroundWidget(w.parentWidget())
-
-
-def qwtFillBackground(*args):
- if len(args) == 2:
- painter, canvas = args
-
- rects = []
- if canvas.testAttribute(Qt.WA_StyledBackground):
- recorder = QwtStyleSheetRecorder(canvas.size())
- p = QPainter(recorder)
- qwtDrawStyledBackground(canvas, p)
- p.end()
- if recorder.background.brush.isOpaque():
- rects = recorder.clipRects
- else:
- rects += [canvas.rect()]
- else:
- r = canvas.rect()
- radius = canvas.borderRadius()
- if radius > 0.0:
- sz = QSize(radius, radius)
- rects += [
- QRect(r.topLeft(), sz),
- QRect(r.topRight() - QPoint(radius, 0), sz),
- QRect(r.bottomRight() - QPoint(radius, radius), sz),
- QRect(r.bottomLeft() - QPoint(0, radius), sz),
- ]
-
- qwtFillBackground(painter, canvas, rects)
-
- elif len(args) == 3:
- painter, widget, fillRects = args
-
- if not fillRects:
- return
- if painter.hasClipping():
- clipRegion = painter.transform().map(painter.clipRegion())
- else:
- clipRegion = widget.contentsRect()
- bgWidget = qwtBackgroundWidget(widget.parentWidget())
- for rect in fillRects:
- if clipRegion.intersects(rect):
- pm = QPixmap(rect.size())
- QwtPainter.fillPixmap(
- bgWidget, pm, widget.mapTo(bgWidget, rect.topLeft())
- )
- painter.drawPixmap(rect, pm)
-
- else:
- raise TypeError(
- "%s() takes 2 or 3 argument(s) (%s given)"
- % ("qwtFillBackground", len(args))
- )
-
-
-class StyleSheetBackground(object):
- def __init__(self):
- self.brush = QBrush()
- self.origin = QPointF()
-
-
-class StyleSheet(object):
- def __init__(self):
- self.hasBorder = False
- self.borderPath = QPainterPath()
- self.cornerRects = []
- self.background = StyleSheetBackground()
-
-
-class QwtPlotCanvas_PrivateData(QObject):
- def __init__(self):
- QObject.__init__(self)
-
- self.focusIndicator = QwtPlotCanvas.NoFocusIndicator
- self.borderRadius = 0
- self.paintAttributes = 0
- self.backingStore = None
- self.styleSheet = StyleSheet()
- self.styleSheet.hasBorder = False
-
-
-class QwtPlotCanvas(QFrame):
- """
- Canvas of a QwtPlot.
-
- Canvas is the widget where all plot items are displayed
-
- .. seealso::
-
- :py:meth:`qwt.plot.QwtPlot.setCanvas()`
-
- Paint attributes:
-
- * `QwtPlotCanvas.BackingStore`:
-
- Paint double buffered reusing the content of the pixmap buffer
- when possible.
-
- Using a backing store might improve the performance significantly,
- when working with widget overlays (like rubber bands).
- Disabling the cache might improve the performance for
- incremental paints
- (using :py:class:`qwt.plot_directpainter.QwtPlotDirectPainter`).
-
- * `QwtPlotCanvas.Opaque`:
-
- Try to fill the complete contents rectangle of the plot canvas
-
- When using styled backgrounds Qt assumes, that the canvas doesn't
- fill its area completely (f.e because of rounded borders) and
- fills the area below the canvas. When this is done with gradients
- it might result in a serious performance bottleneck - depending on
- the size.
-
- When the Opaque attribute is enabled the canvas tries to
- identify the gaps with some heuristics and to fill those only.
-
- .. warning::
-
- Will not work for semitransparent backgrounds
-
- * `QwtPlotCanvas.HackStyledBackground`:
-
- Try to improve painting of styled backgrounds
-
- `QwtPlotCanvas` supports the box model attributes for
- customizing the layout with style sheets. Unfortunately
- the design of Qt style sheets has no concept how to
- handle backgrounds with rounded corners - beside of padding.
-
- When HackStyledBackground is enabled the plot canvas tries
- to separate the background from the background border
- by reverse engineering to paint the background before and
- the border after the plot items. In this order the border
- gets perfectly antialiased and you can avoid some pixel
- artifacts in the corners.
-
- * `QwtPlotCanvas.ImmediatePaint`:
-
- When ImmediatePaint is set replot() calls repaint()
- instead of update().
-
- .. seealso::
-
- :py:meth:`replot()`, :py:meth:`QWidget.repaint()`,
- :py:meth:`QWidget.update()`
-
- Focus indicators:
-
- * `QwtPlotCanvas.NoFocusIndicator`:
-
- Don't paint a focus indicator
-
- * `QwtPlotCanvas.CanvasFocusIndicator`:
-
- The focus is related to the complete canvas.
- Paint the focus indicator using paintFocus()
-
- * `QwtPlotCanvas.ItemFocusIndicator`:
-
- The focus is related to an item (curve, point, ...) on
- the canvas. It is up to the application to display a
- focus indication using f.e. highlighting.
-
- .. py:class:: QwtPlotCanvas([plot=None])
-
- Constructor
-
- :param qwt.plot.QwtPlot plot: Parent plot widget
-
- .. seealso::
-
- :py:meth:`qwt.plot.QwtPlot.setCanvas()`
- """
-
- # enum PaintAttribute
- BackingStore = 1
- Opaque = 2
- HackStyledBackground = 4
- ImmediatePaint = 8
-
- # enum FocusIndicator
- NoFocusIndicator, CanvasFocusIndicator, ItemFocusIndicator = list(range(3))
-
- def __init__(self, plot=None):
- super(QwtPlotCanvas, self).__init__(plot)
- self.__plot = plot
- self.setFrameStyle(QFrame.Panel | QFrame.Sunken)
- self.setLineWidth(2)
- self.__data = QwtPlotCanvas_PrivateData()
- self.setCursor(Qt.CrossCursor)
- self.setAutoFillBackground(True)
- self.setPaintAttribute(QwtPlotCanvas.BackingStore, False)
- self.setPaintAttribute(QwtPlotCanvas.Opaque, True)
- self.setPaintAttribute(QwtPlotCanvas.HackStyledBackground, True)
-
- def plot(self):
- """
- :return: Parent plot widget
- """
- return self.__plot
-
- def setPaintAttribute(self, attribute, on=True):
- """
- Changing the paint attributes
-
- Paint attributes:
-
- * `QwtPlotCanvas.BackingStore`
- * `QwtPlotCanvas.Opaque`
- * `QwtPlotCanvas.HackStyledBackground`
- * `QwtPlotCanvas.ImmediatePaint`
-
- :param int attribute: Paint attribute
- :param bool on: On/Off
-
- .. seealso::
-
- :py:meth:`testPaintAttribute()`, :py:meth:`backingStore()`
- """
- if bool(self.__data.paintAttributes & attribute) == on:
- return
- if on:
- self.__data.paintAttributes |= attribute
- else:
- self.__data.paintAttributes &= ~attribute
- if attribute == self.BackingStore:
- if on:
- if self.__data.backingStore is None:
- self.__data.backingStore = QPixmap()
- if self.isVisible():
- self.__data.backingStore = self.grab(self.rect())
- else:
- self.__data.backingStore = None
- elif attribute == self.Opaque:
- if on:
- self.setAttribute(Qt.WA_OpaquePaintEvent, True)
- elif attribute in (self.HackStyledBackground, self.ImmediatePaint):
- pass
-
- def testPaintAttribute(self, attribute):
- """
- Test whether a paint attribute is enabled
-
- :param int attribute: Paint attribute
- :return: True, when attribute is enabled
-
- .. seealso::
-
- :py:meth:`setPaintAttribute()`
- """
- return self.__data.paintAttributes & attribute
-
- def backingStore(self):
- """
- :return: Backing store, might be None
- """
- return self.__data.backingStore
-
- def invalidateBackingStore(self):
- """Invalidate the internal backing store"""
- if self.__data.backingStore:
- self.__data.backingStore = QPixmap()
-
- def setFocusIndicator(self, focusIndicator):
- """
- Set the focus indicator
-
- Focus indicators:
-
- * `QwtPlotCanvas.NoFocusIndicator`
- * `QwtPlotCanvas.CanvasFocusIndicator`
- * `QwtPlotCanvas.ItemFocusIndicator`
-
- :param int focusIndicator: Focus indicator
-
- .. seealso::
-
- :py:meth:`focusIndicator()`
- """
- self.__data.focusIndicator = focusIndicator
-
- def focusIndicator(self):
- """
- :return: Focus indicator
-
- .. seealso::
-
- :py:meth:`setFocusIndicator()`
- """
- return self.__data.focusIndicator
-
- def setBorderRadius(self, radius):
- """
- Set the radius for the corners of the border frame
-
- :param float radius: Radius of a rounded corner
-
- .. seealso::
-
- :py:meth:`borderRadius()`
- """
- self.__data.borderRadius = max([0.0, radius])
-
- def borderRadius(self):
- """
- :return: Radius for the corners of the border frame
-
- .. seealso::
-
- :py:meth:`setBorderRadius()`
- """
- return self.__data.borderRadius
-
- def event(self, event):
- if event.type() == QEvent.PolishRequest:
- if self.testPaintAttribute(self.Opaque):
- self.setAttribute(Qt.WA_OpaquePaintEvent, True)
- if event.type() in (QEvent.PolishRequest, QEvent.StyleChange):
- self.updateStyleSheetInfo()
- return QFrame.event(self, event)
-
- def paintEvent(self, event):
- painter = QPainter(self)
- painter.setClipRegion(event.region())
- if (
- self.testPaintAttribute(self.BackingStore)
- and self.__data.backingStore is not None
- and not self.__data.backingStore.isNull()
- ):
- bs = self.__data.backingStore
- pixelRatio = bs.devicePixelRatio()
- if bs.size() != self.size() * pixelRatio:
- bs = QwtPainter.backingStore(self, self.size())
- if self.testAttribute(Qt.WA_StyledBackground):
- p = QPainter(bs)
- qwtFillBackground(p, self)
- self.drawCanvas(p, True)
- else:
- p = QPainter()
- if self.__data.borderRadius <= 0.0:
- # print('**DEBUG: QwtPlotCanvas.paintEvent')
- QwtPainter.fillPixmap(self, bs)
- p.begin(bs)
- self.drawCanvas(p, False)
- else:
- p.begin(bs)
- qwtFillBackground(p, self)
- self.drawCanvas(p, True)
- if self.frameWidth() > 0:
- self.drawBorder(p)
- p.end()
- painter.drawPixmap(0, 0, self.__data.backingStore)
- else:
- if self.testAttribute(Qt.WA_StyledBackground):
- if self.testAttribute(Qt.WA_OpaquePaintEvent):
- qwtFillBackground(painter, self)
- self.drawCanvas(painter, True)
- else:
- self.drawCanvas(painter, False)
- else:
- if self.testAttribute(Qt.WA_OpaquePaintEvent):
- if self.autoFillBackground():
- qwtFillBackground(painter, self)
- qwtDrawBackground(painter, self)
- else:
- if self.borderRadius() > 0.0:
- clipPath = QPainterPath()
- clipPath.addRect(self.rect())
- clipPath = clipPath.subtracted(self.borderPath(self.rect()))
- painter.save()
- painter.setClipPath(clipPath, Qt.IntersectClip)
- qwtFillBackground(painter, self)
- qwtDrawBackground(painter, self)
- painter.restore()
- self.drawCanvas(painter, False)
- if self.frameWidth() > 0:
- self.drawBorder(painter)
- if self.hasFocus() and self.focusIndicator() == self.CanvasFocusIndicator:
- self.drawFocusIndicator(painter)
-
- def drawCanvas(self, painter, withBackground):
- hackStyledBackground = False
- if (
- withBackground
- and self.testAttribute(Qt.WA_StyledBackground)
- and self.testPaintAttribute(self.HackStyledBackground)
- ):
- # Antialiasing rounded borders is done by
- # inserting pixels with colors between the
- # border color and the color on the canvas,
- # When the border is painted before the plot items
- # these colors are interpolated for the canvas
- # and the plot items need to be clipped excluding
- # the anialiased pixels. In situations, where
- # the plot items fill the area at the rounded
- # borders this is noticeable.
- # The only way to avoid these annoying "artefacts"
- # is to paint the border on top of the plot items.
- if (
- self.__data.styleSheet.hasBorder
- and not self.__data.styleSheet.borderPath.isEmpty()
- ):
- # We have a border with at least one rounded corner
- hackStyledBackground = True
- if withBackground:
- painter.save()
- if self.testAttribute(Qt.WA_StyledBackground):
- if hackStyledBackground:
- # paint background without border
- painter.setPen(Qt.NoPen)
- painter.setBrush(self.__data.styleSheet.background.brush)
- painter.setBrushOrigin(self.__data.styleSheet.background.origin)
- painter.setClipPath(self.__data.styleSheet.borderPath)
- painter.drawRect(self.contentsRect())
- else:
- qwtDrawStyledBackground(self, painter)
- elif self.autoFillBackground():
- painter.setPen(Qt.NoPen)
- painter.setBrush(self.palette().brush(self.backgroundRole()))
- if self.__data.borderRadius > 0.0 and self.rect() == self.frameRect():
- if self.frameWidth() > 0:
- painter.setClipPath(self.borderPath(self.rect()))
- painter.drawRect(self.rect())
- else:
- painter.setRenderHint(QPainter.Antialiasing, True)
- painter.drawPath(self.borderPath(self.rect()))
- else:
- painter.drawRect(self.rect())
- painter.restore()
- painter.save()
- if not self.__data.styleSheet.borderPath.isEmpty():
- painter.setClipPath(self.__data.styleSheet.borderPath, Qt.IntersectClip)
- else:
- if self.__data.borderRadius > 0.0:
- painter.setClipPath(self.borderPath(self.frameRect()), Qt.IntersectClip)
- else:
- # print('**DEBUG: QwtPlotCanvas.drawCanvas')
- painter.setClipRect(self.contentsRect(), Qt.IntersectClip)
- self.plot().drawCanvas(painter)
- painter.restore()
- if withBackground and hackStyledBackground:
- # Now paint the border on top
- opt = QStyleOptionFrame()
- opt.initFrom(self)
- self.style().drawPrimitive(QStyle.PE_Frame, opt, painter, self)
-
- def drawBorder(self, painter):
- """
- Draw the border of the plot canvas
-
- :param QPainter painter: Painter
-
- .. seealso::
-
- :py:meth:`setBorderRadius()`
- """
- if self.__data.borderRadius > 0:
- if self.frameWidth() > 0:
- QwtPainter.drawRoundedFrame(
- painter,
- QRectF(self.frameRect()),
- self.__data.borderRadius,
- self.__data.borderRadius,
- self.palette(),
- self.frameWidth(),
- self.frameStyle(),
- )
- else:
- opt = QStyleOptionFrame()
- opt.initFrom(self)
- try:
- shape_mask = QFrame.Shape_Mask.value
- shadow_mask = QFrame.Shadow_Mask.value
- except AttributeError:
- shape_mask = QFrame.Shape_Mask
- shadow_mask = QFrame.Shadow_Mask
- frameShape = self.frameStyle() & shape_mask
- frameShadow = self.frameStyle() & shadow_mask
- opt.frameShape = QFrame.Shape(int(opt.frameShape) | frameShape)
- if frameShape in (
- QFrame.Box,
- QFrame.HLine,
- QFrame.VLine,
- QFrame.StyledPanel,
- QFrame.Panel,
- ):
- opt.lineWidth = self.lineWidth()
- opt.midLineWidth = self.midLineWidth()
- else:
- opt.lineWidth = self.frameWidth()
- if frameShadow == QFrame.Sunken:
- opt.state |= QStyle.State_Sunken
- elif frameShadow == QFrame.Raised:
- opt.state |= QStyle.State_Raised
- self.style().drawControl(QStyle.CE_ShapedFrame, opt, painter, self)
-
- def resizeEvent(self, event):
- QFrame.resizeEvent(self, event)
- self.updateStyleSheetInfo()
-
- def drawFocusIndicator(self, painter):
- """
- Draw the focus indication
-
- :param QPainter painter: Painter
- """
- margin = 1
- focusRect = self.contentsRect()
- focusRect.setRect(
- focusRect.x() + margin,
- focusRect.y() + margin,
- focusRect.width() - 2 * margin,
- focusRect.height() - 2 * margin,
- )
- QwtPainter.drawFocusRect(painter, self, focusRect)
-
- def replot(self):
- """
- Invalidate the paint cache and repaint the canvas
- """
- self.invalidateBackingStore()
- if self.testPaintAttribute(self.ImmediatePaint):
- self.repaint(self.contentsRect())
- else:
- self.update(self.contentsRect())
-
- def invalidatePaintCache(self):
- import warnings
-
- warnings.warn(
- "`invalidatePaintCache` has been removed: please use `replot` instead",
- RuntimeWarning,
- )
- self.replot()
-
- def updateStyleSheetInfo(self):
- """
- Update the cached information about the current style sheet
- """
- if not self.testAttribute(Qt.WA_StyledBackground):
- return
- recorder = QwtStyleSheetRecorder(self.size())
- painter = QPainter(recorder)
- opt = QStyleOption()
- opt.initFrom(self)
- self.style().drawPrimitive(QStyle.PE_Widget, opt, painter, self)
- painter.end()
- self.__data.styleSheet.hasBorder = len(recorder.border.rectList) > 0
- self.__data.styleSheet.cornerRects = recorder.clipRects
- if recorder.background.path.isEmpty():
- if self.__data.styleSheet.hasBorder:
- self.__data.styleSheet.borderPath = qwtCombinePathList(
- self.rect(), recorder.border.pathlist
- )
- else:
- self.__data.styleSheet.borderPath = recorder.background.path
- self.__data.styleSheet.background.brush = recorder.background.brush
- self.__data.styleSheet.background.origin = recorder.background.origin
-
- def borderPath(self, rect):
- """
- Calculate the painter path for a styled or rounded border
-
- When the canvas has no styled background or rounded borders
- the painter path is empty.
-
- :param QRect rect: Bounding rectangle of the canvas
- :return: Painter path, that can be used for clipping
- """
- if self.testAttribute(Qt.WA_StyledBackground):
- recorder = QwtStyleSheetRecorder(rect.size())
- painter = QPainter(recorder)
- opt = QStyleOption()
- opt.initFrom(self)
- opt.rect = rect
- self.style().drawPrimitive(QStyle.PE_Widget, opt, painter, self)
- painter.end()
- if not recorder.background.path.isEmpty():
- return recorder.background.path
- if len(recorder.border.rectList) > 0:
- return qwtCombinePathList(rect, recorder.border.pathlist)
- elif self.__data.borderRadius > 0.0:
- fw2 = self.frameWidth() * 0.5
- r = QRectF(rect).adjusted(fw2, fw2, -fw2, -fw2)
- path = QPainterPath()
- path.addRoundedRect(r, self.__data.borderRadius, self.__data.borderRadius)
- return path
- return QPainterPath()
diff --git a/qwt/plot_curve.py b/qwt/plot_curve.py
deleted file mode 100644
index 6b4105e..0000000
--- a/qwt/plot_curve.py
+++ /dev/null
@@ -1,1054 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the Qwt License
-# Copyright (c) 2002 Uwe Rathmann, for the original C++ code
-# Copyright (c) 2015 Pierre Raybaut, for the Python translation/optimization
-# (see LICENSE file for more details)
-
-"""
-QwtPlotCurve
-------------
-
-.. autoclass:: QwtPlotCurve
- :members:
-"""
-
-import math
-import os
-
-import numpy as np
-from qtpy.QtCore import QLineF, QPointF, QRectF, QSize, Qt
-from qtpy.QtGui import QBrush, QColor, QPainter, QPen, QPolygonF
-
-from qwt._math import qwtSqr
-from qwt.graphic import QwtGraphic
-from qwt.plot import QwtPlot, QwtPlotItem, QwtPlotItem_PrivateData
-from qwt.plot_directpainter import QwtPlotDirectPainter
-from qwt.plot_series import (
- QwtPlotSeriesItem,
- QwtPointArrayData,
- QwtSeriesData,
- QwtSeriesStore,
-)
-from qwt.qthelpers import qcolor_from_str
-from qwt.symbol import QwtSymbol
-from qwt.text import QwtText
-
-QT_API = os.environ["QT_API"]
-
-if QT_API == "pyside6":
- import ctypes
-
- import shiboken6 as shiboken
-
-
-def qwtUpdateLegendIconSize(curve):
- if curve.symbol() and curve.testLegendAttribute(QwtPlotCurve.LegendShowSymbol):
- sz = curve.symbol().boundingRect().size()
- sz += QSize(2, 2)
- if curve.testLegendAttribute(QwtPlotCurve.LegendShowLine):
- w = math.ceil(1.5 * sz.width())
- if w % 2:
- w += 1
- sz.setWidth(max([8, w]))
- curve.setLegendIconSize(sz)
-
-
-def qwtVerifyRange(size, i1, i2):
- if size < 1:
- return 0
- i1 = max([0, min([i1, size - 1])])
- i2 = max([0, min([i2, size - 1])])
- if i1 > i2:
- i1, i2 = i2, i1
- return i2 - i1 + 1
-
-
-def array2d_to_qpolygonf(xdata, ydata):
- """
- Utility function to convert two 1D-NumPy arrays representing curve data
- (X-axis, Y-axis data) into a single polyline (QtGui.PolygonF object).
- This feature is compatible with PyQt5 and PySide6 (requires QtPy).
-
- License/copyright: MIT License © Pierre Raybaut 2020-2021.
-
- :param numpy.ndarray xdata: 1D-NumPy array
- :param numpy.ndarray ydata: 1D-NumPy array
- :return: Polyline
- :rtype: QtGui.QPolygonF
- """
- if not (xdata.size == ydata.size == xdata.shape[0] == ydata.shape[0]):
- raise ValueError("Arguments must be 1D NumPy arrays with same size")
- size = xdata.size
- if QT_API.startswith("pyside"): # PySide (obviously...)
- polyline = QPolygonF()
- polyline.resize(size)
- address = shiboken.getCppPointer(polyline.data())[0]
- buffer = (ctypes.c_double * 2 * size).from_address(address)
- else: # PyQt
- if QT_API == "pyqt6":
- polyline = QPolygonF([QPointF(0, 0)] * size)
- else:
- polyline = QPolygonF(size)
- buffer = polyline.data()
- buffer.setsize(16 * size) # 16 bytes per point: 8 bytes per X,Y value (float64)
- memory = np.frombuffer(buffer, np.float64)
- memory[: (size - 1) * 2 + 1 : 2] = np.asarray(xdata, dtype=np.float64)
- memory[1 : (size - 1) * 2 + 2 : 2] = np.asarray(ydata, dtype=np.float64)
- return polyline
-
-
-def series_to_polyline(xMap, yMap, series, from_, to):
- """
- Convert series data to QPolygon(F) polyline
- """
- xdata = xMap.transform(series.xData()[from_ : to + 1])
- ydata = yMap.transform(series.yData()[from_ : to + 1])
- return array2d_to_qpolygonf(xdata, ydata)
-
-
-class QwtPlotCurve_PrivateData(QwtPlotItem_PrivateData):
- def __init__(self):
- QwtPlotItem_PrivateData.__init__(self)
- self.style = QwtPlotCurve.Lines
- self.baseline = 0.0
- self.symbol = None
- self.attributes = 0
- self.legendAttributes = QwtPlotCurve.LegendShowLine
- self.pen = QPen(Qt.black)
- self.brush = QBrush()
-
-
-class QwtPlotCurve(QwtPlotSeriesItem, QwtSeriesStore):
- """
- A plot item, that represents a series of points
-
- A curve is the representation of a series of points in the x-y plane.
- It supports different display styles and symbols.
-
- .. seealso::
-
- :py:class:`qwt.symbol.QwtSymbol()`,
- :py:class:`qwt.scale_map.QwtScaleMap()`
-
- Curve styles:
-
- * `QwtPlotCurve.NoCurve`:
-
- Don't draw a curve. Note: This doesn't affect the symbols.
-
- * `QwtPlotCurve.Lines`:
-
- Connect the points with straight lines.
-
- * `QwtPlotCurve.Sticks`:
-
- Draw vertical or horizontal sticks ( depending on the
- orientation() ) from a baseline which is defined by setBaseline().
-
- * `QwtPlotCurve.Steps`:
-
- Connect the points with a step function. The step function
- is drawn from the left to the right or vice versa,
- depending on the QwtPlotCurve::Inverted attribute.
-
- * `QwtPlotCurve.Dots`:
-
- Draw dots at the locations of the data points. Note:
- This is different from a dotted line (see setPen()), and faster
- as a curve in QwtPlotCurve::NoStyle style and a symbol
- painting a point.
-
- * `QwtPlotCurve.UserCurve`:
-
- Styles >= QwtPlotCurve.UserCurve are reserved for derived
- classes of QwtPlotCurve that overload drawCurve() with
- additional application specific curve types.
-
- Curve attributes:
-
- * `QwtPlotCurve.Inverted`:
-
- For `QwtPlotCurve.Steps` only.
- Draws a step function from the right to the left.
-
- Legend attributes:
-
- * `QwtPlotCurve.LegendNoAttribute`:
-
- `QwtPlotCurve` tries to find a color representing the curve
- and paints a rectangle with it.
-
- * `QwtPlotCurve.LegendShowLine`:
-
- If the style() is not `QwtPlotCurve.NoCurve` a line
- is painted with the curve pen().
-
- * `QwtPlotCurve.LegendShowSymbol`:
-
- If the curve has a valid symbol it is painted.
-
- * `QwtPlotCurve.LegendShowBrush`:
-
- If the curve has a brush a rectangle filled with the
- curve brush() is painted.
-
-
- .. py:class:: QwtPlotCurve([title=None])
-
- Constructor
-
- :param title: Curve title
- :type title: qwt.text.QwtText or str or None
- """
-
- # enum CurveStyle
- NoCurve = -1
- Lines, Sticks, Steps, Dots = list(range(4))
- UserCurve = 100
-
- # enum CurveAttribute
- Inverted = 0x01
-
- # enum LegendAttribute
- LegendNoAttribute = 0x00
- LegendShowLine = 0x01
- LegendShowSymbol = 0x02
- LegendShowBrush = 0x04
-
- def __init__(self, title=None):
- if title is None:
- title = QwtText("")
- if not isinstance(title, QwtText):
- title = QwtText(title)
- self.__data = None
- QwtPlotSeriesItem.__init__(self, title)
- QwtSeriesStore.__init__(self)
- self.init()
-
- @classmethod
- def make(
- cls,
- xdata=None,
- ydata=None,
- title=None,
- plot=None,
- z=None,
- x_axis=None,
- y_axis=None,
- style=None,
- symbol=None,
- linecolor=None,
- linewidth=None,
- linestyle=None,
- antialiased=False,
- size=None,
- finite=None,
- ):
- """
- Create and setup a new `QwtPlotCurve` object (convenience function).
-
- :param xdata: List/array of x values
- :param ydata: List/array of y values
- :param title: Curve title
- :type title: qwt.text.QwtText or str or None
- :param plot: Plot to attach the curve to
- :type plot: qwt.plot.QwtPlot or None
- :param z: Z-value
- :type z: float or None
- :param x_axis: curve X-axis (default: QwtPlot.yLeft)
- :type x_axis: int or None
- :param y_axis: curve Y-axis (default: QwtPlot.xBottom)
- :type y_axis: int or None
- :param style: curve style (`QwtPlotCurve.NoCurve`, `QwtPlotCurve.Lines`, `QwtPlotCurve.Sticks`, `QwtPlotCurve.Steps`, `QwtPlotCurve.Dots`, `QwtPlotCurve.UserCurve`)
- :type style: int or None
- :param symbol: curve symbol
- :type symbol: qwt.symbol.QwtSymbol or None
- :param linecolor: curve line color
- :type linecolor: QColor or str or None
- :param linewidth: curve line width
- :type linewidth: float or None
- :param linestyle: curve pen style
- :type linestyle: Qt.PenStyle or None
- :param bool antialiased: if True, enable antialiasing rendering
- :param size: size of xData and yData
- :type size: int or None
- :param bool finite: if True, keep only finite array elements (remove all infinity and not a number values), otherwise do not filter array elements
-
- .. seealso::
-
- :py:meth:`setData()`, :py:meth:`setPen()`, :py:meth:`attach()`
- """
- item = cls(title)
- if z is not None:
- item.setZ(z)
- if xdata is not None or ydata is not None:
- if xdata is None:
- raise ValueError("Missing xdata parameter")
- if ydata is None:
- raise ValueError("Missing ydata parameter")
- item.setData(xdata, ydata, size=size, finite=finite)
- x_axis = QwtPlot.xBottom if x_axis is None else x_axis
- y_axis = QwtPlot.yLeft if y_axis is None else y_axis
- item.setAxes(x_axis, y_axis)
- if style is not None:
- item.setStyle(style)
- if symbol is not None:
- item.setSymbol(symbol)
- linecolor = qcolor_from_str(linecolor, Qt.black)
- linewidth = 1.0 if linewidth is None else linewidth
- linestyle = Qt.SolidLine if linestyle is None else linestyle
- item.setPen(QPen(linecolor, linewidth, linestyle))
- item.setRenderHint(cls.RenderAntialiased, antialiased)
- if plot is not None:
- item.attach(plot)
- return item
-
- def init(self):
- """Initialize internal members"""
- self.__data = QwtPlotCurve_PrivateData()
- self.setItemAttribute(QwtPlotItem.Legend)
- self.setItemAttribute(QwtPlotItem.AutoScale)
- self.setData(QwtPointArrayData())
- self.setZ(20.0)
-
- def rtti(self):
- """:return: `QwtPlotItem.Rtti_PlotCurve`"""
- return QwtPlotItem.Rtti_PlotCurve
-
- def setLegendAttribute(self, attribute, on=True):
- """
- Specify an attribute how to draw the legend icon
-
- Legend attributes:
-
- * `QwtPlotCurve.LegendNoAttribute`
- * `QwtPlotCurve.LegendShowLine`
- * `QwtPlotCurve.LegendShowSymbol`
- * `QwtPlotCurve.LegendShowBrush`
-
- :param int attribute: Legend attribute
- :param bool on: On/Off
-
- .. seealso::
-
- :py:meth:`testLegendAttribute()`, :py:meth:`legendIcon()`
- """
- if on != self.testLegendAttribute(attribute):
- if on:
- self.__data.legendAttributes |= attribute
- else:
- self.__data.legendAttributes &= ~attribute
- qwtUpdateLegendIconSize(self)
- self.legendChanged()
-
- def testLegendAttribute(self, attribute):
- """
- :param int attribute: Legend attribute
- :return: True, when attribute is enabled
-
- .. seealso::
-
- :py:meth:`setLegendAttribute()`
- """
- return self.__data.legendAttributes & attribute
-
- def setStyle(self, style):
- """
- Set the curve's drawing style
-
- Valid curve styles:
-
- * `QwtPlotCurve.NoCurve`
- * `QwtPlotCurve.Lines`
- * `QwtPlotCurve.Sticks`
- * `QwtPlotCurve.Steps`
- * `QwtPlotCurve.Dots`
- * `QwtPlotCurve.UserCurve`
-
- :param int style: Curve style
-
- .. seealso::
-
- :py:meth:`style()`
- """
- if style != self.__data.style:
- self.__data.style = style
- self.legendChanged()
- self.itemChanged()
-
- def style(self):
- """
- :return: Style of the curve
-
- .. seealso::
-
- :py:meth:`setStyle()`
- """
- return self.__data.style
-
- def setSymbol(self, symbol):
- """
- Assign a symbol
-
- The curve will take the ownership of the symbol, hence the previously
- set symbol will be delete by setting a new one. If symbol is None no
- symbol will be drawn.
-
- :param qwt.symbol.QwtSymbol symbol: Symbol
-
- .. seealso::
-
- :py:meth:`symbol()`
- """
- if symbol != self.__data.symbol:
- self.__data.symbol = symbol
- qwtUpdateLegendIconSize(self)
- self.legendChanged()
- self.itemChanged()
-
- def symbol(self):
- """
- :return: Current symbol or None, when no symbol has been assigned
-
- .. seealso::
-
- :py:meth:`setSymbol()`
- """
- return self.__data.symbol
-
- def setPen(self, *args):
- """
- Build and/or assign a pen, depending on the arguments.
-
- .. py:method:: setPen(color, width, style)
- :noindex:
-
- Build and assign a pen
-
- In Qt5 the default pen width is 1.0 ( 0.0 in Qt4 ) what makes it
- non cosmetic (see `QPen.isCosmetic()`). This method signature has
- been introduced to hide this incompatibility.
-
- :param QColor color: Pen color
- :param float width: Pen width
- :param Qt.PenStyle style: Pen style
-
- .. py:method:: setPen(pen)
- :noindex:
-
- Assign a pen
-
- :param QPen pen: New pen
-
- .. seealso::
-
- :py:meth:`pen()`, :py:meth:`brush()`
- """
- if len(args) == 3:
- color, width, style = args
- pen = QPen(color, width, style)
- elif len(args) == 1:
- (pen,) = args
- else:
- raise TypeError(
- "%s().setPen() takes 1 or 3 argument(s) (%s given)"
- % (self.__class__.__name__, len(args))
- )
- if pen != self.__data.pen:
- if isinstance(pen, QColor):
- pen = QPen(pen)
- else:
- assert isinstance(pen, QPen)
- self.__data.pen = pen
- self.legendChanged()
- self.itemChanged()
-
- def pen(self):
- """
- :return: Pen used to draw the lines
-
- .. seealso::
-
- :py:meth:`setPen()`, :py:meth:`brush()`
- """
- return self.__data.pen
-
- def setBrush(self, brush):
- """
- Assign a brush.
-
- In case of `brush.style() != QBrush.NoBrush`
- and `style() != QwtPlotCurve.Sticks`
- the area between the curve and the baseline will be filled.
-
- In case `not brush.color().isValid()` the area will be filled by
- `pen.color()`. The fill algorithm simply connects the first and the
- last curve point to the baseline. So the curve data has to be sorted
- (ascending or descending).
-
- :param brush: New brush
- :type brush: QBrush or QColor
-
- .. seealso::
-
- :py:meth:`brush()`, :py:meth:`setBaseline()`, :py:meth:`baseline()`
- """
- if isinstance(brush, QColor):
- brush = QBrush(brush)
- else:
- assert isinstance(brush, QBrush)
- if brush != self.__data.brush:
- self.__data.brush = brush
- self.legendChanged()
- self.itemChanged()
-
- def brush(self):
- """
- :return: Brush used to fill the area between lines and the baseline
-
- .. seealso::
-
- :py:meth:`setBrush()`, :py:meth:`setBaseline()`,
- :py:meth:`baseline()`
- """
- return self.__data.brush
-
- def directPaint(self, from_, to):
- """
- When observing a measurement while it is running, new points have
- to be added to an existing seriesItem. This method can be used to
- display them avoiding a complete redraw of the canvas.
-
- Setting `plot().canvas().setAttribute(Qt.WA_PaintOutsidePaintEvent, True)`
- will result in faster painting, if the paint engine of the canvas
- widget supports this feature.
-
- :param int from_: Index of the first point to be painted
- :param int to: Index of the last point to be painted
-
- .. seealso::
-
- :py:meth:`drawSeries()`
- """
- directPainter = QwtPlotDirectPainter(self.plot())
- directPainter.drawSeries(self, from_, to)
-
- def drawSeries(self, painter, xMap, yMap, canvasRect, from_, to):
- """
- Draw an interval of the curve
-
- :param QPainter painter: Painter
- :param qwt.scale_map.QwtScaleMap xMap: Maps x-values into pixel coordinates.
- :param qwt.scale_map.QwtScaleMap yMap: Maps y-values into pixel coordinates.
- :param QRectF canvasRect: Contents rectangle of the canvas
- :param int from_: Index of the first point to be painted
- :param int to: Index of the last point to be painted. If to < 0 the curve will be painted to its last point.
-
- .. seealso::
-
- :py:meth:`drawCurve()`, :py:meth:`drawSymbols()`
- """
- numSamples = self.dataSize()
- if not painter or numSamples <= 0:
- return
- if to < 0:
- to = numSamples - 1
- if qwtVerifyRange(numSamples, from_, to) > 0:
- painter.save()
- painter.setPen(self.__data.pen)
- self.drawCurve(
- painter, self.__data.style, xMap, yMap, canvasRect, from_, to
- )
- painter.restore()
- if self.__data.symbol and self.__data.symbol.style() != QwtSymbol.NoSymbol:
- painter.save()
- self.drawSymbols(
- painter, self.__data.symbol, xMap, yMap, canvasRect, from_, to
- )
- painter.restore()
-
- def drawCurve(self, painter, style, xMap, yMap, canvasRect, from_, to):
- """
- Draw the line part (without symbols) of a curve interval.
-
- :param QPainter painter: Painter
- :param int style: curve style, see `QwtPlotCurve.CurveStyle`
- :param qwt.scale_map.QwtScaleMap xMap: Maps x-values into pixel coordinates.
- :param qwt.scale_map.QwtScaleMap yMap: Maps y-values into pixel coordinates.
- :param QRectF canvasRect: Contents rectangle of the canvas
- :param int from_: Index of the first point to be painted
- :param int to: Index of the last point to be painted. If to < 0 the curve will be painted to its last point.
-
- .. seealso::
-
- :py:meth:`draw()`, :py:meth:`drawDots()`, :py:meth:`drawLines()`,
- :py:meth:`drawSteps()`, :py:meth:`drawSticks()`
- """
- if style == self.Lines:
- self.drawLines(painter, xMap, yMap, canvasRect, from_, to)
- elif style == self.Sticks:
- self.drawSticks(painter, xMap, yMap, canvasRect, from_, to)
- elif style == self.Steps:
- self.drawSteps(painter, xMap, yMap, canvasRect, from_, to)
- elif style == self.Dots:
- self.drawDots(painter, xMap, yMap, canvasRect, from_, to)
-
- def drawLines(self, painter, xMap, yMap, canvasRect, from_, to):
- """
- Draw lines
-
- :param QPainter painter: Painter
- :param qwt.scale_map.QwtScaleMap xMap: Maps x-values into pixel coordinates.
- :param qwt.scale_map.QwtScaleMap yMap: Maps y-values into pixel coordinates.
- :param QRectF canvasRect: Contents rectangle of the canvas
- :param int from_: Index of the first point to be painted
- :param int to: Index of the last point to be painted. If to < 0 the curve will be painted to its last point.
-
- .. seealso::
-
- :py:meth:`draw()`, :py:meth:`drawDots()`,
- :py:meth:`drawSteps()`, :py:meth:`drawSticks()`
- """
- if from_ > to:
- return
- doFill = (
- self.__data.brush.style() != Qt.NoBrush
- and self.__data.brush.color().alpha() > 0
- )
- polyline = series_to_polyline(xMap, yMap, self.data(), from_, to)
- painter.drawPolyline(polyline)
- if doFill:
- self.fillCurve(painter, xMap, yMap, canvasRect, polyline)
-
- def drawSticks(self, painter, xMap, yMap, canvasRect, from_, to):
- """
- Draw sticks
-
- :param QPainter painter: Painter
- :param qwt.scale_map.QwtScaleMap xMap: Maps x-values into pixel coordinates.
- :param qwt.scale_map.QwtScaleMap yMap: Maps y-values into pixel coordinates.
- :param QRectF canvasRect: Contents rectangle of the canvas
- :param int from_: Index of the first point to be painted
- :param int to: Index of the last point to be painted. If to < 0 the curve will be painted to its last point.
-
- .. seealso::
-
- :py:meth:`draw()`, :py:meth:`drawDots()`,
- :py:meth:`drawSteps()`, :py:meth:`drawLines()`
- """
- painter.save()
- painter.setRenderHint(QPainter.Antialiasing, False)
- x0 = xMap.transform(self.__data.baseline)
- y0 = yMap.transform(self.__data.baseline)
- o = self.orientation()
- series = self.data()
- for i in range(from_, to + 1):
- sample = series.sample(i)
- xi = xMap.transform(sample.x())
- yi = yMap.transform(sample.y())
- if o == Qt.Horizontal:
- painter.drawLine(QLineF(xi, y0, xi, yi))
- else:
- painter.drawLine(QLineF(x0, yi, xi, yi))
- painter.restore()
-
- def drawDots(self, painter, xMap, yMap, canvasRect, from_, to):
- """
- Draw dots
-
- :param QPainter painter: Painter
- :param qwt.scale_map.QwtScaleMap xMap: Maps x-values into pixel coordinates.
- :param qwt.scale_map.QwtScaleMap yMap: Maps y-values into pixel coordinates.
- :param QRectF canvasRect: Contents rectangle of the canvas
- :param int from_: Index of the first point to be painted
- :param int to: Index of the last point to be painted. If to < 0 the curve will be painted to its last point.
-
- .. seealso::
-
- :py:meth:`draw()`, :py:meth:`drawSticks()`,
- :py:meth:`drawSteps()`, :py:meth:`drawLines()`
- """
- doFill = (
- self.__data.brush.style() != Qt.NoBrush
- and self.__data.brush.color().alpha() > 0
- )
- polyline = series_to_polyline(xMap, yMap, self.data(), from_, to)
- painter.drawPoints(polyline)
- if doFill:
- self.fillCurve(painter, xMap, yMap, canvasRect, polyline)
-
- def drawSteps(self, painter, xMap, yMap, canvasRect, from_, to):
- """
- Draw steps
-
- :param QPainter painter: Painter
- :param qwt.scale_map.QwtScaleMap xMap: Maps x-values into pixel coordinates.
- :param qwt.scale_map.QwtScaleMap yMap: Maps y-values into pixel coordinates.
- :param QRectF canvasRect: Contents rectangle of the canvas
- :param int from_: Index of the first point to be painted
- :param int to: Index of the last point to be painted. If to < 0 the curve will be painted to its last point.
-
- .. seealso::
-
- :py:meth:`draw()`, :py:meth:`drawSticks()`,
- :py:meth:`drawDots()`, :py:meth:`drawLines()`
- """
- size = 2 * (to - from_) + 1
- if QT_API == "pyside6":
- polygon = QPolygonF()
- polygon.resize(size)
- elif QT_API == "pyqt6":
- polygon = QPolygonF([QPointF(0, 0)] * size)
- else:
- polygon = QPolygonF(size)
- inverted = self.orientation() == Qt.Vertical
- if self.__data.attributes & self.Inverted:
- inverted = not inverted
- series = self.data()
- ip = 0
- for i in range(from_, to + 1):
- sample = series.sample(i)
- xi = xMap.transform(sample.x())
- yi = yMap.transform(sample.y())
- if ip > 0:
- p0 = polygon[ip - 2]
- if inverted:
- polygon[ip - 1] = QPointF(p0.x(), yi)
- else:
- polygon[ip - 1] = QPointF(xi, p0.y())
- polygon[ip] = QPointF(xi, yi)
- ip += 2
- painter.drawPolyline(polygon)
- if self.__data.brush.style() != Qt.NoBrush:
- self.fillCurve(painter, xMap, yMap, canvasRect, polygon)
-
- def setCurveAttribute(self, attribute, on=True):
- """
- Specify an attribute for drawing the curve
-
- Supported curve attributes:
-
- * `QwtPlotCurve.Inverted`
-
- :param int attribute: Curve attribute
- :param bool on: On/Off
-
- .. seealso::
-
- :py:meth:`testCurveAttribute()`
- """
- if (self.__data.attributes & attribute) == on:
- return
- if on:
- self.__data.attributes |= attribute
- else:
- self.__data.attributes &= ~attribute
- self.itemChanged()
-
- def testCurveAttribute(self, attribute):
- """
- :return: True, if attribute is enabled
-
- .. seealso::
-
- :py:meth:`setCurveAttribute()`
- """
- return self.__data.attributes & attribute
-
- def fillCurve(self, painter, xMap, yMap, canvasRect, polygon):
- """
- Fill the area between the curve and the baseline with
- the curve brush
-
- :param QPainter painter: Painter
- :param qwt.scale_map.QwtScaleMap xMap: Maps x-values into pixel coordinates.
- :param qwt.scale_map.QwtScaleMap yMap: Maps y-values into pixel coordinates.
- :param QRectF canvasRect: Contents rectangle of the canvas
- :param QPolygonF polygon: Polygon - will be modified !
-
- .. seealso::
-
- :py:meth:`setBrush()`, :py:meth:`setBaseline()`,
- :py:meth:`setStyle()`
- """
- if self.__data.brush.style() == Qt.NoBrush:
- return
- self.closePolyline(painter, xMap, yMap, polygon)
- if polygon.count() <= 2:
- return
- brush = self.__data.brush
- if not brush.color().isValid():
- brush.setColor(self.__data.pen.color())
- painter.save()
- painter.setPen(Qt.NoPen)
- painter.setBrush(brush)
- painter.drawPolygon(polygon)
- painter.restore()
-
- def closePolyline(self, painter, xMap, yMap, polygon):
- """
- Complete a polygon to be a closed polygon including the
- area between the original polygon and the baseline.
-
- :param QPainter painter: Painter
- :param qwt.scale_map.QwtScaleMap xMap: Maps x-values into pixel coordinates.
- :param qwt.scale_map.QwtScaleMap yMap: Maps y-values into pixel coordinates.
- :param QPolygonF polygon: Polygon to be completed
- """
- if polygon.size() < 2:
- return
- baseline = self.__data.baseline
- if self.orientation() == Qt.Horizontal:
- if yMap.transformation():
- baseline = yMap.transformation().bounded(baseline)
- refY = yMap.transform(baseline)
- polygon.append(QPointF(polygon.last().x(), refY))
- polygon.append(QPointF(polygon.first().x(), refY))
- else:
- if xMap.transformation():
- baseline = xMap.transformation().bounded(baseline)
- refX = xMap.transform(baseline)
- polygon.append(QPointF(refX, polygon.last().y()))
- polygon.append(QPointF(refX, polygon.first().y()))
-
- def drawSymbols(self, painter, symbol, xMap, yMap, canvasRect, from_, to):
- """
- Draw symbols
-
- :param QPainter painter: Painter
- :param qwt.symbol.QwtSymbol symbol: Curve symbol
- :param qwt.scale_map.QwtScaleMap xMap: Maps x-values into pixel coordinates.
- :param qwt.scale_map.QwtScaleMap yMap: Maps y-values into pixel coordinates.
- :param QRectF canvasRect: Contents rectangle of the canvas
- :param int from_: Index of the first point to be painted
- :param int to: Index of the last point to be painted. If to < 0 the curve will be painted to its last point.
-
- .. seealso::
-
- :py:meth:`setSymbol()`, :py:meth:`drawSeries()`,
- :py:meth:`drawCurve()`
- """
- chunkSize = 500
- for i in range(from_, to + 1, chunkSize):
- n = min([chunkSize, to - i + 1])
- points = series_to_polyline(xMap, yMap, self.data(), i, i + n - 1)
- if points.size() > 0:
- symbol.drawSymbols(painter, points)
-
- def setBaseline(self, value):
- """
- Set the value of the baseline
-
- The baseline is needed for filling the curve with a brush or
- the Sticks drawing style.
-
- The interpretation of the baseline depends on the `orientation()`.
- With `Qt.Horizontal`, the baseline is interpreted as a horizontal line
- at y = baseline(), with `Qt.Vertical`, it is interpreted as a vertical
- line at x = baseline().
-
- The default value is 0.0.
-
- :param float value: Value of the baseline
-
- .. seealso::
-
- :py:meth:`baseline()`, :py:meth:`setBrush()`,
- :py:meth:`setStyle()`
- """
- if self.__data.baseline != value:
- self.__data.baseline = value
- self.itemChanged()
-
- def baseline(self):
- """
- :return: Value of the baseline
-
- .. seealso::
-
- :py:meth:`setBaseline()`
- """
- return self.__data.baseline
-
- def closestPoint(self, pos):
- """
- Find the closest curve point for a specific position
-
- :param QPoint pos: Position, where to look for the closest curve point
- :return: tuple `(index, dist)`
-
- `dist` is the distance between the position and the closest curve
- point. `index` is the index of the closest curve point, or -1 if
- none can be found ( f.e when the curve has no points ).
-
- .. note::
-
- `closestPoint()` implements a dumb algorithm, that iterates
- over all points
- """
- numSamples = self.dataSize()
- if self.plot() is None or numSamples <= 0:
- return -1
- series = self.data()
- xMap = self.plot().canvasMap(self.xAxis())
- yMap = self.plot().canvasMap(self.yAxis())
- index = -1
- dmin = 1.0e10
- for i in range(numSamples):
- sample = series.sample(i)
- cx = xMap.transform(sample.x()) - pos.x()
- cy = yMap.transform(sample.y()) - pos.y()
- f = qwtSqr(cx) + qwtSqr(cy)
- if f < dmin:
- index = i
- dmin = f
- dist = math.sqrt(dmin)
- return index, dist
-
- def legendIcon(self, index, size):
- """
- :param int index: Index of the legend entry (ignored as there is only one)
- :param QSizeF size: Icon size
- :return: Icon representing the curve on the legend
-
- .. seealso::
-
- :py:meth:`qwt.plot.QwtPlotItem.setLegendIconSize()`,
- :py:meth:`qwt.plot.QwtPlotItem.legendData()`
- """
- if size.isEmpty():
- return QwtGraphic()
- graphic = QwtGraphic()
- graphic.setDefaultSize(size)
- graphic.setRenderHint(QwtGraphic.RenderPensUnscaled, True)
- painter = QPainter(graphic)
- painter.setRenderHint(
- QPainter.Antialiasing, self.testRenderHint(QwtPlotItem.RenderAntialiased)
- )
- if self.__data.legendAttributes == 0 or (
- self.__data.legendAttributes & QwtPlotCurve.LegendShowBrush
- ):
- brush = self.__data.brush
- if brush.style() == Qt.NoBrush and self.__data.legendAttributes == 0:
- if self.style() != QwtPlotCurve.NoCurve:
- brush = QBrush(self.pen().color())
- elif (
- self.__data.symbol
- and self.__data.symbol.style() != QwtSymbol.NoSymbol
- ):
- brush = QBrush(self.__data.symbol.pen().color())
- if brush.style() != Qt.NoBrush:
- r = QRectF(0, 0, size.width(), size.height())
- painter.fillRect(r, brush)
- if self.__data.legendAttributes & QwtPlotCurve.LegendShowLine:
- if self.pen() != Qt.NoPen:
- painter.setPen(self.pen())
- y = size.height() // 2
- painter.drawLine(QLineF(0, y, size.width(), y))
- if self.__data.legendAttributes & QwtPlotCurve.LegendShowSymbol:
- if self.__data.symbol:
- r = QRectF(0, 0, size.width(), size.height())
- self.__data.symbol.drawSymbol(painter, r)
- return graphic
-
- def setData(self, *args, **kwargs):
- """
- Initialize data with a series data object or an array of points.
-
- .. py:method:: setData(data):
-
- :param data: Series data (e.g. `QwtPointArrayData` instance)
- :type data: .plot_series.QwtSeriesData
-
- .. py:method:: setData(xData, yData, [size=None], [finite=True]):
-
- Initialize data with `x` and `y` arrays.
-
- This signature was removed in Qwt6 and is temporarily maintained here to ensure compatibility with Qwt5.
-
- Same as `setSamples(x, y, [size=None], [finite=True])`
-
- :param x: List/array of x values
- :param y: List/array of y values
- :param size: size of xData and yData
- :type size: int or None
- :param bool finite: if True, keep only finite array elements (remove all infinity and not a number values), otherwise do not filter array elements
-
- .. seealso::
-
- :py:meth:`setSamples()`
- """
- if len(args) == 1 and not kwargs:
- super(QwtPlotCurve, self).setData(*args)
- elif len(args) in (2, 3, 4):
- self.setSamples(*args, **kwargs)
- else:
- raise TypeError(
- "%s().setData() takes 1, 2, 3 or 4 argument(s) (%s given)"
- % (self.__class__.__name__, len(args))
- )
-
- def setSamples(self, *args, **kwargs):
- """
- Initialize data with an array of points.
-
- .. py:method:: setSamples(data):
-
- :param data: Series data (e.g. `QwtPointArrayData` instance)
- :type data: .plot_series.QwtSeriesData
-
-
- .. py:method:: setSamples(samples):
-
- Same as `setData(QwtPointArrayData(samples))`
-
- :param samples: List/array of points
-
- .. py:method:: setSamples(xData, yData, [size=None], [finite=True]):
-
- Same as `setData(QwtPointArrayData(xData, yData, [size=None]))`
-
- :param xData: List/array of x values
- :param yData: List/array of y values
- :param size: size of xData and yData
- :type size: int or None
- :param bool finite: if True, keep only finite array elements (remove all infinity and not a number values), otherwise do not filter array elements
-
- .. seealso::
-
- :py:class:`.plot_series.QwtPointArrayData`
- """
- if len(args) == 1 and not kwargs:
- (samples,) = args
- if isinstance(samples, QwtSeriesData):
- self.setData(samples)
- else:
- self.setData(QwtPointArrayData(samples))
- elif len(args) >= 2:
- xData, yData = args[:2]
- try:
- size = kwargs.pop("size")
- except KeyError:
- size = None
- try:
- finite = kwargs.pop("finite")
- except KeyError:
- finite = None
- if kwargs:
- raise TypeError(
- "%s().setSamples(): unknown %s keyword "
- "argument(s)"
- % (self.__class__.__name__, ", ".join(list(kwargs.keys())))
- )
- for arg in args[2:]:
- if isinstance(arg, bool):
- finite = arg
- elif isinstance(arg, int):
- size = arg
- self.setData(QwtPointArrayData(xData, yData, size=size, finite=finite))
- else:
- raise TypeError(
- "%s().setSamples() takes 1, 2 or 3 argument(s) "
- "(%s given)" % (self.__class__.__name__, len(args))
- )
diff --git a/qwt/plot_directpainter.py b/qwt/plot_directpainter.py
deleted file mode 100644
index 06697d3..0000000
--- a/qwt/plot_directpainter.py
+++ /dev/null
@@ -1,286 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the Qwt License
-# Copyright (c) 2002 Uwe Rathmann, for the original C++ code
-# Copyright (c) 2015 Pierre Raybaut, for the Python translation/optimization
-# (see LICENSE file for more details)
-
-"""
-QwtPlotDirectPainter
---------------------
-
-.. autoclass:: QwtPlotDirectPainter
- :members:
-"""
-
-from qtpy.QtCore import QEvent, QObject, Qt
-from qtpy.QtGui import QPainter, QRegion
-
-from qwt.plot import QwtPlotItem
-from qwt.plot_canvas import QwtPlotCanvas
-
-
-def qwtRenderItem(painter, canvasRect, seriesItem, from_, to):
- # TODO: A minor performance improvement is possible with caching the maps
- plot = seriesItem.plot()
- xMap = plot.canvasMap(seriesItem.xAxis())
- yMap = plot.canvasMap(seriesItem.yAxis())
- painter.setRenderHint(
- QPainter.Antialiasing, seriesItem.testRenderHint(QwtPlotItem.RenderAntialiased)
- )
- seriesItem.drawSeries(painter, xMap, yMap, canvasRect, from_, to)
-
-
-def qwtHasBackingStore(canvas):
- return (
- canvas.testPaintAttribute(QwtPlotCanvas.BackingStore)
- and canvas.backingStore() is not None
- and not canvas.backingStore().isNull()
- )
-
-
-class QwtPlotDirectPainter_PrivateData(QObject):
- def __init__(self):
- QObject.__init__(self)
-
- self.attributes = 0
- self.hasClipping = False
- self.seriesItem = None # QwtPlotSeriesItem
- self.clipRegion = QRegion()
- self.painter = QPainter()
- self.from_ = None
- self.to = None
-
-
-class QwtPlotDirectPainter(QObject):
- """
- Painter object trying to paint incrementally
-
- Often applications want to display samples while they are
- collected. When there are too many samples complete replots
- will be expensive to be processed in a collection cycle.
-
- `QwtPlotDirectPainter` offers an API to paint
- subsets (f.e all additions points) without erasing/repainting
- the plot canvas.
-
- On certain environments it might be important to calculate a proper
- clip region before painting. F.e. for Qt Embedded only the clipped part
- of the backing store will be copied to a (maybe unaccelerated)
- frame buffer.
-
- .. warning::
-
- Incremental painting will only help when no replot is triggered
- by another operation (like changing scales) and nothing needs
- to be erased.
-
- Paint attributes:
-
- * `QwtPlotDirectPainter.AtomicPainter`:
-
- Initializing a `QPainter` is an expensive operation.
- When `AtomicPainter` is set each call of `drawSeries()` opens/closes
- a temporary `QPainter`. Otherwise `QwtPlotDirectPainter` tries to
- use the same `QPainter` as long as possible.
-
- * `QwtPlotDirectPainter.FullRepaint`:
-
- When `FullRepaint` is set the plot canvas is explicitly repainted
- after the samples have been rendered.
-
- * `QwtPlotDirectPainter.CopyBackingStore`:
-
- When `QwtPlotCanvas.BackingStore` is enabled the painter
- has to paint to the backing store and the widget. In certain
- situations/environments it might be faster to paint to
- the backing store only and then copy the backing store to the canvas.
- This flag can also be useful for settings, where Qt fills the
- the clip region with the widget background.
- """
-
- # enum Attribute
- AtomicPainter = 0x01
- FullRepaint = 0x02
- CopyBackingStore = 0x04
-
- def __init__(self, parent=None):
- QObject.__init__(self, parent)
- self.__data = QwtPlotDirectPainter_PrivateData()
-
- def setAttribute(self, attribute, on=True):
- """
- Change an attribute
-
- :param int attribute: Attribute to change
- :param bool on: On/Off
-
- .. seealso::
-
- :py:meth:`testAttribute()`
- """
- if self.testAttribute(attribute) != on:
- self.__data.attributes |= attribute
- else:
- self.__data.attributes &= ~attribute
- if attribute == self.AtomicPainter and on:
- self.reset()
-
- def testAttribute(self, attribute):
- """
- :param int attribute: Attribute to be tested
- :return: True, when attribute is enabled
-
- .. seealso::
-
- :py:meth:`setAttribute()`
- """
- return self.__data.attributes & attribute
-
- def setClipping(self, enable):
- """
- En/Disables clipping
-
- :param bool enable: Enables clipping is true, disable it otherwise
-
- .. seealso::
-
- :py:meth:`hasClipping()`, :py:meth:`clipRegion()`,
- :py:meth:`setClipRegion()`
- """
- self.__data.hasClipping = enable
-
- def hasClipping(self):
- """
- :return: Return true, when clipping is enabled
-
- .. seealso::
-
- :py:meth:`setClipping()`, :py:meth:`clipRegion()`,
- :py:meth:`setClipRegion()`
- """
- return self.__data.hasClipping
-
- def setClipRegion(self, region):
- """
- Assign a clip region and enable clipping
-
- Depending on the environment setting a proper clip region might
- improve the performance heavily. F.e. on Qt embedded only the clipped
- part of the backing store will be copied to a (maybe unaccelerated)
- frame buffer device.
-
- :param QRegion region: Clip region
-
- .. seealso::
-
- :py:meth:`hasClipping()`, :py:meth:`setClipping()`,
- :py:meth:`clipRegion()`
- """
- self.__data.clipRegion = region
- self.__data.hasClipping = True
-
- def clipRegion(self):
- """
- :return: Return Currently set clip region.
-
- .. seealso::
-
- :py:meth:`hasClipping()`, :py:meth:`setClipping()`,
- :py:meth:`setClipRegion()`
- """
- return self.__data.clipRegion
-
- def drawSeries(self, seriesItem, from_, to):
- """
- Draw a set of points of a seriesItem.
-
- When observing a measurement while it is running, new points have
- to be added to an existing seriesItem. drawSeries() can be used to
- display them avoiding a complete redraw of the canvas.
-
- Setting `plot().canvas().setAttribute(Qt.WA_PaintOutsidePaintEvent, True)`
- will result in faster painting, if the paint engine of the canvas widget
- supports this feature.
-
- :param qwt.plot_series.QwtPlotSeriesItem seriesItem: Item to be painted
- :param int from_: Index of the first point to be painted
- :param int to: Index of the last point to be painted. If to < 0 the series will be painted to its last point.
- """
- if seriesItem is None or seriesItem.plot() is None:
- return
- canvas = seriesItem.plot().canvas()
- canvasRect = canvas.contentsRect()
- if canvas and qwtHasBackingStore(canvas):
- painter = QPainter(canvas.backingStore())
- if self.__data.hasClipping:
- painter.setClipRegion(self.__data.clipRegion)
- qwtRenderItem(painter, canvasRect, seriesItem, from_, to)
- painter.end()
- if self.testAttribute(self.FullRepaint):
- canvas.repaint()
- return
- if canvas.testAttribute(Qt.WA_WState_InPaintEvent):
- if not self.__data.painter.isActive():
- self.reset()
- self.__data.painter.begin(canvas)
- canvas.installEventFilter(self)
- if self.__data.hasClipping:
- self.__data.painter.setClipRegion(
- QRegion(canvasRect) & self.__data.clipRegion
- )
- elif not self.__data.painter.hasClipping():
- self.__data.painter.setClipRect(canvasRect)
- qwtRenderItem(self.__data.painter, canvasRect, seriesItem, from_, to)
- if self.__data.attributes & self.AtomicPainter:
- self.reset()
- elif self.__data.hasClipping:
- self.__data.painter.setClipping(False)
- else:
- self.reset()
- self.__data.seriesItem = seriesItem
- self.__data.from_ = from_
- self.__data.to = to
- clipRegion = QRegion(canvasRect)
- if self.__data.hasClipping:
- clipRegion &= self.__data.clipRegion
- canvas.installEventFilter(self)
- canvas.repaint(clipRegion)
- canvas.removeEventFilter(self)
- self.__data.seriesItem = None
-
- def reset(self):
- """Close the internal QPainter"""
- if self.__data.painter.isActive():
- w = self.__data.painter.device() # XXX: cast to QWidget
- if w:
- w.removeEventFilter(self)
- self.__data.painter.end()
-
- def eventFilter(self, obj_, event):
- if event.type() == QEvent.Paint:
- self.reset()
- if self.__data.seriesItem:
- pe = event # XXX: cast to QPaintEvent
- canvas = self.__data.seriesItem.plot().canvas()
- painter = QPainter(canvas)
- painter.setClipRegion(pe.region())
- doCopyCache = self.testAttribute(self.CopyBackingStore)
- if doCopyCache:
- plotCanvas = canvas # XXX: cast to QwtPlotCanvas
- if plotCanvas:
- doCopyCache = qwtHasBackingStore(plotCanvas)
- if doCopyCache:
- painter.drawPixmap(
- plotCanvas.rect().topLeft(), plotCanvas.backingStore()
- )
- if not doCopyCache:
- qwtRenderItem(
- painter,
- canvas.contentsRect(),
- self.__data.seriesItem,
- self.__data.from_,
- self.__data.to,
- )
- return True
- return False
diff --git a/qwt/plot_grid.py b/qwt/plot_grid.py
deleted file mode 100644
index a75a07c..0000000
--- a/qwt/plot_grid.py
+++ /dev/null
@@ -1,519 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the Qwt License
-# Copyright (c) 2002 Uwe Rathmann, for the original C++ code
-# Copyright (c) 2015 Pierre Raybaut, for the Python translation/optimization
-# (see LICENSE file for more details)
-
-"""
-QwtPlotGrid
------------
-
-.. autoclass:: QwtPlotGrid
- :members:
-"""
-
-from qtpy.QtCore import QLineF, QObject, Qt
-from qtpy.QtGui import QPen
-
-from qwt._math import qwtFuzzyGreaterOrEqual, qwtFuzzyLessOrEqual
-from qwt.plot import QwtPlotItem
-from qwt.qthelpers import qcolor_from_str
-from qwt.scale_div import QwtScaleDiv
-
-
-class QwtPlotGrid_PrivateData(QObject):
- def __init__(self):
- QObject.__init__(self)
-
- self.xEnabled = True
- self.yEnabled = True
- self.xMinEnabled = False
- self.yMinEnabled = False
- self.xScaleDiv = QwtScaleDiv()
- self.yScaleDiv = QwtScaleDiv()
- self.majorPen = QPen()
- self.minorPen = QPen()
-
-
-class QwtPlotGrid(QwtPlotItem):
- """
- A class which draws a coordinate grid
-
- The `QwtPlotGrid` class can be used to draw a coordinate grid.
- A coordinate grid consists of major and minor vertical
- and horizontal grid lines. The locations of the grid lines
- are determined by the X and Y scale divisions which can
- be assigned with `setXDiv()` and `setYDiv()`.
- The `draw()` member draws the grid within a bounding
- rectangle.
- """
-
- def __init__(self, title="Grid"):
- QwtPlotItem.__init__(self, title)
- self.__data = QwtPlotGrid_PrivateData()
- self.setItemInterest(QwtPlotItem.ScaleInterest, True)
- self.setZ(10.0)
-
- @classmethod
- def make(
- cls,
- plot=None,
- z=None,
- enablemajor=None,
- enableminor=None,
- color=None,
- width=None,
- style=None,
- mincolor=None,
- minwidth=None,
- minstyle=None,
- ):
- """
- Create and setup a new `QwtPlotGrid` object (convenience function).
-
- :param plot: Plot to attach the curve to
- :type plot: qwt.plot.QwtPlot or None
- :param z: Z-value
- :type z: float or None
- :param enablemajor: Tuple of two boolean values (x, y) for enabling major grid lines
- :type enablemajor: bool or None
- :param enableminor: Tuple of two boolean values (x, y) for enabling minor grid lines
- :type enableminor: bool or None
- :param color: Pen color for both major and minor grid lines (default: Qt.gray)
- :type color: QColor or str or None
- :param width: Pen width for both major and minor grid lines (default: 1.0)
- :type width: float or None
- :param style: Pen style for both major and minor grid lines (default: Qt.DotLine)
- :type style: Qt.PenStyle or None
- :param mincolor: Pen color for minor grid lines only (default: Qt.gray)
- :type mincolor: QColor or str or None
- :param minwidth: Pen width for minor grid lines only (default: 1.0)
- :type minwidth: float or None
- :param minstyle: Pen style for minor grid lines only (default: Qt.DotLine)
- :type minstyle: Qt.PenStyle or None
-
- .. seealso::
-
- :py:meth:`setMinorPen()`, :py:meth:`setMajorPen()`
- """
- item = cls()
- if z is not None:
- item.setZ(z)
- color = qcolor_from_str(color, Qt.gray)
- width = 1.0 if width is None else float(width)
- style = Qt.DotLine if style is None else style
- item.setPen(QPen(color, width, style))
- if mincolor is not None or minwidth is not None or minstyle is not None:
- mincolor = qcolor_from_str(mincolor, Qt.gray)
- minwidth = 1.0 if width is None else minwidth
- minstyle = Qt.DotLine if style is None else minstyle
- item.setMinorPen(QPen(mincolor, minwidth, minstyle))
- if enablemajor is not None:
- if isinstance(enablemajor, tuple) and len(enablemajor) == 2:
- item.enableX(enablemajor[0])
- item.enableY(enablemajor[1])
- else:
- raise TypeError(
- "Invalid enablemajor %r (expecting tuple of two booleans)"
- % enablemajor
- )
- if enableminor is not None:
- if isinstance(enableminor, tuple) and len(enableminor) == 2:
- item.enableXMin(enableminor[0])
- item.enableYMin(enableminor[1])
- else:
- raise TypeError(
- "Invalid enableminor %r (expecting tuple of two booleans)"
- % enableminor
- )
- if plot is not None:
- item.attach(plot)
- return item
-
- def rtti(self):
- """
- :return: Return `QwtPlotItem.Rtti_PlotGrid`
- """
- return QwtPlotItem.Rtti_PlotGrid
-
- def enableX(self, on):
- """
- Enable or disable vertical grid lines
-
- :param bool on: Enable (true) or disable
-
- .. seealso::
-
- :py:meth:`enableXMin()`
- """
- if self.__data.xEnabled != on:
- self.__data.xEnabled = on
- self.legendChanged()
- self.itemChanged()
-
- def enableY(self, on):
- """
- Enable or disable horizontal grid lines
-
- :param bool on: Enable (true) or disable
-
- .. seealso::
-
- :py:meth:`enableYMin()`
- """
- if self.__data.yEnabled != on:
- self.__data.yEnabled = on
- self.legendChanged()
- self.itemChanged()
-
- def enableXMin(self, on):
- """
- Enable or disable minor vertical grid lines.
-
- :param bool on: Enable (true) or disable
-
- .. seealso::
-
- :py:meth:`enableX()`
- """
- if self.__data.xMinEnabled != on:
- self.__data.xMinEnabled = on
- self.legendChanged()
- self.itemChanged()
-
- def enableYMin(self, on):
- """
- Enable or disable minor horizontal grid lines.
-
- :param bool on: Enable (true) or disable
-
- .. seealso::
-
- :py:meth:`enableY()`
- """
- if self.__data.yMinEnabled != on:
- self.__data.yMinEnabled = on
- self.legendChanged()
- self.itemChanged()
-
- def setXDiv(self, scaleDiv):
- """
- Assign an x axis scale division
-
- :param qwt.scale_div.QwtScaleDiv scaleDiv: Scale division
- """
- if self.__data.xScaleDiv != scaleDiv:
- self.__data.xScaleDiv = scaleDiv
- self.itemChanged()
-
- def setYDiv(self, scaleDiv):
- """
- Assign an y axis scale division
-
- :param qwt.scale_div.QwtScaleDiv scaleDiv: Scale division
- """
- if self.__data.yScaleDiv != scaleDiv:
- self.__data.yScaleDiv = scaleDiv
- self.itemChanged()
-
- def setPen(self, *args):
- """
- Build and/or assign a pen for both major and minor grid lines
-
- .. py:method:: setPen(color, width, style)
- :noindex:
-
- Build and assign a pen for both major and minor grid lines
-
- In Qt5 the default pen width is 1.0 ( 0.0 in Qt4 ) what makes it
- non cosmetic (see `QPen.isCosmetic()`). This method signature has
- been introduced to hide this incompatibility.
-
- :param QColor color: Pen color
- :param float width: Pen width
- :param Qt.PenStyle style: Pen style
-
- .. py:method:: setPen(pen)
- :noindex:
-
- Assign a pen for both major and minor grid lines
-
- :param QPen pen: New pen
-
- .. seealso::
-
- :py:meth:`pen()`, :py:meth:`brush()`
- """
- if len(args) == 3:
- color, width, style = args
- self.setPen(QPen(color, width, style))
- elif len(args) == 1:
- (pen,) = args
- if self.__data.majorPen != pen or self.__data.minorPen != pen:
- self.__data.majorPen = pen
- self.__data.minorPen = pen
- self.legendChanged()
- self.itemChanged()
- else:
- raise TypeError(
- "%s().setPen() takes 1 or 3 argument(s) (%s given)"
- % (self.__class__.__name__, len(args))
- )
-
- def setMajorPen(self, *args):
- """
- Build and/or assign a pen for both major grid lines
-
- .. py:method:: setMajorPen(color, width, style)
- :noindex:
-
- Build and assign a pen for both major grid lines
-
- In Qt5 the default pen width is 1.0 ( 0.0 in Qt4 ) what makes it
- non cosmetic (see `QPen.isCosmetic()`). This method signature has
- been introduced to hide this incompatibility.
-
- :param QColor color: Pen color
- :param float width: Pen width
- :param Qt.PenStyle style: Pen style
-
- .. py:method:: setMajorPen(pen)
- :noindex:
-
- Assign a pen for the major grid lines
-
- :param QPen pen: New pen
-
- .. seealso::
-
- :py:meth:`majorPen()`, :py:meth:`setMinorPen()`,
- :py:meth:`setPen()`, :py:meth:`pen()`, :py:meth:`brush()`
- """
- if len(args) == 3:
- color, width, style = args
- self.setMajorPen(QPen(color, width, style))
- elif len(args) == 1:
- (pen,) = args
- if self.__data.majorPen != pen:
- self.__data.majorPen = pen
- self.legendChanged()
- self.itemChanged()
- else:
- raise TypeError(
- "%s().setMajorPen() takes 1 or 3 argument(s) (%s "
- "given)" % (self.__class__.__name__, len(args))
- )
-
- def setMinorPen(self, *args):
- """
- Build and/or assign a pen for both minor grid lines
-
- .. py:method:: setMinorPen(color, width, style)
- :noindex:
-
- Build and assign a pen for both minor grid lines
-
- In Qt5 the default pen width is 1.0 ( 0.0 in Qt4 ) what makes it
- non cosmetic (see `QPen.isCosmetic()`). This method signature has
- been introduced to hide this incompatibility.
-
- :param QColor color: Pen color
- :param float width: Pen width
- :param Qt.PenStyle style: Pen style
-
- .. py:method:: setMinorPen(pen)
- :noindex:
-
- Assign a pen for the minor grid lines
-
- :param QPen pen: New pen
-
- .. seealso::
-
- :py:meth:`minorPen()`, :py:meth:`setMajorPen()`,
- :py:meth:`setPen()`, :py:meth:`pen()`, :py:meth:`brush()`
- """
- if len(args) == 3:
- color, width, style = args
- self.setMinorPen(QPen(color, width, style))
- elif len(args) == 1:
- (pen,) = args
- if self.__data.minorPen != pen:
- self.__data.minorPen = pen
- self.legendChanged()
- self.itemChanged()
- else:
- raise TypeError(
- "%s().setMinorPen() takes 1 or 3 argument(s) (%s "
- "given)" % (self.__class__.__name__, len(args))
- )
-
- def draw(self, painter, xMap, yMap, canvasRect):
- """
- Draw the grid
-
- The grid is drawn into the bounding rectangle such that
- grid lines begin and end at the rectangle's borders. The X and Y
- maps are used to map the scale divisions into the drawing region
- screen.
-
- :param QPainter painter: Painter
- :param qwt.scale_map.QwtScaleMap xMap: X axis map
- :param qwt.scale_map.QwtScaleMap yMap: Y axis
- :param QRectF canvasRect: Contents rectangle of the plot canvas
- """
- minorPen = QPen(self.__data.minorPen)
- minorPen.setCapStyle(Qt.FlatCap)
- painter.setPen(minorPen)
- if self.__data.xEnabled and self.__data.xMinEnabled:
- self.drawLines(
- painter,
- canvasRect,
- Qt.Vertical,
- xMap,
- self.__data.xScaleDiv.ticks(QwtScaleDiv.MinorTick),
- )
- self.drawLines(
- painter,
- canvasRect,
- Qt.Vertical,
- xMap,
- self.__data.xScaleDiv.ticks(QwtScaleDiv.MediumTick),
- )
- if self.__data.yEnabled and self.__data.yMinEnabled:
- self.drawLines(
- painter,
- canvasRect,
- Qt.Horizontal,
- yMap,
- self.__data.yScaleDiv.ticks(QwtScaleDiv.MinorTick),
- )
- self.drawLines(
- painter,
- canvasRect,
- Qt.Horizontal,
- yMap,
- self.__data.yScaleDiv.ticks(QwtScaleDiv.MediumTick),
- )
- majorPen = QPen(self.__data.majorPen)
- majorPen.setCapStyle(Qt.FlatCap)
- painter.setPen(majorPen)
- if self.__data.xEnabled:
- self.drawLines(
- painter,
- canvasRect,
- Qt.Vertical,
- xMap,
- self.__data.xScaleDiv.ticks(QwtScaleDiv.MajorTick),
- )
- if self.__data.yEnabled:
- self.drawLines(
- painter,
- canvasRect,
- Qt.Horizontal,
- yMap,
- self.__data.yScaleDiv.ticks(QwtScaleDiv.MajorTick),
- )
-
- def drawLines(self, painter, canvasRect, orientation, scaleMap, values):
- x1 = canvasRect.left()
- x2 = canvasRect.right() - 1.0
- y1 = canvasRect.top()
- y2 = canvasRect.bottom() - 1.0
- for val in values:
- value = scaleMap.transform(val)
- if orientation == Qt.Horizontal:
- if qwtFuzzyGreaterOrEqual(value, y1) and qwtFuzzyLessOrEqual(value, y2):
- painter.drawLine(QLineF(x1, value, x2, value))
- else:
- if qwtFuzzyGreaterOrEqual(value, x1) and qwtFuzzyLessOrEqual(value, x2):
- painter.drawLine(QLineF(value, y1, value, y2))
-
- def majorPen(self):
- """
- :return: the pen for the major grid lines
-
- .. seealso::
-
- :py:meth:`setMajorPen()`, :py:meth:`setMinorPen()`,
- :py:meth:`setPen()`
- """
- return self.__data.majorPen
-
- def minorPen(self):
- """
- :return: the pen for the minor grid lines
-
- .. seealso::
-
- :py:meth:`setMinorPen()`, :py:meth:`setMajorPen()`,
- :py:meth:`setPen()`
- """
- return self.__data.minorPen
-
- def xEnabled(self):
- """
- :return: True if vertical grid lines are enabled
-
- .. seealso::
-
- :py:meth:`enableX()`
- """
- return self.__data.xEnabled
-
- def yEnabled(self):
- """
- :return: True if horizontal grid lines are enabled
-
- .. seealso::
-
- :py:meth:`enableY()`
- """
- return self.__data.yEnabled
-
- def xMinEnabled(self):
- """
- :return: True if minor vertical grid lines are enabled
-
- .. seealso::
-
- :py:meth:`enableXMin()`
- """
- return self.__data.xMinEnabled
-
- def yMinEnabled(self):
- """
- :return: True if minor horizontal grid lines are enabled
-
- .. seealso::
-
- :py:meth:`enableYMin()`
- """
- return self.__data.yMinEnabled
-
- def xScaleDiv(self):
- """
- :return: the scale division of the x axis
- """
- return self.__data.xScaleDiv
-
- def yScaleDiv(self):
- """
- :return: the scale division of the y axis
- """
- return self.__data.yScaleDiv
-
- def updateScaleDiv(self, xScaleDiv, yScaleDiv):
- """
- Update the grid to changes of the axes scale division
-
- :param qwt.scale_map.QwtScaleMap xMap: Scale division of the x-axis
- :param qwt.scale_map.QwtScaleMap yMap: Scale division of the y-axis
-
- .. seealso::
-
- :py:meth:`updateAxes()`
- """
- self.setXDiv(xScaleDiv)
- self.setYDiv(yScaleDiv)
diff --git a/qwt/plot_layout.py b/qwt/plot_layout.py
deleted file mode 100644
index 0919914..0000000
--- a/qwt/plot_layout.py
+++ /dev/null
@@ -1,1167 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the Qwt License
-# Copyright (c) 2002 Uwe Rathmann, for the original C++ code
-# Copyright (c) 2015 Pierre Raybaut, for the Python translation/optimization
-# (see LICENSE file for more details)
-
-"""
-QwtPlotLayout
--------------
-
-.. autoclass:: QwtPlotLayout
- :members:
-"""
-
-import math
-
-from qtpy.QtCore import QObject, QRectF, QSize, Qt
-from qtpy.QtGui import QFont, QRegion
-
-from qwt.plot import QwtPlot
-from qwt.scale_draw import QwtAbstractScaleDraw
-from qwt.scale_widget import QwtScaleWidget
-from qwt.text import QwtText
-
-QWIDGETSIZE_MAX = (1 << 24) - 1
-
-
-class LegendData(object):
- def __init__(self):
- self.frameWidth = None
- self.hScrollExtent = None
- self.vScrollExtent = None
- self.hint = QSize()
-
-
-class TitleData(object):
- def __init__(self):
- self.text = QwtText()
- self.frameWidth = None
-
-
-class FooterData(object):
- def __init__(self):
- self.text = QwtText()
- self.frameWidth = None
-
-
-class ScaleData(object):
- def __init__(self):
- self.isEnabled = None
- self.scaleWidget = QwtScaleWidget()
- self.scaleFont = QFont()
- self.start = None
- self.end = None
- self.baseLineOffset = None
- self.tickOffset = None
- self.dimWithoutTitle = None
-
-
-class CanvasData(object):
- def __init__(self):
- self.contentsMargins = [0 for _i in QwtPlot.AXES]
-
-
-class QwtPlotLayout_LayoutData(object):
- def __init__(self):
- self.legend = LegendData()
- self.title = TitleData()
- self.footer = FooterData()
- self.scale = [ScaleData() for _i in QwtPlot.AXES]
- self.canvas = CanvasData()
-
- def init(self, plot, rect):
- """Extract all layout relevant data from the plot components"""
- # legend
- legend = plot.legend()
- if legend:
- self.legend.frameWidth = legend.frameWidth()
- self.legend.hScrollExtent = legend.scrollExtent(Qt.Horizontal)
- self.legend.vScrollExtent = legend.scrollExtent(Qt.Vertical)
- hint = legend.sizeHint()
- w = min([hint.width(), math.floor(rect.width())])
- h = legend.heightForWidth(w)
- if h <= 0:
- h = hint.height()
- self.legend.hint = QSize(w, h)
- # title
- self.title.frameWidth = 0
- self.title.text = QwtText()
- if plot.titleLabel():
- label = plot.titleLabel()
- self.title.text = label.text()
- if not self.title.text.testPaintAttribute(QwtText.PaintUsingTextFont):
- self.title.text.setFont(label.font())
- self.title.frameWidth = plot.titleLabel().frameWidth()
- # footer
- self.footer.frameWidth = 0
- self.footer.text = QwtText()
- if plot.footerLabel():
- label = plot.footerLabel()
- self.footer.text = label.text()
- if not self.footer.text.testPaintAttribute(QwtText.PaintUsingTextFont):
- self.footer.text.setFont(label.font())
- self.footer.frameWidth = plot.footerLabel().frameWidth()
- # scales
- for axis in QwtPlot.AXES:
- if plot.axisEnabled(axis):
- scaleWidget = plot.axisWidget(axis)
- self.scale[axis].isEnabled = True
- self.scale[axis].scaleWidget = scaleWidget
- self.scale[axis].scaleFont = scaleWidget.font()
- self.scale[axis].start = scaleWidget.startBorderDist()
- self.scale[axis].end = scaleWidget.endBorderDist()
- self.scale[axis].baseLineOffset = scaleWidget.margin()
- self.scale[axis].tickOffset = scaleWidget.margin()
- if scaleWidget.scaleDraw().hasComponent(QwtAbstractScaleDraw.Ticks):
- self.scale[
- axis
- ].tickOffset += scaleWidget.scaleDraw().maxTickLength()
- self.scale[axis].dimWithoutTitle = scaleWidget.dimForLength(
- QWIDGETSIZE_MAX, self.scale[axis].scaleFont
- )
- if not scaleWidget.title().isEmpty():
- self.scale[axis].dimWithoutTitle -= scaleWidget.titleHeightForWidth(
- QWIDGETSIZE_MAX
- )
- else:
- self.scale[axis].isEnabled = False
- self.scale[axis].start = 0
- self.scale[axis].end = 0
- self.scale[axis].baseLineOffset = 0
- self.scale[axis].tickOffset = 0.0
- self.scale[axis].dimWithoutTitle = 0
- layout = plot.canvas().layout()
- if layout is not None:
- mgn = layout.contentsMargins()
- self.canvas.contentsMargins = [
- mgn.left(),
- mgn.top(),
- mgn.right(),
- mgn.bottom(),
- ]
-
-
-class QwtPlotLayout_PrivateData(QObject):
- def __init__(self):
- QObject.__init__(self)
-
- self.spacing = 5
- self.titleRect = QRectF()
- self.footerRect = QRectF()
- self.legendRect = QRectF()
- self.scaleRect = [QRectF() for _i in QwtPlot.AXES]
- self.canvasRect = QRectF()
- self.layoutData = QwtPlotLayout_LayoutData()
- self.legendPos = None
- self.legendRatio = None
- self.canvasMargin = [0] * len(QwtPlot.AXES)
- self.alignCanvasToScales = [False] * len(QwtPlot.AXES)
-
-
-class QwtPlotLayout(object):
- """
- Layout engine for QwtPlot.
-
- It is used by the `QwtPlot` widget to organize its internal widgets
- or by `QwtPlot.print()` to render its content to a QPaintDevice like
- a QPrinter, QPixmap/QImage or QSvgRenderer.
-
- .. seealso::
-
- :py:meth:`qwt.plot.QwtPlot.setPlotLayout()`
-
- Valid options:
-
- * `QwtPlotLayout.AlignScales`: Unused
- * `QwtPlotLayout.IgnoreScrollbars`: Ignore the dimension of the scrollbars. There are no scrollbars, when the plot is not rendered to widgets.
- * `QwtPlotLayout.IgnoreFrames`: Ignore all frames.
- * `QwtPlotLayout.IgnoreLegend`: Ignore the legend.
- * `QwtPlotLayout.IgnoreTitle`: Ignore the title.
- * `QwtPlotLayout.IgnoreFooter`: Ignore the footer.
- """
-
- # enum Option
- AlignScales = 0x01
- IgnoreScrollbars = 0x02
- IgnoreFrames = 0x04
- IgnoreLegend = 0x08
- IgnoreTitle = 0x10
- IgnoreFooter = 0x20
-
- def __init__(self):
- self.__data = QwtPlotLayout_PrivateData()
- self.setLegendPosition(QwtPlot.BottomLegend)
- self.setCanvasMargin(4)
- self.setAlignCanvasToScales(False)
- self.invalidate()
-
- def setCanvasMargin(self, margin, axis=-1):
- """
- Change a margin of the canvas. The margin is the space
- above/below the scale ticks. A negative margin will
- be set to -1, excluding the borders of the scales.
-
- :param int margin: New margin
- :param int axisId: Axis index
-
- .. seealso::
-
- :py:meth:`canvasMargin()`
-
- .. warning::
-
- The margin will have no effect when `alignCanvasToScale()` is True
- """
- if margin < 1:
- margin = -1
- if axis == -1:
- for axis in QwtPlot.AXES:
- self.__data.canvasMargin[axis] = margin
- elif axis in QwtPlot.AXES:
- self.__data.canvasMargin[axis] = margin
-
- def canvasMargin(self, axisId):
- """
- :param int axisId: Axis index
- :return: Margin around the scale tick borders
-
- .. seealso::
-
- :py:meth:`setCanvasMargin()`
- """
- if axisId not in QwtPlot.AXES:
- return 0
- return self.__data.canvasMargin[axisId]
-
- def setAlignCanvasToScales(self, *args):
- """
- Change the align-canvas-to-axis-scales setting.
-
- .. py:method:: setAlignCanvasToScales(on):
-
- Set the align-canvas-to-axis-scales flag for all axes
-
- :param bool on: True/False
-
- .. py:method:: setAlignCanvasToScales(axisId, on):
-
- Change the align-canvas-to-axis-scales setting.
- The canvas may:
-
- - extend beyond the axis scale ends to maximize its size,
- - align with the axis scale ends to control its size.
-
- The axisId parameter is somehow confusing as it identifies a
- border of the plot and not the axes, that are aligned. F.e when
- `QwtPlot.yLeft` is set, the left end of the the x-axes
- (`QwtPlot.xTop`, `QwtPlot.xBottom`) is aligned.
-
- :param int axisId: Axis index
- :param bool on: True/False
-
- .. seealso::
-
- :py:meth:`setAlignCanvasToScale()`,
- :py:meth:`alignCanvasToScale()`
- """
- if len(args) == 1:
- (on,) = args
- for axis in QwtPlot.AXES:
- self.__data.alignCanvasToScales[axis] = on
- elif len(args) == 2:
- axisId, on = args
- if axisId in QwtPlot.AXES:
- self.__data.alignCanvasToScales[axisId] = on
- else:
- raise TypeError(
- "%s().setAlignCanvasToScales() takes 1 or 2 "
- "argument(s) (%s given)" % (self.__class__.__name__, len(args))
- )
-
- def alignCanvasToScale(self, axisId):
- """
- Return the align-canvas-to-axis-scales setting.
- The canvas may:
-
- - extend beyond the axis scale ends to maximize its size
- - align with the axis scale ends to control its size.
-
- :param int axisId: Axis index
- :return: align-canvas-to-axis-scales setting
-
- .. seealso::
-
- :py:meth:`setAlignCanvasToScale()`, :py:meth:`setCanvasMargin()`
- """
- if axisId not in QwtPlot.AXES:
- return False
- return self.__data.alignCanvasToScales[axisId]
-
- def setSpacing(self, spacing):
- """
- Change the spacing of the plot. The spacing is the distance
- between the plot components.
-
- :param int spacing: New spacing
-
- .. seealso::
-
- :py:meth:`setCanvasMargin()`, :py:meth:`spacing()`
- """
- self.__data.spacing = max([0, spacing])
-
- def spacing(self):
- """
- :return: Spacing
-
- .. seealso::
-
- :py:meth:`margin()`, :py:meth:`setSpacing()`
- """
- return self.__data.spacing
-
- def setLegendPosition(self, *args):
- """
- Specify the position of the legend
-
- .. py:method:: setLegendPosition(pos, [ratio=0.]):
-
- Specify the position of the legend
-
- :param QwtPlot.LegendPosition pos: Legend position
- :param float ratio: Ratio between legend and the bounding rectangle of title, footer, canvas and axes
-
- The legend will be shrunk if it would need more space than the
- given ratio. The ratio is limited to ]0.0 .. 1.0]. In case of
- <= 0.0 it will be reset to the default ratio. The default
- vertical/horizontal ratio is 0.33/0.5.
-
- Valid position values:
-
- * `QwtPlot.LeftLegend`,
- * `QwtPlot.RightLegend`,
- * `QwtPlot.TopLegend`,
- * `QwtPlot.BottomLegend`
-
- .. seealso::
-
- :py:meth:`setLegendPosition()`
- """
- if len(args) == 2:
- pos, ratio = args
- if ratio > 1.0:
- ratio = 1.0
- if pos in (QwtPlot.TopLegend, QwtPlot.BottomLegend):
- if ratio <= 0.0:
- ratio = 0.33
- self.__data.legendRatio = ratio
- self.__data.legendPos = pos
- elif pos in (QwtPlot.LeftLegend, QwtPlot.RightLegend):
- if ratio <= 0.0:
- ratio = 0.5
- self.__data.legendRatio = ratio
- self.__data.legendPos = pos
- elif len(args) == 1:
- (pos,) = args
- self.setLegendPosition(pos, 0.0)
- else:
- raise TypeError(
- "%s().setLegendPosition() takes 1 or 2 argument(s)"
- "(%s given)" % (self.__class__.__name__, len(args))
- )
-
- def legendPosition(self):
- """
- :return: Position of the legend
-
- .. seealso::
-
- :py:meth:`legendPosition()`
- """
- return self.__data.legendPos
-
- def setLegendRatio(self, ratio):
- """
- Specify the relative size of the legend in the plot
-
- :param float ratio: Ratio between legend and the bounding rectangle of title, footer, canvas and axes
-
- The legend will be shrunk if it would need more space than the
- given ratio. The ratio is limited to ]0.0 .. 1.0]. In case of
- <= 0.0 it will be reset to the default ratio. The default
- vertical/horizontal ratio is 0.33/0.5.
-
- .. seealso::
-
- :py:meth:`legendRatio()`
- """
- self.setLegendPosition(self.legendPosition(), ratio)
-
- def legendRatio(self):
- """
- :return: The relative size of the legend in the plot.
-
- .. seealso::
-
- :py:meth:`setLegendRatio()`
- """
- return self.__data.legendRatio
-
- def setTitleRect(self, rect):
- """
- Set the geometry for the title
-
- This method is intended to be used from derived layouts
- overloading `activate()`
-
- :param QRectF rect: Rectangle
-
- .. seealso::
-
- :py:meth:`titleRect()`, :py:meth:`activate()`
- """
- self.__data.titleRect = rect
-
- def titleRect(self):
- """
- :return: Geometry for the title
-
- .. seealso::
-
- :py:meth:`invalidate()`, :py:meth:`activate()`
- """
- return self.__data.titleRect
-
- def setFooterRect(self, rect):
- """
- Set the geometry for the footer
-
- This method is intended to be used from derived layouts
- overloading `activate()`
-
- :param QRectF rect: Rectangle
-
- .. seealso::
-
- :py:meth:`footerRect()`, :py:meth:`activate()`
- """
- self.__data.footerRect = rect
-
- def footerRect(self):
- """
- :return: Geometry for the footer
-
- .. seealso::
-
- :py:meth:`invalidate()`, :py:meth:`activate()`
- """
- return self.__data.footerRect
-
- def setLegendRect(self, rect):
- """
- Set the geometry for the legend
-
- This method is intended to be used from derived layouts
- overloading `activate()`
-
- :param QRectF rect: Rectangle for the legend
-
- .. seealso::
-
- :py:meth:`footerRect()`, :py:meth:`activate()`
- """
- self.__data.legendRect = rect
-
- def legendRect(self):
- """
- :return: Geometry for the legend
-
- .. seealso::
-
- :py:meth:`invalidate()`, :py:meth:`activate()`
- """
- return self.__data.legendRect
-
- def setScaleRect(self, axis, rect):
- """
- Set the geometry for an axis
-
- This method is intended to be used from derived layouts
- overloading `activate()`
-
- :param int axisId: Axis index
- :param QRectF rect: Rectangle for the scale
-
- .. seealso::
-
- :py:meth:`scaleRect()`, :py:meth:`activate()`
- """
- if axis in QwtPlot.AXES:
- self.__data.scaleRect[axis] = rect
-
- def scaleRect(self, axis):
- """
- :param int axisId: Axis index
- :return: Geometry for the scale
-
- .. seealso::
-
- :py:meth:`invalidate()`, :py:meth:`activate()`
- """
- if axis not in QwtPlot.AXES:
- return QRectF()
- return self.__data.scaleRect[axis]
-
- def setCanvasRect(self, rect):
- """
- Set the geometry for the canvas
-
- This method is intended to be used from derived layouts
- overloading `activate()`
-
- :param QRectF rect: Rectangle
-
- .. seealso::
-
- :py:meth:`canvasRect()`, :py:meth:`activate()`
- """
- self.__data.canvasRect = rect
-
- def canvasRect(self):
- """
- :return: Geometry for the canvas
-
- .. seealso::
-
- :py:meth:`invalidate()`, :py:meth:`activate()`
- """
- return self.__data.canvasRect
-
- def invalidate(self):
- """
- Invalidate the geometry of all components.
-
- .. seealso::
-
- :py:meth:`activate()`
- """
- self.__data.titleRect = QRectF()
- self.__data.footerRect = QRectF()
- self.__data.legendRect = QRectF()
- self.__data.canvasRect = QRectF()
- for axis in QwtPlot.AXES:
- self.__data.scaleRect[axis] = QRectF()
-
- def minimumSizeHint(self, plot):
- """
- :param qwt.plot.QwtPlot plot: Plot widget
- :return: Minimum size hint
-
- .. seealso::
-
- :py:meth:`qwt.plot.QwtPlot.minimumSizeHint()`
- """
-
- class _ScaleData(object):
- def __init__(self):
- self.w = 0
- self.h = 0
- self.minLeft = 0
- self.minRight = 0
- self.tickOffset = 0
-
- scaleData = [_ScaleData() for _i in QwtPlot.AXES]
- canvasBorder = [0 for _i in QwtPlot.AXES]
- layout = plot.canvas().layout()
- if layout is None:
- left, top, right, bottom = 0, 0, 0, 0
- else:
- mgn = layout.contentsMargins()
- left, top, right, bottom = (
- mgn.left(),
- mgn.top(),
- mgn.right(),
- mgn.bottom(),
- )
- for axis in QwtPlot.AXES:
- if plot.axisEnabled(axis):
- scl = plot.axisWidget(axis)
- sd = scaleData[axis]
- hint = scl.minimumSizeHint()
- sd.w = hint.width()
- sd.h = hint.height()
- sd.minLeft, sd.minLeft = scl.getBorderDistHint()
- sd.tickOffset = scl.margin()
- if scl.scaleDraw().hasComponent(QwtAbstractScaleDraw.Ticks):
- sd.tickOffset += math.ceil(scl.scaleDraw().maxTickLength())
- canvasBorder[axis] = left + self.__data.canvasMargin[axis] + 1
- for axis in QwtPlot.AXES:
- sd = scaleData[axis]
- if sd.w and axis in (QwtPlot.xBottom, QwtPlot.xTop):
- if (
- sd.minLeft > canvasBorder[QwtPlot.yLeft]
- and scaleData[QwtPlot.yLeft].w
- ):
- shiftLeft = sd.minLeft - canvasBorder[QwtPlot.yLeft]
- if shiftLeft > scaleData[QwtPlot.yLeft].w:
- shiftLeft = scaleData[QwtPlot.yLeft].w
- sd.w -= shiftLeft
- if (
- sd.minRight > canvasBorder[QwtPlot.yRight]
- and scaleData[QwtPlot.yRight].w
- ):
- shiftRight = sd.minRight - canvasBorder[QwtPlot.yRight]
- if shiftRight > scaleData[QwtPlot.yRight].w:
- shiftRight = scaleData[QwtPlot.yRight].w
- sd.w -= shiftRight
- if sd.h and axis in (QwtPlot.yLeft, QwtPlot.yRight):
- if (
- sd.minLeft > canvasBorder[QwtPlot.xBottom]
- and scaleData[QwtPlot.xBottom].h
- ):
- shiftBottom = sd.minLeft - canvasBorder[QwtPlot.xBottom]
- if shiftBottom > scaleData[QwtPlot.xBottom].tickOffset:
- shiftBottom = scaleData[QwtPlot.xBottom].tickOffset
- sd.h -= shiftBottom
- if (
- sd.minLeft > canvasBorder[QwtPlot.xTop]
- and scaleData[QwtPlot.xTop].h
- ):
- shiftTop = sd.minRight - canvasBorder[QwtPlot.xTop]
- if shiftTop > scaleData[QwtPlot.xTop].tickOffset:
- shiftTop = scaleData[QwtPlot.xTop].tickOffset
- sd.h -= shiftTop
- canvas = plot.canvas()
- minCanvasSize = canvas.minimumSize()
- w = scaleData[QwtPlot.yLeft].w + scaleData[QwtPlot.yRight].w
- cw = (
- max([scaleData[QwtPlot.xBottom].w, scaleData[QwtPlot.xTop].w])
- + left
- + 1
- + right
- + 1
- )
- w += max([cw, minCanvasSize.width()])
- h = scaleData[QwtPlot.xBottom].h + scaleData[QwtPlot.xTop].h
- ch = (
- max([scaleData[QwtPlot.yLeft].h, scaleData[QwtPlot.yRight].h])
- + top
- + 1
- + bottom
- + 1
- )
- h += max([ch, minCanvasSize.height()])
- for label in [plot.titleLabel(), plot.footerLabel()]:
- if label and not label.text().isEmpty():
- centerOnCanvas = not plot.axisEnabled(
- QwtPlot.yLeft
- ) and plot.axisEnabled(QwtPlot.yRight)
- labelW = w
- if centerOnCanvas:
- labelW -= scaleData[QwtPlot.yLeft].w + scaleData[QwtPlot.yRight].w
- labelH = label.heightForWidth(labelW)
- if labelH > labelW:
- w = labelW = labelH
- if centerOnCanvas:
- w += scaleData[QwtPlot.yLeft].w + scaleData[QwtPlot.yRight].w
- labelH = label.heightForWidth(labelW)
- h += labelH + self.__data.spacing
- legend = plot.legend()
- if legend and not legend.isEmpty():
- if self.__data.legendPos in (QwtPlot.LeftLegend, QwtPlot.RightLegend):
- legendW = legend.sizeHint().width()
- legendH = legend.heightForWidth(legendW)
- if legend.frameWidth() > 0:
- w += self.__data.spacing
- if legendH > h:
- legendW += legend.scrollExtent(Qt.Horizontal)
- if self.__data.legendRatio < 1.0:
- legendW = min([legendW, int(w / (1.0 - self.__data.legendRatio))])
- w += legendW + self.__data.spacing
- else:
- legendW = min([legend.sizeHint().width(), w])
- legendH = legend.heightForWidth(legendW)
- if legend.frameWidth() > 0:
- h += self.__data.spacing
- if self.__data.legendRatio < 1.0:
- legendH = min([legendH, int(h / (1.0 - self.__data.legendRatio))])
- h += legendH + self.__data.spacing
- return QSize(int(w), int(h))
-
- def layoutLegend(self, options, rect):
- """
- Find the geometry for the legend
-
- :param options: Options how to layout the legend
- :param QRectF rect: Rectangle where to place the legend
- :return: Geometry for the legend
- """
- hint = self.__data.layoutData.legend.hint
- if self.__data.legendPos in (QwtPlot.LeftLegend, QwtPlot.RightLegend):
- dim = min([hint.width(), int(rect.width() * self.__data.legendRatio)])
- if not (options & self.IgnoreScrollbars):
- if hint.height() > rect.height():
- dim += self.__data.layoutData.legend.hScrollExtent
- else:
- dim = min([hint.height(), int(rect.height() * self.__data.legendRatio)])
- dim = max([dim, self.__data.layoutData.legend.vScrollExtent])
- legendRect = QRectF(rect)
- if self.__data.legendPos == QwtPlot.LeftLegend:
- legendRect.setWidth(dim)
- elif self.__data.legendPos == QwtPlot.RightLegend:
- legendRect.setX(rect.right() - dim)
- legendRect.setWidth(dim)
- elif self.__data.legendPos == QwtPlot.TopLegend:
- legendRect.setHeight(dim)
- elif self.__data.legendPos == QwtPlot.BottomLegend:
- legendRect.setY(rect.bottom() - dim)
- legendRect.setHeight(dim)
- return legendRect
-
- def alignLegend(self, canvasRect, legendRect):
- """
- Align the legend to the canvas
-
- :param QRectF canvasRect: Geometry of the canvas
- :param QRectF legendRect: Maximum geometry for the legend
- :return: Geometry for the aligned legend
- """
- alignedRect = legendRect
- if self.__data.legendPos in (QwtPlot.BottomLegend, QwtPlot.TopLegend):
- if self.__data.layoutData.legend.hint.width() < canvasRect.width():
- alignedRect.setX(canvasRect.x())
- alignedRect.setWidth(canvasRect.width())
- else:
- if self.__data.layoutData.legend.hint.height() < canvasRect.height():
- alignedRect.setY(canvasRect.y())
- alignedRect.setHeight(canvasRect.height())
- return alignedRect
-
- def expandLineBreaks(self, options, rect):
- """
- Expand all line breaks in text labels, and calculate the height
- of their widgets in orientation of the text.
-
- :param options: Options how to layout the legend
- :param QRectF rect: Bounding rectangle for title, footer, axes and canvas.
- :return: tuple `(dimTitle, dimFooter, dimAxes)`
-
- Returns:
-
- * `dimTitle`: Expanded height of the title widget
- * `dimFooter`: Expanded height of the footer widget
- * `dimAxes`: Expanded heights of the axis in axis orientation.
- """
- dimTitle = dimFooter = 0
- dimAxes = [0 for axis in QwtPlot.AXES]
- backboneOffset = [0 for _i in QwtPlot.AXES]
- for axis in QwtPlot.AXES:
- if not (options & self.IgnoreFrames):
- backboneOffset[axis] += self.__data.layoutData.canvas.contentsMargins[
- axis
- ]
- if not self.__data.alignCanvasToScales[axis]:
- backboneOffset[axis] += self.__data.canvasMargin[axis]
- done = False
- while not done:
- done = True
- # the size for the 4 axis depend on each other. Expanding
- # the height of a horizontal axis will shrink the height
- # for the vertical axis, shrinking the height of a vertical
- # axis will result in a line break what will expand the
- # width and results in shrinking the width of a horizontal
- # axis what might result in a line break of a horizontal
- # axis ... . So we loop as long until no size changes.
- if not (
- (options & self.IgnoreTitle)
- or self.__data.layoutData.title.text.isEmpty()
- ):
- w = rect.width()
- if (
- self.__data.layoutData.scale[QwtPlot.yLeft].isEnabled
- != self.__data.layoutData.scale[QwtPlot.yRight].isEnabled
- ):
- w -= dimAxes[QwtPlot.yLeft] + dimAxes[QwtPlot.yRight]
- d = math.ceil(self.__data.layoutData.title.text.heightForWidth(w))
- if not (options & self.IgnoreFrames):
- d += 2 * self.__data.layoutData.title.frameWidth
- if d > dimTitle:
- dimTitle = d
- done = False
- if not (
- (options & self.IgnoreFooter)
- or self.__data.layoutData.footer.text.isEmpty()
- ):
- w = rect.width()
- if (
- self.__data.layoutData.scale[QwtPlot.yLeft].isEnabled
- != self.__data.layoutData.scale[QwtPlot.yRight].isEnabled
- ):
- w -= dimAxes[QwtPlot.yLeft] + dimAxes[QwtPlot.yRight]
- d = math.ceil(self.__data.layoutData.footer.text.heightForWidth(w))
- if not (options & self.IgnoreFrames):
- d += 2 * self.__data.layoutData.footer.frameWidth
- if d > dimFooter:
- dimFooter = d
- done = False
- for axis in QwtPlot.AXES:
- scaleData = self.__data.layoutData.scale[axis]
- if scaleData.isEnabled:
- if axis in (QwtPlot.xTop, QwtPlot.xBottom):
- length = (
- rect.width()
- - dimAxes[QwtPlot.yLeft]
- - dimAxes[QwtPlot.yRight]
- )
- length -= scaleData.start + scaleData.end
- if dimAxes[QwtPlot.yRight] > 0:
- length -= 1
- length += min(
- [
- dimAxes[QwtPlot.yLeft],
- scaleData.start - backboneOffset[QwtPlot.yLeft],
- ]
- )
- length += min(
- [
- dimAxes[QwtPlot.yRight],
- scaleData.end - backboneOffset[QwtPlot.yRight],
- ]
- )
- else:
- length = (
- rect.height()
- - dimAxes[QwtPlot.xTop]
- - dimAxes[QwtPlot.xBottom]
- )
- length -= scaleData.start + scaleData.end
- length -= 1
- if dimAxes[QwtPlot.xBottom] <= 0:
- length -= 1
- if dimAxes[QwtPlot.xTop] <= 0:
- length -= 1
- if dimAxes[QwtPlot.xBottom] > 0:
- length += min(
- [
- self.__data.layoutData.scale[
- QwtPlot.xBottom
- ].tickOffset,
- float(
- scaleData.start
- - backboneOffset[QwtPlot.xBottom]
- ),
- ]
- )
- if dimAxes[QwtPlot.xTop] > 0:
- length += min(
- [
- self.__data.layoutData.scale[
- QwtPlot.xTop
- ].tickOffset,
- float(scaleData.end - backboneOffset[QwtPlot.xTop]),
- ]
- )
- if dimTitle > 0:
- length -= dimTitle + self.__data.spacing
- d = scaleData.dimWithoutTitle
- if not scaleData.scaleWidget.title().isEmpty():
- d += scaleData.scaleWidget.titleHeightForWidth(
- math.floor(length)
- )
- if d > dimAxes[axis]:
- dimAxes[axis] = d
- done = False
- return dimTitle, dimFooter, dimAxes
-
- def alignScales(self, options, canvasRect, scaleRect):
- """
- Align the ticks of the axis to the canvas borders using
- the empty corners.
-
- :param options: Options how to layout the legend
- :param QRectF canvasRect: Geometry of the canvas ( IN/OUT )
- :param QRectF scaleRect: Geometry of the scales ( IN/OUT )
- """
- backboneOffset = [0 for _i in QwtPlot.AXES]
- for axis in QwtPlot.AXES:
- backboneOffset[axis] = 0
- if not self.__data.alignCanvasToScales[axis]:
- backboneOffset[axis] += self.__data.canvasMargin[axis]
- if not options & self.IgnoreFrames:
- backboneOffset[axis] += self.__data.layoutData.canvas.contentsMargins[
- axis
- ]
- for axis in QwtPlot.AXES:
- if not scaleRect[axis].isValid():
- continue
- startDist = self.__data.layoutData.scale[axis].start
- endDist = self.__data.layoutData.scale[axis].end
- axisRect = scaleRect[axis]
- if axis in (QwtPlot.xTop, QwtPlot.xBottom):
- leftScaleRect = scaleRect[QwtPlot.yLeft]
- leftOffset = backboneOffset[QwtPlot.yLeft] - startDist
- if leftScaleRect.isValid():
- dx = leftOffset + leftScaleRect.width()
- if self.__data.alignCanvasToScales[QwtPlot.yLeft] and dx < 0.0:
- cLeft = canvasRect.left()
- canvasRect.setLeft(max([cLeft, axisRect.left() - dx]))
- else:
- minLeft = leftScaleRect.left()
- left = axisRect.left() + leftOffset
- axisRect.setLeft(max([left, minLeft]))
- else:
- if (
- self.__data.alignCanvasToScales[QwtPlot.yLeft]
- and leftOffset < 0
- ):
- canvasRect.setLeft(
- max([canvasRect.left(), axisRect.left() - leftOffset])
- )
- else:
- if leftOffset > 0:
- axisRect.setLeft(axisRect.left() + leftOffset)
- rightScaleRect = scaleRect[QwtPlot.yRight]
- rightOffset = backboneOffset[QwtPlot.yRight] - endDist + 1
- if rightScaleRect.isValid():
- dx = rightOffset + rightScaleRect.width()
- if self.__data.alignCanvasToScales[QwtPlot.yRight] and dx < 0:
- cRight = canvasRect.right()
- canvasRect.setRight(min([cRight, axisRect.right() + dx]))
- maxRight = rightScaleRect.right()
- right = axisRect.right() - rightOffset
- axisRect.setRight(min([right, maxRight]))
- else:
- if (
- self.__data.alignCanvasToScales[QwtPlot.yRight]
- and rightOffset < 0
- ):
- canvasRect.setRight(
- min([canvasRect.right(), axisRect.right() + rightOffset])
- )
- else:
- if rightOffset > 0:
- axisRect.setRight(axisRect.right() - rightOffset)
- else:
- bottomScaleRect = scaleRect[QwtPlot.xBottom]
- bottomOffset = backboneOffset[QwtPlot.xBottom] - endDist + 1
- if bottomScaleRect.isValid():
- dy = bottomOffset + bottomScaleRect.height()
- if self.__data.alignCanvasToScales[QwtPlot.xBottom] and dy < 0:
- cBottom = canvasRect.bottom()
- canvasRect.setBottom(min([cBottom, axisRect.bottom() + dy]))
- else:
- maxBottom = (
- bottomScaleRect.top()
- + self.__data.layoutData.scale[QwtPlot.xBottom].tickOffset
- )
- bottom = axisRect.bottom() - bottomOffset
- axisRect.setBottom(min([bottom, maxBottom]))
- else:
- if (
- self.__data.alignCanvasToScales[QwtPlot.xBottom]
- and bottomOffset < 0
- ):
- canvasRect.setBottom(
- min([canvasRect.bottom(), axisRect.bottom() + bottomOffset])
- )
- else:
- if bottomOffset > 0:
- axisRect.setBottom(axisRect.bottom() - bottomOffset)
- topScaleRect = scaleRect[QwtPlot.xTop]
- topOffset = backboneOffset[QwtPlot.xTop] - startDist
- if topScaleRect.isValid():
- dy = topOffset + topScaleRect.height()
- if self.__data.alignCanvasToScales[QwtPlot.xTop] and dy < 0:
- cTop = canvasRect.top()
- canvasRect.setTop(max([cTop, axisRect.top() - dy]))
- else:
- minTop = (
- topScaleRect.bottom()
- - self.__data.layoutData.scale[QwtPlot.xTop].tickOffset
- )
- top = axisRect.top() + topOffset
- axisRect.setTop(max([top, minTop]))
- else:
- if self.__data.alignCanvasToScales[QwtPlot.xTop] and topOffset < 0:
- canvasRect.setTop(
- max([canvasRect.top(), axisRect.top() - topOffset])
- )
- else:
- if topOffset > 0:
- axisRect.setTop(axisRect.top() + topOffset)
- for axis in QwtPlot.AXES:
- sRect = scaleRect[axis]
- if not sRect.isValid():
- continue
- if axis in (QwtPlot.xBottom, QwtPlot.xTop):
- if self.__data.alignCanvasToScales[QwtPlot.yLeft]:
- y = canvasRect.left() - self.__data.layoutData.scale[axis].start
- if not options & self.IgnoreFrames:
- y += self.__data.layoutData.canvas.contentsMargins[
- QwtPlot.yLeft
- ]
- sRect.setLeft(y)
- if self.__data.alignCanvasToScales[QwtPlot.yRight]:
- y = canvasRect.right() - 1 + self.__data.layoutData.scale[axis].end
- if not options & self.IgnoreFrames:
- y -= self.__data.layoutData.canvas.contentsMargins[
- QwtPlot.yRight
- ]
- sRect.setRight(y)
- if self.__data.alignCanvasToScales[axis]:
- if axis == QwtPlot.xTop:
- sRect.setBottom(canvasRect.top())
- else:
- sRect.setTop(canvasRect.bottom())
- else:
- if self.__data.alignCanvasToScales[QwtPlot.xTop]:
- x = canvasRect.top() - self.__data.layoutData.scale[axis].start
- if not options & self.IgnoreFrames:
- x += self.__data.layoutData.canvas.contentsMargins[QwtPlot.xTop]
- sRect.setTop(x)
- if self.__data.alignCanvasToScales[QwtPlot.xBottom]:
- x = canvasRect.bottom() - 1 + self.__data.layoutData.scale[axis].end
- if not options & self.IgnoreFrames:
- x -= self.__data.layoutData.canvas.contentsMargins[
- QwtPlot.xBottom
- ]
- sRect.setBottom(x)
- if self.__data.alignCanvasToScales[axis]:
- if axis == QwtPlot.yLeft:
- sRect.setRight(canvasRect.left())
- else:
- sRect.setLeft(canvasRect.right())
-
- def activate(self, plot, plotRect, options=0x00):
- """
- Recalculate the geometry of all components.
-
- :param qwt.plot.QwtPlot plot: Plot to be layout
- :param QRectF plotRect: Rectangle where to place the components
- :param options: Layout options
- """
- self.invalidate()
- rect = QRectF(plotRect)
- self.__data.layoutData.init(plot, rect)
- if (
- not (options & self.IgnoreLegend)
- and plot.legend()
- and not plot.legend().isEmpty()
- ):
- self.__data.legendRect = self.layoutLegend(options, rect)
- region = QRegion(rect.toRect())
- rect = QRectF(
- region.subtracted(
- QRegion(self.__data.legendRect.toRect())
- ).boundingRect()
- )
- if self.__data.legendPos == QwtPlot.LeftLegend:
- rect.setLeft(rect.left() + self.__data.spacing)
- elif self.__data.legendPos == QwtPlot.RightLegend:
- rect.setRight(rect.right() - self.__data.spacing)
- elif self.__data.legendPos == QwtPlot.TopLegend:
- rect.setTop(rect.top() + self.__data.spacing)
- elif self.__data.legendPos == QwtPlot.BottomLegend:
- rect.setBottom(rect.bottom() - self.__data.spacing)
-
- # +---+-----------+---+
- # | Title |
- # +---+-----------+---+
- # | | Axis | |
- # +---+-----------+---+
- # | A | | A |
- # | x | Canvas | x |
- # | i | | i |
- # | s | | s |
- # +---+-----------+---+
- # | | Axis | |
- # +---+-----------+---+
- # | Footer |
- # +---+-----------+---+
-
- # title, footer and axes include text labels. The height of each
- # label depends on its line breaks, that depend on the width
- # for the label. A line break in a horizontal text will reduce
- # the available width for vertical texts and vice versa.
- # expandLineBreaks finds the height/width for title, footer and axes
- # including all line breaks.
-
- dimTitle, dimFooter, dimAxes = self.expandLineBreaks(options, rect)
- if dimTitle > 0:
- self.__data.titleRect.setRect(
- rect.left(), rect.top(), rect.width(), dimTitle
- )
- rect.setTop(self.__data.titleRect.bottom() + self.__data.spacing)
- if (
- self.__data.layoutData.scale[QwtPlot.yLeft].isEnabled
- != self.__data.layoutData.scale[QwtPlot.yRight].isEnabled
- ):
- self.__data.titleRect.setX(rect.left() + dimAxes[QwtPlot.yLeft])
- self.__data.titleRect.setWidth(
- rect.width() - dimAxes[QwtPlot.yLeft] - dimAxes[QwtPlot.yRight]
- )
- if dimFooter > 0:
- self.__data.footerRect.setRect(
- rect.left(), rect.bottom() - dimFooter, rect.width(), dimFooter
- )
- rect.setBottom(self.__data.footerRect.top() - self.__data.spacing)
- if (
- self.__data.layoutData.scale[QwtPlot.yLeft].isEnabled
- != self.__data.layoutData.scale[QwtPlot.yRight].isEnabled
- ):
- self.__data.footerRect.setX(rect.left() + dimAxes[QwtPlot.yLeft])
- self.__data.footerRect.setWidth(
- rect.width() - dimAxes[QwtPlot.yLeft] - dimAxes[QwtPlot.yRight]
- )
- self.__data.canvasRect.setRect(
- rect.x() + dimAxes[QwtPlot.yLeft],
- rect.y() + dimAxes[QwtPlot.xTop],
- rect.width() - dimAxes[QwtPlot.yRight] - dimAxes[QwtPlot.yLeft],
- rect.height() - dimAxes[QwtPlot.xBottom] - dimAxes[QwtPlot.xTop],
- )
- for axis in QwtPlot.AXES:
- if dimAxes[axis]:
- dim = dimAxes[axis]
- scaleRect = self.__data.scaleRect[axis]
- scaleRect.setRect(*self.__data.canvasRect.getRect())
- if axis == QwtPlot.yLeft:
- scaleRect.setX(self.__data.canvasRect.left() - dim)
- scaleRect.setWidth(dim)
- elif axis == QwtPlot.yRight:
- scaleRect.setX(self.__data.canvasRect.right())
- scaleRect.setWidth(dim)
- elif axis == QwtPlot.xBottom:
- scaleRect.setY(self.__data.canvasRect.bottom())
- scaleRect.setHeight(dim)
- elif axis == QwtPlot.xTop:
- scaleRect.setY(self.__data.canvasRect.top() - dim)
- scaleRect.setHeight(dim)
- scaleRect = scaleRect.normalized()
-
- # +---+-----------+---+
- # | <- Axis -> |
- # +-^-+-----------+-^-+
- # | | | | | |
- # | | | |
- # | A | | A |
- # | x | Canvas | x |
- # | i | | i |
- # | s | | s |
- # | | | |
- # | | | | | |
- # +-V-+-----------+-V-+
- # | <- Axis -> |
- # +---+-----------+---+
-
- # The ticks of the axes - not the labels above - should
- # be aligned to the canvas. So we try to use the empty
- # corners to extend the axes, so that the label texts
- # left/right of the min/max ticks are moved into them.
-
- self.alignScales(options, self.__data.canvasRect, self.__data.scaleRect)
- if not self.__data.legendRect.isEmpty():
- self.__data.legendRect = self.alignLegend(
- self.__data.canvasRect, self.__data.legendRect
- )
diff --git a/qwt/plot_marker.py b/qwt/plot_marker.py
deleted file mode 100644
index 1db25cd..0000000
--- a/qwt/plot_marker.py
+++ /dev/null
@@ -1,633 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the Qwt License
-# Copyright (c) 2002 Uwe Rathmann, for the original C++ code
-# Copyright (c) 2015 Pierre Raybaut, for the Python translation/optimization
-# (see LICENSE file for more details)
-
-"""
-QwtPlotMarker
--------------
-
-.. autoclass:: QwtPlotMarker
- :members:
-"""
-
-from qtpy.QtCore import QLineF, QObject, QPointF, QRect, QRectF, QSizeF, Qt
-from qtpy.QtGui import QPainter, QPen
-
-from qwt.graphic import QwtGraphic
-from qwt.plot import QwtPlot, QwtPlotItem
-from qwt.qthelpers import qcolor_from_str
-from qwt.symbol import QwtSymbol
-from qwt.text import QwtText
-
-
-class QwtPlotMarker_PrivateData(QObject):
- def __init__(self):
- QObject.__init__(self)
-
- self.labelAlignment = Qt.AlignCenter
- self.labelOrientation = Qt.Horizontal
- self.spacing = 2
- self.symbol = None
- self.style = QwtPlotMarker.NoLine
- self.xValue = 0.0
- self.yValue = 0.0
- self.label = QwtText()
- self.pen = QPen()
-
-
-class QwtPlotMarker(QwtPlotItem):
- """
- A class for drawing markers
-
- A marker can be a horizontal line, a vertical line,
- a symbol, a label or any combination of them, which can
- be drawn around a center point inside a bounding rectangle.
-
- The `setSymbol()` member assigns a symbol to the marker.
- The symbol is drawn at the specified point.
-
- With `setLabel()`, a label can be assigned to the marker.
- The `setLabelAlignment()` member specifies where the label is drawn. All
- the Align*-constants in `Qt.AlignmentFlags` (see Qt documentation)
- are valid. The interpretation of the alignment depends on the marker's
- line style. The alignment refers to the center point of
- the marker, which means, for example, that the label would be printed
- left above the center point if the alignment was set to
- `Qt.AlignLeft | Qt.AlignTop`.
-
- Line styles:
-
- * `QwtPlotMarker.NoLine`: No line
- * `QwtPlotMarker.HLine`: A horizontal line
- * `QwtPlotMarker.VLine`: A vertical line
- * `QwtPlotMarker.Cross`: A crosshair
- """
-
- # enum LineStyle
- NoLine, HLine, VLine, Cross = list(range(4))
-
- def __init__(self, title=None):
- if title is None:
- title = ""
- if not isinstance(title, QwtText):
- title = QwtText(title)
- QwtPlotItem.__init__(self, title)
- self.__data = QwtPlotMarker_PrivateData()
- self.setZ(30.0)
-
- @classmethod
- def make(
- cls,
- xvalue=None,
- yvalue=None,
- title=None,
- label=None,
- symbol=None,
- plot=None,
- z=None,
- x_axis=None,
- y_axis=None,
- align=None,
- orientation=None,
- spacing=None,
- linestyle=None,
- color=None,
- width=None,
- style=None,
- antialiased=False,
- ):
- """
- Create and setup a new `QwtPlotMarker` object (convenience function).
-
- :param xvalue: x position (optional, default: None)
- :type xvalue: float or None
- :param yvalue: y position (optional, default: None)
- :type yvalue: float or None
- :param title: Marker title
- :type title: qwt.text.QwtText or str or None
- :param label: Label text
- :type label: qwt.text.QwtText or str or None
- :param symbol: New symbol
- :type symbol: qwt.symbol.QwtSymbol or None
- :param plot: Plot to attach the curve to
- :type plot: qwt.plot.QwtPlot or None
- :param z: Z-value
- :type z: float or None
- :param int x_axis: curve X-axis (default: QwtPlot.yLeft)
- :param int y_axis: curve Y-axis (default: QwtPlot.xBottom)
- :param align: Alignment of the label
- :type align: Qt.Alignment or None
- :param orientation: Orientation of the label
- :type orientation: Qt.Orientation or None
- :param spacing: Spacing (distance between the position and the label)
- :type spacing: int or None
- :param int linestyle: Line style
- :param color: Pen color
- :type color: QColor or str or None
- :param float width: Pen width
- :param Qt.PenStyle style: Pen style
- :param bool antialiased: if True, enable antialiasing rendering
-
- .. seealso::
-
- :py:meth:`setData()`, :py:meth:`setPen()`, :py:meth:`attach()`
- """
- item = cls(title)
- if z is not None:
- item.setZ(z)
- if symbol is not None:
- item.setSymbol(symbol)
- if xvalue is not None:
- item.setXValue(xvalue)
- if yvalue is not None:
- item.setYValue(yvalue)
- if label is not None:
- item.setLabel(label)
- x_axis = QwtPlot.xBottom if x_axis is None else x_axis
- y_axis = QwtPlot.yLeft if y_axis is None else y_axis
- item.setAxes(x_axis, y_axis)
- if align is not None:
- item.setLabelAlignment(align)
- if orientation is not None:
- item.setLabelOrientation(orientation)
- if spacing is not None:
- item.setSpacing(spacing)
- color = qcolor_from_str(color, Qt.black)
- width = 1.0 if width is None else width
- style = Qt.SolidLine if style is None else style
- item.setLinePen(QPen(color, width, style))
- item.setRenderHint(cls.RenderAntialiased, antialiased)
- if linestyle is not None:
- item.setLineStyle(linestyle)
- if plot is not None:
- item.attach(plot)
- return item
-
- def rtti(self):
- """:return: `QwtPlotItem.Rtti_PlotMarker`"""
- return QwtPlotItem.Rtti_PlotMarker
-
- def value(self):
- """:return: Value"""
- return QPointF(self.__data.xValue, self.__data.yValue)
-
- def xValue(self):
- """:return: x Value"""
- return self.__data.xValue
-
- def yValue(self):
- """:return: y Value"""
- return self.__data.yValue
-
- def setValue(self, *args):
- """
- Set Value
-
- .. py:method:: setValue(pos):
-
- :param QPointF pos: Position
-
- .. py:method:: setValue(x, y):
-
- :param float x: x position
- :param float y: y position
- """
- if len(args) == 1:
- (pos,) = args
- self.setValue(pos.x(), pos.y())
- elif len(args) == 2:
- x, y = args
- if x != self.__data.xValue or y != self.__data.yValue:
- self.__data.xValue = x
- self.__data.yValue = y
- self.itemChanged()
- else:
- raise TypeError(
- "%s() takes 1 or 2 argument(s) (%s given)"
- % (self.__class__.__name__, len(args))
- )
-
- def setXValue(self, x):
- """
- Set X Value
-
- :param float x: x position
- """
- self.setValue(x, self.__data.yValue)
-
- def setYValue(self, y):
- """
- Set Y Value
-
- :param float y: y position
- """
- self.setValue(self.__data.xValue, y)
-
- def draw(self, painter, xMap, yMap, canvasRect):
- """
- Draw the marker
-
- :param QPainter painter: Painter
- :param qwt.scale_map.QwtScaleMap xMap: x Scale Map
- :param qwt.scale_map.QwtScaleMap yMap: y Scale Map
- :param QRectF canvasRect: Contents rectangle of the canvas in painter coordinates
- """
- pos = QPointF(
- xMap.transform(self.__data.xValue), yMap.transform(self.__data.yValue)
- )
- self.drawLines(painter, canvasRect, pos)
- if self.__data.symbol and self.__data.symbol.style() != QwtSymbol.NoSymbol:
- sz = self.__data.symbol.size()
- width, height = int(sz.width()), int(sz.height())
- clipRect = QRectF(canvasRect.adjusted(-width, -height, width, height))
- if clipRect.contains(pos):
- self.__data.symbol.drawSymbols(painter, [pos])
- self.drawLabel(painter, canvasRect, pos)
-
- def drawLines(self, painter, canvasRect, pos):
- """
- Draw the lines marker
-
- :param QPainter painter: Painter
- :param QRectF canvasRect: Contents rectangle of the canvas in painter coordinates
- :param QPointF pos: Position of the marker, translated into widget coordinates
-
- .. seealso::
-
- :py:meth:`drawLabel()`,
- :py:meth:`qwt.symbol.QwtSymbol.drawSymbol()`
- """
- if self.__data.style == self.NoLine:
- return
- painter.setPen(self.__data.pen)
- if self.__data.style in (QwtPlotMarker.HLine, QwtPlotMarker.Cross):
- y = pos.y()
- painter.drawLine(QLineF(canvasRect.left(), y, canvasRect.right() - 1.0, y))
- if self.__data.style in (QwtPlotMarker.VLine, QwtPlotMarker.Cross):
- x = pos.x()
- painter.drawLine(QLineF(x, canvasRect.top(), x, canvasRect.bottom() - 1.0))
-
- def drawLabel(self, painter, canvasRect, pos):
- """
- Align and draw the text label of the marker
-
- :param QPainter painter: Painter
- :param QRectF canvasRect: Contents rectangle of the canvas in painter coordinates
- :param QPointF pos: Position of the marker, translated into widget coordinates
-
- .. seealso::
-
- :py:meth:`drawLabel()`,
- :py:meth:`qwt.symbol.QwtSymbol.drawSymbol()`
- """
- if self.__data.label.isEmpty():
- return
- align = self.__data.labelAlignment
- alignPos = QPointF(pos)
- symbolOff = QSizeF(0, 0)
- if self.__data.style == QwtPlotMarker.VLine:
- # In VLine-style the y-position is pointless and
- # the alignment flags are relative to the canvas
- if bool(self.__data.labelAlignment & Qt.AlignTop):
- alignPos.setY(canvasRect.top())
- align &= ~Qt.AlignTop
- align |= Qt.AlignBottom
- elif bool(self.__data.labelAlignment & Qt.AlignBottom):
- # In HLine-style the x-position is pointless and
- # the alignment flags are relative to the canvas
- alignPos.setY(canvasRect.bottom() - 1)
- align &= ~Qt.AlignBottom
- align |= Qt.AlignTop
- else:
- alignPos.setY(canvasRect.center().y())
- elif self.__data.style == QwtPlotMarker.HLine:
- if bool(self.__data.labelAlignment & Qt.AlignLeft):
- alignPos.setX(canvasRect.left())
- align &= ~Qt.AlignLeft
- align |= Qt.AlignRight
- elif bool(self.__data.labelAlignment & Qt.AlignRight):
- alignPos.setX(canvasRect.right() - 1)
- align &= ~Qt.AlignRight
- align |= Qt.AlignLeft
- else:
- alignPos.setX(canvasRect.center().x())
- else:
- if self.__data.symbol and self.__data.symbol.style() != QwtSymbol.NoSymbol:
- symbolOff = QSizeF(self.__data.symbol.size()) + QSizeF(1, 1)
- symbolOff /= 2
- pw2 = self.__data.pen.widthF() / 2.0
- if pw2 == 0.0:
- pw2 = 0.5
- spacing = self.__data.spacing
- xOff = max([pw2, symbolOff.width()])
- yOff = max([pw2, symbolOff.height()])
- textSize = self.__data.label.textSize(painter.font())
- if align & Qt.AlignLeft:
- alignPos.setX(alignPos.x() - (xOff + spacing))
- if self.__data.labelOrientation == Qt.Vertical:
- alignPos.setX(alignPos.x() - textSize.height())
- else:
- alignPos.setX(alignPos.x() - textSize.width())
- elif align & Qt.AlignRight:
- alignPos.setX(alignPos.x() + xOff + spacing)
- else:
- if self.__data.labelOrientation == Qt.Vertical:
- alignPos.setX(alignPos.x() - textSize.height() / 2)
- else:
- alignPos.setX(alignPos.x() - textSize.width() / 2)
- if align & Qt.AlignTop:
- alignPos.setY(alignPos.y() - (yOff + spacing))
- if self.__data.labelOrientation != Qt.Vertical:
- alignPos.setY(alignPos.y() - textSize.height())
- elif align & Qt.AlignBottom:
- alignPos.setY(alignPos.y() + yOff + spacing)
- if self.__data.labelOrientation == Qt.Vertical:
- alignPos.setY(alignPos.y() + textSize.width())
- else:
- if self.__data.labelOrientation == Qt.Vertical:
- alignPos.setY(alignPos.y() + textSize.width() / 2)
- else:
- alignPos.setY(alignPos.y() - textSize.height() / 2)
- painter.translate(alignPos.x(), alignPos.y())
- if self.__data.labelOrientation == Qt.Vertical:
- painter.rotate(-90.0)
- textRect = QRectF(0, 0, textSize.width(), textSize.height())
- self.__data.label.draw(painter, textRect)
-
- def setLineStyle(self, style):
- """
- Set the line style
-
- :param int style: Line style
-
- Line styles:
-
- * `QwtPlotMarker.NoLine`: No line
- * `QwtPlotMarker.HLine`: A horizontal line
- * `QwtPlotMarker.VLine`: A vertical line
- * `QwtPlotMarker.Cross`: A crosshair
-
- .. seealso::
-
- :py:meth:`lineStyle()`
- """
- if style != self.__data.style:
- self.__data.style = style
- self.legendChanged()
- self.itemChanged()
-
- def lineStyle(self):
- """
- :return: the line style
-
- .. seealso::
-
- :py:meth:`setLineStyle()`
- """
- return self.__data.style
-
- def setSymbol(self, symbol):
- """
- Assign a symbol
-
- :param qwt.symbol.QwtSymbol symbol: New symbol
-
- .. seealso::
-
- :py:meth:`symbol()`
- """
- if symbol != self.__data.symbol:
- self.__data.symbol = symbol
- if symbol is not None:
- self.setLegendIconSize(symbol.boundingRect().size())
- self.legendChanged()
- self.itemChanged()
-
- def symbol(self):
- """
- :return: the symbol
-
- .. seealso::
-
- :py:meth:`setSymbol()`
- """
- return self.__data.symbol
-
- def setLabel(self, label):
- """
- Set the label
-
- :param label: Label text
- :type label: qwt.text.QwtText or str
-
- .. seealso::
-
- :py:meth:`label()`
- """
- if not isinstance(label, QwtText):
- label = QwtText(label)
- if label != self.__data.label:
- self.__data.label = label
- self.itemChanged()
-
- def label(self):
- """
- :return: the label
-
- .. seealso::
-
- :py:meth:`setLabel()`
- """
- return self.__data.label
-
- def setLabelAlignment(self, align):
- """
- Set the alignment of the label
-
- In case of `QwtPlotMarker.HLine` the alignment is relative to the
- y position of the marker, but the horizontal flags correspond to the
- canvas rectangle. In case of `QwtPlotMarker.VLine` the alignment is
- relative to the x position of the marker, but the vertical flags
- correspond to the canvas rectangle.
-
- In all other styles the alignment is relative to the marker's position.
-
- :param Qt.Alignment align: Alignment
-
- .. seealso::
-
- :py:meth:`labelAlignment()`, :py:meth:`labelOrientation()`
- """
- if align != self.__data.labelAlignment:
- self.__data.labelAlignment = align
- self.itemChanged()
-
- def labelAlignment(self):
- """
- :return: the label alignment
-
- .. seealso::
-
- :py:meth:`setLabelAlignment()`, :py:meth:`setLabelOrientation()`
- """
- return self.__data.labelAlignment
-
- def setLabelOrientation(self, orientation):
- """
- Set the orientation of the label
-
- When orientation is `Qt.Vertical` the label is rotated by 90.0 degrees
- (from bottom to top).
-
- :param Qt.Orientation orientation: Orientation of the label
-
- .. seealso::
-
- :py:meth:`labelOrientation()`, :py:meth:`setLabelAlignment()`
- """
- if orientation != self.__data.labelOrientation:
- self.__data.labelOrientation = orientation
- self.itemChanged()
-
- def labelOrientation(self):
- """
- :return: the label orientation
-
- .. seealso::
-
- :py:meth:`setLabelOrientation()`, :py:meth:`labelAlignment()`
- """
- return self.__data.labelOrientation
-
- def setSpacing(self, spacing):
- """
- Set the spacing
-
- When the label is not centered on the marker position, the spacing
- is the distance between the position and the label.
-
- :param int spacing: Spacing
-
- .. seealso::
-
- :py:meth:`spacing()`, :py:meth:`setLabelAlignment()`
- """
- if spacing < 0:
- spacing = 0
- if spacing != self.__data.spacing:
- self.__data.spacing = spacing
- self.itemChanged()
-
- def spacing(self):
- """
- :return: the spacing
-
- .. seealso::
-
- :py:meth:`setSpacing()`
- """
- return self.__data.spacing
-
- def setLinePen(self, *args):
- """
- Build and/or assigna a line pen, depending on the arguments.
-
- .. py:method:: setLinePen(color, width, style)
- :noindex:
-
- Build and assign a line pen
-
- In Qt5 the default pen width is 1.0 ( 0.0 in Qt4 ) what makes it
- non cosmetic (see `QPen.isCosmetic()`). This method signature has
- been introduced to hide this incompatibility.
-
- :param QColor color: Pen color
- :param float width: Pen width
- :param Qt.PenStyle style: Pen style
-
- .. py:method:: setLinePen(pen)
- :noindex:
-
- Specify a pen for the line.
-
- :param QPen pen: New pen
-
- .. seealso::
-
- :py:meth:`pen()`, :py:meth:`brush()`
- """
- if len(args) == 1 and isinstance(args[0], QPen):
- (pen,) = args
- elif len(args) in (1, 2, 3):
- color = args[0]
- width = 0.0
- style = Qt.SolidLine
- if len(args) > 1:
- width = args[1]
- if len(args) > 2:
- style = args[2]
- pen = QPen(color, width, style)
- self.setLinePen(pen)
- else:
- raise TypeError(
- "%s().setLinePen() takes 1, 2 or 3 argument(s) "
- "(%s given)" % (self.__class__.__name__, len(args))
- )
- if pen != self.__data.pen:
- self.__data.pen = pen
- self.legendChanged()
- self.itemChanged()
-
- def linePen(self):
- """
- :return: the line pen
-
- .. seealso::
-
- :py:meth:`setLinePen()`
- """
- return self.__data.pen
-
- def boundingRect(self):
- if self.__data.style == QwtPlotMarker.HLine:
- return QRectF(self.__data.xValue, self.__data.yValue, -1.0, 0.0)
- elif self.__data.style == QwtPlotMarker.VLine:
- return QRectF(self.__data.xValue, self.__data.yValue, 0.0, -1.0)
- else:
- return QRectF(self.__data.xValue, self.__data.yValue, 0.0, 0.0)
-
- def legendIcon(self, index, size):
- """
- :param int index: Index of the legend entry (ignored as there is only one)
- :param QSizeF size: Icon size
- :return: Icon representing the marker on the legend
-
- .. seealso::
-
- :py:meth:`qwt.plot.QwtPlotItem.setLegendIconSize()`,
- :py:meth:`qwt.plot.QwtPlotItem.legendData()`
- """
- if size.isEmpty():
- return QwtGraphic()
- icon = QwtGraphic()
- icon.setDefaultSize(size)
- icon.setRenderHint(QwtGraphic.RenderPensUnscaled, True)
- painter = QPainter(icon)
- painter.setRenderHint(
- QPainter.Antialiasing, self.testRenderHint(QwtPlotItem.RenderAntialiased)
- )
- if self.__data.style != QwtPlotMarker.NoLine:
- painter.setPen(self.__data.pen)
- if self.__data.style in (QwtPlotMarker.HLine, QwtPlotMarker.Cross):
- y = 0.5 * size.height()
- painter.drawLine(QLineF(0.0, y, size.width(), y))
- if self.__data.style in (QwtPlotMarker.VLine, QwtPlotMarker.Cross):
- x = 0.5 * size.width()
- painter.drawLine(QLineF(x, 0.0, x, size.height()))
- if self.__data.symbol:
- r = QRect(0, 0, size.width(), size.height())
- self.__data.symbol.drawSymbol(painter, r)
- return icon
diff --git a/qwt/plot_renderer.py b/qwt/plot_renderer.py
deleted file mode 100644
index d8b2640..0000000
--- a/qwt/plot_renderer.py
+++ /dev/null
@@ -1,739 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the Qwt License
-# Copyright (c) 2002 Uwe Rathmann, for the original C++ code
-# Copyright (c) 2015 Pierre Raybaut, for the Python translation/optimization
-# (see LICENSE file for more details)
-
-"""
-QwtPlotRenderer
----------------
-
-.. autoclass:: QwtPlotRenderer
- :members:
-"""
-
-import math
-import os.path as osp
-
-from qtpy.compat import getsavefilename
-from qtpy.QtCore import QObject, QRect, QRectF, QSizeF, Qt
-from qtpy.QtGui import (
- QColor,
- QImage,
- QImageWriter,
- QPageSize,
- QPaintDevice,
- QPainter,
- QPainterPath,
- QPalette,
- QPen,
- QTransform,
-)
-from qtpy.QtPrintSupport import QPrinter
-from qtpy.QtSvg import QSvgGenerator
-from qtpy.QtWidgets import QFileDialog
-
-from qwt.painter import QwtPainter
-from qwt.plot import QwtPlot
-from qwt.plot_layout import QwtPlotLayout
-from qwt.scale_draw import QwtScaleDraw
-from qwt.scale_map import QwtScaleMap
-
-
-def qwtCanvasClip(canvas, canvasRect):
- """
- The clip region is calculated in integers
- To avoid too much rounding errors better
- calculate it in target device resolution
- """
- x1 = math.ceil(canvasRect.left())
- x2 = math.floor(canvasRect.right())
- y1 = math.ceil(canvasRect.top())
- y2 = math.floor(canvasRect.bottom())
- r = QRect(x1, y1, x2 - x1 - 1, y2 - y1 - 1)
- return canvas.borderPath(r)
-
-
-class QwtPlotRenderer_PrivateData(QObject):
- def __init__(self):
- QObject.__init__(self)
-
- self.discardFlags = QwtPlotRenderer.DiscardNone
- self.layoutFlags = QwtPlotRenderer.DefaultLayout
-
-
-class QwtPlotRenderer(QObject):
- """
- Renderer for exporting a plot to a document, a printer
- or anything else, that is supported by QPainter/QPaintDevice
-
- Discard flags:
-
- * `QwtPlotRenderer.DiscardNone`: Render all components of the plot
- * `QwtPlotRenderer.DiscardBackground`: Don't render the background of the plot
- * `QwtPlotRenderer.DiscardTitle`: Don't render the title of the plot
- * `QwtPlotRenderer.DiscardLegend`: Don't render the legend of the plot
- * `QwtPlotRenderer.DiscardCanvasBackground`: Don't render the background of the canvas
- * `QwtPlotRenderer.DiscardFooter`: Don't render the footer of the plot
- * `QwtPlotRenderer.DiscardCanvasFrame`: Don't render the frame of the canvas
-
- .. note::
-
- The `QwtPlotRenderer.DiscardCanvasFrame` flag has no effect when using
- style sheets, where the frame is part of the background
-
- Layout flags:
-
- * `QwtPlotRenderer.DefaultLayout`: Use the default layout as on screen
- * `QwtPlotRenderer.FrameWithScales`: Instead of the scales a box is painted around the plot canvas, where the scale ticks are aligned to.
- """
-
- # enum DiscardFlag
- DiscardNone = 0x00
- DiscardBackground = 0x01
- DiscardTitle = 0x02
- DiscardLegend = 0x04
- DiscardCanvasBackground = 0x08
- DiscardFooter = 0x10
- DiscardCanvasFrame = 0x20
-
- # enum LayoutFlag
- DefaultLayout = 0x00
- FrameWithScales = 0x01
-
- def __init__(self, parent=None):
- QObject.__init__(self, parent)
- self.__data = QwtPlotRenderer_PrivateData()
-
- def setDiscardFlag(self, flag, on=True):
- """
- Change a flag, indicating what to discard from rendering
-
- :param int flag: Flag to change
- :param bool on: On/Off
-
- .. seealso::
-
- :py:meth:`testDiscardFlag()`, :py:meth:`setDiscardFlags()`,
- :py:meth:`discardFlags()`
- """
- if on:
- self.__data.discardFlags |= flag
- else:
- self.__data.discardFlags &= ~flag
-
- def testDiscardFlag(self, flag):
- """
- :param int flag: Flag to be tested
- :return: True, if flag is enabled.
-
- .. seealso::
-
- :py:meth:`setDiscardFlag()`, :py:meth:`setDiscardFlags()`,
- :py:meth:`discardFlags()`
- """
- return self.__data.discardFlags & flag
-
- def setDiscardFlags(self, flags):
- """
- Set the flags, indicating what to discard from rendering
-
- :param int flags: Flags
-
- .. seealso::
-
- :py:meth:`testDiscardFlag()`, :py:meth:`setDiscardFlag()`,
- :py:meth:`discardFlags()`
- """
- self.__data.discardFlags = flags
-
- def discardFlags(self):
- """
- :return: Flags, indicating what to discard from rendering
-
- .. seealso::
-
- :py:meth:`setDiscardFlag()`, :py:meth:`setDiscardFlags()`,
- :py:meth:`testDiscardFlag()`
- """
- return self.__data.discardFlags
-
- def setLayoutFlag(self, flag, on=True):
- """
- Change a layout flag
-
- :param int flag: Flag to change
-
- .. seealso::
-
- :py:meth:`testLayoutFlag()`, :py:meth:`setLayoutFlags()`,
- :py:meth:`layoutFlags()`
- """
- if on:
- self.__data.layoutFlags |= flag
- else:
- self.__data.layoutFlags &= ~flag
-
- def testLayoutFlag(self, flag):
- """
- :param int flag: Flag to be tested
- :return: True, if flag is enabled.
-
- .. seealso::
-
- :py:meth:`setLayoutFlag()`, :py:meth:`setLayoutFlags()`,
- :py:meth:`layoutFlags()`
- """
- return self.__data.layoutFlags & flag
-
- def setLayoutFlags(self, flags):
- """
- Set the layout flags
-
- :param int flags: Flags
-
- .. seealso::
-
- :py:meth:`setLayoutFlag()`, :py:meth:`testLayoutFlag()`,
- :py:meth:`layoutFlags()`
- """
- self.__data.layoutFlags = flags
-
- def layoutFlags(self):
- """
- :return: Layout flags
-
- .. seealso::
-
- :py:meth:`setLayoutFlags()`, :py:meth:`setLayoutFlag()`,
- :py:meth:`testLayoutFlag()`
- """
- return self.__data.layoutFlags
-
- def renderDocument(
- self, plot, filename, sizeMM=(300, 200), resolution=85, format_=None
- ):
- """
- Render a plot to a file
-
- The format of the document will be auto-detected from the
- suffix of the file name.
-
- :param qwt.plot.QwtPlot plot: Plot widget
- :param str fileName: Path of the file, where the document will be stored
- :param QSizeF sizeMM: Size for the document in millimeters
- :param int resolution: Resolution in dots per Inch (dpi)
- """
- if isinstance(sizeMM, tuple):
- sizeMM = QSizeF(*sizeMM)
- if format_ is None:
- ext = osp.splitext(filename)[1]
- if not ext:
- raise TypeError("Unable to determine target format from filename")
- format_ = ext[1:]
- if plot is None or sizeMM.isEmpty() or resolution <= 0:
- return
- title = plot.title().text()
- if not title:
- title = "Plot Document"
- mmToInch = 1.0 / 25.4
- size = sizeMM * mmToInch * resolution
- documentRect = QRectF(0.0, 0.0, size.width(), size.height())
- fmt = format_.lower()
- if fmt in ("pdf", "ps"):
- printer = QPrinter()
- if fmt == "pdf":
- try:
- printer.setOutputFormat(QPrinter.PdfFormat)
- except AttributeError:
- # PyQt6 on Linux
- printer.setPrinterName("")
- else:
- printer.setOutputFormat(QPrinter.PostScriptFormat)
- try:
- printer.setColorMode(QPrinter.Color)
- except AttributeError:
- # PyQt6 on Linux
- pass
- printer.setFullPage(True)
- printer.setPageSize(QPageSize(sizeMM, QPageSize.Millimeter))
- printer.setDocName(title)
- printer.setOutputFileName(filename)
- printer.setResolution(resolution)
- painter = QPainter(printer)
- self.render(plot, painter, documentRect)
- painter.end()
- elif fmt == "svg":
- generator = QSvgGenerator()
- generator.setTitle(title)
- generator.setFileName(filename)
- generator.setResolution(resolution)
- generator.setViewBox(documentRect)
- painter = QPainter(generator)
- self.render(plot, painter, documentRect)
- painter.end()
- elif fmt in QImageWriter.supportedImageFormats():
- imageRect = documentRect.toRect()
- dotsPerMeter = int(round(resolution * mmToInch * 1000.0))
- image = QImage(imageRect.size(), QImage.Format_ARGB32)
- image.setDotsPerMeterX(dotsPerMeter)
- image.setDotsPerMeterY(dotsPerMeter)
- image.fill(QColor(Qt.white).rgb())
- painter = QPainter(image)
- self.render(plot, painter, imageRect)
- painter.end()
- image.save(filename, fmt)
- else:
- raise TypeError("Unsupported file format '%s'" % fmt)
-
- def renderTo(self, plot, dest):
- """
- Render a plot to a file
-
- Supported formats are:
-
- - pdf: Portable Document Format PDF
- - ps: Postcript
- - svg: Scalable Vector Graphics SVG
- - all image formats supported by Qt, see QImageWriter.supportedImageFormats()
-
- Scalable vector graphic formats like PDF or SVG are superior to
- raster graphics formats.
-
- :param qwt.plot.QwtPlot plot: Plot widget
- :param dest: QPaintDevice, QPrinter or QSvgGenerator instance
-
- .. seealso::
-
- :py:meth:`render()`,
- :py:meth:`qwt.painter.QwtPainter.setRoundingAlignment()`
- """
- if isinstance(dest, QPaintDevice):
- w = dest.width()
- h = dest.height()
- rect = QRectF(0, 0, w, h)
- elif isinstance(dest, QPrinter):
- w = dest.width()
- h = dest.height()
- rect = QRectF(0, 0, w, h)
- aspect = rect.width() / rect.height()
- if aspect < 1.0:
- rect.setHeight(aspect * rect.width())
- elif isinstance(dest, QSvgGenerator):
- rect = dest.viewBoxF()
- if rect.isEmpty():
- rect.setRect(0, 0, dest.width(), dest.height())
- if rect.isEmpty():
- rect.setRect(0, 0, 800, 600)
- else:
- raise TypeError("Unsupported destination type %s" % type(dest))
- p = QPainter(dest)
- self.render(plot, p, rect)
-
- def render(self, plot, painter, plotRect):
- """
- Paint the contents of a QwtPlot instance into a given rectangle.
-
- :param qwt.plot.QwtPlot plot: Plot to be rendered
- :param QPainter painter: Painter
- :param str format: Format for the document
- :param QRectF plotRect: Bounding rectangle
-
- .. seealso::
-
- :py:meth:`renderDocument()`, :py:meth:`renderTo()`,
- :py:meth:`qwt.painter.QwtPainter.setRoundingAlignment()`
- """
- if (
- painter == 0
- or not painter.isActive()
- or not plotRect.isValid()
- or plot.size().isNull()
- ):
- return
- if not self.__data.discardFlags & self.DiscardBackground:
- QwtPainter.drawBackground(painter, plotRect, plot)
-
- # The layout engine uses the same methods as they are used
- # by the Qt layout system. Therefore we need to calculate the
- # layout in screen coordinates and paint with a scaled painter.
- transform = QTransform()
- transform.scale(
- float(painter.device().logicalDpiX()) / plot.logicalDpiX(),
- float(painter.device().logicalDpiY()) / plot.logicalDpiY(),
- )
-
- invtrans, _ok = transform.inverted()
- layoutRect = invtrans.mapRect(plotRect)
- if not (self.__data.discardFlags & self.DiscardBackground):
- mg = plot.contentsMargins()
- layoutRect.adjust(mg.left(), mg.top(), -mg.right(), -mg.bottom())
-
- layout = plot.plotLayout()
- baseLineDists = canvasMargins = [None] * len(QwtPlot.AXES)
-
- for axisId in QwtPlot.AXES:
- canvasMargins[axisId] = layout.canvasMargin(axisId)
- if self.__data.layoutFlags & self.FrameWithScales:
- scaleWidget = plot.axisWidget(axisId)
- if scaleWidget:
- mgn = scaleWidget.contentsMargins()
- baseLineDists[axisId] = max(
- [mgn.left(), mgn.top(), mgn.right(), mgn.bottom()]
- )
- scaleWidget.setMargin(0)
- if not plot.axisEnabled(axisId):
- # When we have a scale the frame is painted on
- # the position of the backbone - otherwise we
- # need to introduce a margin around the canvas
- if axisId == QwtPlot.yLeft:
- layoutRect.adjust(1, 0, 0, 0)
- elif axisId == QwtPlot.yRight:
- layoutRect.adjust(0, 0, -1, 0)
- elif axisId == QwtPlot.xTop:
- layoutRect.adjust(0, 1, 0, 0)
- elif axisId == QwtPlot.xBottom:
- layoutRect.adjust(0, 0, 0, -1)
-
- # Calculate the layout for the document.
- layoutOptions = QwtPlotLayout.IgnoreScrollbars
-
- if (
- self.__data.layoutFlags & self.FrameWithScales
- or self.__data.discardFlags & self.DiscardCanvasFrame
- ):
- layoutOptions |= QwtPlotLayout.IgnoreFrames
-
- if self.__data.discardFlags & self.DiscardLegend:
- layoutOptions |= QwtPlotLayout.IgnoreLegend
- if self.__data.discardFlags & self.DiscardTitle:
- layoutOptions |= QwtPlotLayout.IgnoreTitle
- if self.__data.discardFlags & self.DiscardFooter:
- layoutOptions |= QwtPlotLayout.IgnoreFooter
-
- layout.activate(plot, layoutRect, layoutOptions)
-
- maps = self.buildCanvasMaps(plot, layout.canvasRect())
- if self.updateCanvasMargins(plot, layout.canvasRect(), maps):
- # recalculate maps and layout, when the margins
- # have been changed
- layout.activate(plot, layoutRect, layoutOptions)
- maps = self.buildCanvasMaps(plot, layout.canvasRect())
-
- painter.save()
- painter.setWorldTransform(transform, True)
-
- self.renderCanvas(plot, painter, layout.canvasRect(), maps)
-
- if (
- not self.__data.discardFlags & self.DiscardTitle
- ) and plot.titleLabel().text():
- self.renderTitle(plot, painter, layout.titleRect())
-
- if (
- not self.__data.discardFlags & self.DiscardFooter
- ) and plot.titleLabel().text():
- self.renderFooter(plot, painter, layout.footerRect())
-
- if (
- not self.__data.discardFlags & self.DiscardLegend
- ) and plot.titleLabel().text():
- self.renderLegend(plot, painter, layout.legendRect())
-
- for axisId in QwtPlot.AXES:
- scaleWidget = plot.axisWidget(axisId)
- if scaleWidget:
- mgn = scaleWidget.contentsMargins()
- baseDist = max([mgn.left(), mgn.top(), mgn.right(), mgn.bottom()])
- startDist, endDist = scaleWidget.getBorderDistHint()
- self.renderScale(
- plot,
- painter,
- axisId,
- startDist,
- endDist,
- baseDist,
- layout.scaleRect(axisId),
- )
-
- painter.restore()
-
- for axisId in QwtPlot.AXES:
- if self.__data.layoutFlags & self.FrameWithScales:
- scaleWidget = plot.axisWidget(axisId)
- if scaleWidget:
- scaleWidget.setMargin(baseLineDists[axisId])
- layout.setCanvasMargin(canvasMargins[axisId])
-
- layout.invalidate()
-
- def renderTitle(self, plot, painter, rect):
- """
- Render the title into a given rectangle.
-
- :param qwt.plot.QwtPlot plot: Plot widget
- :param QPainter painter: Painter
- :param QRectF rect: Bounding rectangle
- """
- painter.setFont(plot.titleLabel().font())
- color = plot.titleLabel().palette().color(QPalette.Active, QPalette.Text)
- painter.setPen(color)
- plot.titleLabel().text().draw(painter, rect)
-
- def renderFooter(self, plot, painter, rect):
- """
- Render the footer into a given rectangle.
-
- :param qwt.plot.QwtPlot plot: Plot widget
- :param QPainter painter: Painter
- :param QRectF rect: Bounding rectangle
- """
- painter.setFont(plot.footerLabel().font())
- color = plot.footerLabel().palette().color(QPalette.Active, QPalette.Text)
- painter.setPen(color)
- plot.footerLabel().text().draw(painter, rect)
-
- def renderLegend(self, plot, painter, rect):
- """
- Render the legend into a given rectangle.
-
- :param qwt.plot.QwtPlot plot: Plot widget
- :param QPainter painter: Painter
- :param QRectF rect: Bounding rectangle
- """
- if plot.legend():
- fillBackground = not self.__data.discardFlags & self.DiscardBackground
- plot.legend().renderLegend(painter, rect, fillBackground)
-
- def renderScale(self, plot, painter, axisId, startDist, endDist, baseDist, rect):
- """
- Paint a scale into a given rectangle.
- Paint the scale into a given rectangle.
-
- :param qwt.plot.QwtPlot plot: Plot widget
- :param QPainter painter: Painter
- :param int axisId: Axis
- :param int startDist: Start border distance
- :param int endDist: End border distance
- :param int baseDist: Base distance
- :param QRectF rect: Bounding rectangle
- """
- if not plot.axisEnabled(axisId):
- return
- scaleWidget = plot.axisWidget(axisId)
- if scaleWidget.isColorBarEnabled() and scaleWidget.colorBarWidth() > 0:
- scaleWidget.drawColorBar(painter, scaleWidget.colorBarRect(rect))
- baseDist += scaleWidget.colorBarWidth() + scaleWidget.spacing()
- painter.save()
- if axisId == QwtPlot.yLeft:
- x = rect.right() - 1.0 - baseDist
- y = rect.y() + startDist
- w = rect.height() - startDist - endDist
- align = QwtScaleDraw.LeftScale
- elif axisId == QwtPlot.yRight:
- x = rect.left() + baseDist
- y = rect.y() + startDist
- w = rect.height() - startDist - endDist
- align = QwtScaleDraw.RightScale
- elif axisId == QwtPlot.xTop:
- x = rect.left() + startDist
- y = rect.bottom() - 1.0 - baseDist
- w = rect.width() - startDist - endDist
- align = QwtScaleDraw.TopScale
- else: # QwtPlot.xBottom
- x = rect.left() + startDist
- y = rect.top() + baseDist
- w = rect.width() - startDist - endDist
- align = QwtScaleDraw.BottomScale
-
- scaleWidget.drawTitle(painter, align, rect)
- painter.setFont(scaleWidget.font())
- sd = scaleWidget.scaleDraw()
- sdPos = sd.pos()
- sdLength = sd.length()
- sd.move(x, y)
- sd.setLength(w)
- palette = scaleWidget.palette()
- palette.setCurrentColorGroup(QPalette.Active)
- sd.draw(painter, palette)
- sd.move(sdPos)
- sd.setLength(sdLength)
- painter.restore()
-
- def renderCanvas(self, plot, painter, canvasRect, maps):
- """
- Render the canvas into a given rectangle.
-
- :param qwt.plot.QwtPlot plot: Plot widget
- :param QPainter painter: Painter
- :param QRectF rect: Bounding rectangle
- :param qwt.scale_map.QwtScaleMap maps: mapping between plot and paint device coordinates
- """
- canvas = plot.canvas()
- r = canvasRect.adjusted(0.0, 0.0, -1.0, 1.0)
- if self.__data.layoutFlags & self.FrameWithScales:
- painter.save()
- r.adjust(-1.0, -1.0, 1.0, 1.0)
- painter.setPen(QPen(Qt.black))
- if not (self.__data.discardFlags & self.DiscardCanvasBackground):
- bgBrush = canvas.palette().brush(plot.backgroundRole())
- painter.setBrush(bgBrush)
- painter.drawRect(r)
- painter.restore()
- painter.save()
- painter.setClipRect(canvasRect)
- plot.drawItems(painter, canvasRect, maps)
- painter.restore()
- elif canvas.testAttribute(Qt.WA_StyledBackground):
- clipPath = QPainterPath()
- painter.save()
- if not self.__data.discardFlags & self.DiscardCanvasBackground:
- QwtPainter.drawBackground(painter, r, canvas)
- clipPath = qwtCanvasClip(canvas, canvasRect)
- painter.restore()
- painter.save()
- if clipPath.isEmpty():
- painter.setClipRect(canvasRect)
- else:
- painter.setClipPath(clipPath)
- plot.drawItems(painter, canvasRect, maps)
- painter.restore()
- else:
- clipPath = QPainterPath()
- frameWidth = 0
- if not self.__data.discardFlags & self.DiscardCanvasFrame:
- frameWidth = canvas.frameWidth()
- clipPath = qwtCanvasClip(canvas, canvasRect)
- innerRect = canvasRect.adjusted(
- frameWidth, frameWidth, -frameWidth, -frameWidth
- )
- painter.save()
- if clipPath.isEmpty():
- painter.setClipRect(innerRect)
- else:
- painter.setClipPath(clipPath)
- if not self.__data.discardFlags & self.DiscardCanvasBackground:
- QwtPainter.drawBackground(painter, innerRect, canvas)
- plot.drawItems(painter, innerRect, maps)
- painter.restore()
- if frameWidth > 0:
- painter.save()
- frameStyle = canvas.frameShadow() | canvas.frameShape()
- radius = canvas.borderRadius()
- if radius > 0.0:
- QwtPainter.drawRoundedFrame(
- painter,
- canvasRect,
- radius,
- radius,
- canvas.palette(),
- frameWidth,
- frameStyle,
- )
- else:
- midLineWidth = canvas.midLineWidth()
- QwtPainter.drawFrame(
- painter,
- canvasRect,
- canvas.palette(),
- canvas.foregroundRole(),
- frameWidth,
- midLineWidth,
- frameStyle,
- )
- painter.restore()
-
- def buildCanvasMaps(self, plot, canvasRect):
- """
- Calculated the scale maps for rendering the canvas
-
- :param qwt.plot.QwtPlot plot: Plot widget
- :param QRectF canvasRect: Target rectangle
- :return: Calculated scale maps
- """
- maps = []
- for axisId in QwtPlot.AXES:
- map_ = QwtScaleMap()
- map_.setTransformation(plot.axisScaleEngine(axisId).transformation())
- sd = plot.axisScaleDiv(axisId)
- map_.setScaleInterval(sd.lowerBound(), sd.upperBound())
- if plot.axisEnabled(axisId):
- s = plot.axisWidget(axisId)
- scaleRect = plot.plotLayout().scaleRect(axisId)
- if axisId in (QwtPlot.xTop, QwtPlot.xBottom):
- from_ = scaleRect.left() + s.startBorderDist()
- to = scaleRect.right() - s.endBorderDist()
- else:
- from_ = scaleRect.bottom() - s.endBorderDist()
- to = scaleRect.top() + s.startBorderDist()
- else:
- margin = 0
- if not plot.plotLayout().alignCanvasToScale(axisId):
- margin = plot.plotLayout().canvasMargin(axisId)
- if axisId in (QwtPlot.yLeft, QwtPlot.yRight):
- from_ = canvasRect.bottom() - margin
- to = canvasRect.top() + margin
- else:
- from_ = canvasRect.left() + margin
- to = canvasRect.right() - margin
- map_.setPaintInterval(from_, to)
- maps.append(map_)
- return maps
-
- def updateCanvasMargins(self, plot, canvasRect, maps):
- margins = plot.getCanvasMarginsHint(maps, canvasRect)
- marginsChanged = False
- for axisId in QwtPlot.AXES:
- if margins[axisId] >= 0.0:
- m = math.ceil(margins[axisId])
- plot.plotLayout().setCanvasMargin(m, axisId)
- marginsChanged = True
- return marginsChanged
-
- def exportTo(self, plot, documentname, sizeMM=None, resolution=85):
- """
- Execute a file dialog and render the plot to the selected file
-
- :param qwt.plot.QwtPlot plot: Plot widget
- :param str documentName: Default document name
- :param QSizeF sizeMM: Size for the document in millimeters
- :param int resolution: Resolution in dots per Inch (dpi)
- :return: True, when exporting was successful
-
- .. seealso::
-
- :py:meth:`renderDocument()`
- """
- if plot is None:
- return
- if sizeMM is None:
- sizeMM = QSizeF(300, 200)
- filename = documentname
- imageFormats = QImageWriter.supportedImageFormats()
- filter_ = [
- "PDF documents (*.pdf)",
- "SVG documents (*.svg)",
- "Postscript documents (*.ps)",
- ]
- if imageFormats:
- imageFilter = "Images"
- imageFilter += " ("
- for idx, fmt in enumerate(imageFormats):
- if idx > 0:
- imageFilter += " "
- imageFilter += "*." + str(fmt)
- imageFilter += ")"
- filter_ += [imageFilter]
- filename, _s = getsavefilename(
- plot,
- "Export File Name",
- filename,
- ";;".join(filter_),
- options=QFileDialog.DontConfirmOverwrite,
- )
- if not filename:
- return False
- self.renderDocument(plot, filename, sizeMM, resolution)
- return True
- return True
diff --git a/qwt/plot_series.py b/qwt/plot_series.py
deleted file mode 100644
index e0f21f8..0000000
--- a/qwt/plot_series.py
+++ /dev/null
@@ -1,384 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the Qwt License
-# Copyright (c) 2002 Uwe Rathmann, for the original C++ code
-# Copyright (c) 2015 Pierre Raybaut, for the Python translation/optimization
-# (see LICENSE file for more details)
-
-"""
-Plotting series item
---------------------
-
-QwtPlotSeriesItem
-~~~~~~~~~~~~~~~~~
-
-.. autoclass:: QwtPlotSeriesItem
- :members:
-
-QwtSeriesData
-~~~~~~~~~~~~~
-
-.. autoclass:: QwtSeriesData
- :members:
-
-QwtPointArrayData
-~~~~~~~~~~~~~~~~~
-
-.. autoclass:: QwtPointArrayData
- :members:
-
-QwtSeriesStore
-~~~~~~~~~~~~~~
-
-.. autoclass:: QwtSeriesStore
- :members:
-"""
-
-import numpy as np
-from qtpy.QtCore import QPointF, QRectF, Qt
-
-from qwt.plot import QwtPlotItem, QwtPlotItem_PrivateData
-from qwt.text import QwtText
-
-
-class QwtPlotSeriesItem_PrivateData(QwtPlotItem_PrivateData):
- def __init__(self):
- QwtPlotItem_PrivateData.__init__(self)
- self.orientation = Qt.Horizontal
-
-
-class QwtPlotSeriesItem(QwtPlotItem):
- """
- Base class for plot items representing a series of samples
- """
-
- def __init__(self, title):
- if not isinstance(title, QwtText):
- title = QwtText(title)
- QwtPlotItem.__init__(self, title)
- self.__data = QwtPlotSeriesItem_PrivateData()
- self.setItemInterest(QwtPlotItem.ScaleInterest, True)
-
- def setOrientation(self, orientation):
- """
- Set the orientation of the item. Default is `Qt.Horizontal`.
-
- The `orientation()` might be used in specific way by a plot item.
- F.e. a QwtPlotCurve uses it to identify how to display the curve
- int `QwtPlotCurve.Steps` or `QwtPlotCurve.Sticks` style.
-
- .. seealso::
-
- :py:meth`orientation()`
- """
- if self.__data.orientation != orientation:
- self.__data.orientation = orientation
- self.legendChanged()
- self.itemChanged()
-
- def orientation(self):
- """
- :return: Orientation of the plot item
-
- .. seealso::
-
- :py:meth`setOrientation()`
- """
- return self.__data.orientation
-
- def draw(self, painter, xMap, yMap, canvasRect):
- """
- Draw the complete series
-
- :param QPainter painter: Painter
- :param qwt.scale_map.QwtScaleMap xMap: Maps x-values into pixel coordinates.
- :param qwt.scale_map.QwtScaleMap yMap: Maps y-values into pixel coordinates.
- :param QRectF canvasRect: Contents rectangle of the canvas
- """
- self.drawSeries(painter, xMap, yMap, canvasRect, 0, -1)
-
- def drawSeries(self, painter, xMap, yMap, canvasRect, from_, to):
- """
- Draw a subset of the samples
-
- :param QPainter painter: Painter
- :param qwt.scale_map.QwtScaleMap xMap: Maps x-values into pixel coordinates.
- :param qwt.scale_map.QwtScaleMap yMap: Maps y-values into pixel coordinates.
- :param QRectF canvasRect: Contents rectangle of the canvas
- :param int from_: Index of the first point to be painted
- :param int to: Index of the last point to be painted. If to < 0 the curve will be painted to its last point.
-
- .. seealso::
-
- This method is implemented in `qwt.plot_curve.QwtPlotCurve`
- """
- raise NotImplementedError
-
- def boundingRect(self):
- return self.dataRect() # dataRect method is implemented in QwtSeriesStore
-
- def updateScaleDiv(self, xScaleDiv, yScaleDiv):
- rect = QRectF(
- xScaleDiv.lowerBound(),
- yScaleDiv.lowerBound(),
- xScaleDiv.range(),
- yScaleDiv.range(),
- )
- self.setRectOfInterest(
- rect
- ) # setRectOfInterest method is implemented in QwtSeriesData
-
- def dataChanged(self):
- self.itemChanged()
-
-
-class QwtSeriesData(object):
- """
- Abstract interface for iterating over samples
-
- `PythonQwt` offers several implementations of the QwtSeriesData API,
- but in situations, where data of an application specific format
- needs to be displayed, without having to copy it, it is recommended
- to implement an individual data access.
-
- A subclass of `QwtSeriesData` must implement:
-
- - size():
-
- Should return number of data points.
-
- - sample()
-
- Should return values x and y values of the sample at specific position
- as QPointF object.
-
- - boundingRect()
-
- Should return the bounding rectangle of the data series.
- It is used for autoscaling and might help certain algorithms for
- displaying the data.
- The member `_boundingRect` is intended for caching the calculated
- rectangle.
- """
-
- def __init__(self):
- self._boundingRect = QRectF(0.0, 0.0, -1.0, -1.0)
-
- def setRectOfInterest(self, rect):
- """
- Set a the "rect of interest"
-
- QwtPlotSeriesItem defines the current area of the plot canvas
- as "rectangle of interest" ( QwtPlotSeriesItem::updateScaleDiv() ).
- It can be used to implement different levels of details.
-
- The default implementation does nothing.
-
- :param QRectF rect: Rectangle of interest
- """
- pass
-
- def size(self):
- """
- :return: Number of samples
- """
- pass
-
- def sample(self, i):
- """
- Return a sample
-
- :param int i: Index
- :return: Sample at position i
- """
- pass
-
- def boundingRect(self):
- """
- Calculate the bounding rect of all samples
-
- The bounding rect is necessary for autoscaling and can be used
- for a couple of painting optimizations.
-
- :return: Bounding rectangle
- """
- pass
-
-
-class QwtPointArrayData(QwtSeriesData):
- """
- Interface for iterating over two array objects
-
- .. py:class:: QwtCQwtPointArrayDataolorMap(x, y, [size=None])
-
- :param x: Array of x values
- :type x: list or tuple or numpy.array
- :param y: Array of y values
- :type y: list or tuple or numpy.array
- :param int size: Size of the x and y arrays
- :param bool finite: if True, keep only finite array elements (remove all infinity and not a number values), otherwise do not filter array elements
- """
-
- def __init__(self, x=None, y=None, size=None, finite=None):
- QwtSeriesData.__init__(self)
- if x is None and y is not None:
- x = np.arange(len(y))
- elif y is None and x is not None:
- y = x
- x = np.arange(len(y))
- elif x is None and y is None:
- x = np.array([])
- y = np.array([])
- if isinstance(x, (tuple, list)):
- x = np.array(x)
- if isinstance(y, (tuple, list)):
- y = np.array(y)
- if size is not None:
- x = np.resize(x, (size,))
- y = np.resize(y, (size,))
- if len(x) != len(y):
- minlen = min(len(x), len(y))
- x = np.resize(x, (minlen,))
- y = np.resize(y, (minlen,))
- if finite if finite is not None else True:
- indexes = np.logical_and(np.isfinite(x), np.isfinite(y))
- self.__x = x[indexes]
- self.__y = y[indexes]
- else:
- self.__x = x
- self.__y = y
-
- def boundingRect(self):
- """
- Calculate the bounding rectangle
-
- The bounding rectangle is calculated once by iterating over all
- points and is stored for all following requests.
-
- :return: Bounding rectangle
- """
- xmin = self.__x.min()
- xmax = self.__x.max()
- ymin = self.__y.min()
- ymax = self.__y.max()
- return QRectF(xmin, ymin, xmax - xmin, ymax - ymin)
-
- def size(self):
- """
- :return: Size of the data set
- """
- return min([self.__x.size, self.__y.size])
-
- def sample(self, index):
- """
- :param int index: Index
- :return: Sample at position `index`
- """
- return QPointF(self.__x[index], self.__y[index])
-
- def xData(self):
- """
- :return: Array of the x-values
- """
- return self.__x
-
- def yData(self):
- """
- :return: Array of the y-values
- """
- return self.__y
-
-
-class QwtSeriesStore(object):
- """
- Class storing a `QwtSeriesData` object
-
- `QwtSeriesStore` and `QwtPlotSeriesItem` are intended as base classes for
- all plot items iterating over a series of samples.
- """
-
- def __init__(self):
- self.__series = None
-
- def setData(self, series):
- """
- Assign a series of samples
-
- :param qwt.plot_series.QwtSeriesData series: Data
-
- .. warning::
-
- The item takes ownership of the data object, deleting it
- when its not used anymore.
- """
- if self.__series != series:
- self.__series = series
- self.dataChanged()
-
- def dataChanged(self):
- raise NotImplementedError
-
- def data(self):
- """
- :return: the series data
- """
- return self.__series
-
- def sample(self, index):
- """
- :param int index: Index
- :return: Sample at position index
- """
- if self.__series:
- return self.__series.sample(index)
- else:
- return
-
- def dataSize(self):
- """
- :return: Number of samples of the series
-
- .. seealso::
-
- :py:meth:`setData()`,
- :py:meth:`qwt.plot_series.QwtSeriesData.size()`
- """
- if self.__series is None:
- return 0
- return self.__series.size()
-
- def dataRect(self):
- """
- :return: Bounding rectangle of the series or an invalid rectangle, when no series is stored
-
- .. seealso::
-
- :py:meth:`qwt.plot_series.QwtSeriesData.boundingRect()`
- """
- if self.__series is None or self.dataSize() == 0:
- return QRectF(1.0, 1.0, -2.0, -2.0)
- return self.__series.boundingRect()
-
- def setRectOfInterest(self, rect):
- """
- Set a the "rect of interest" for the series
-
- :param QRectF rect: Rectangle of interest
-
- .. seealso::
-
- :py:meth:`qwt.plot_series.QwtSeriesData.setRectOfInterest()`
- """
- if self.__series:
- self.__series.setRectOfInterest(rect)
-
- def swapData(self, series):
- """
- Replace a series without deleting the previous one
-
- :param qwt.plot_series.QwtSeriesData series: New series
- :return: Previously assigned series
- """
- swappedSeries = self.__series
- self.__series = series
- return swappedSeries
diff --git a/qwt/qthelpers.py b/qwt/qthelpers.py
deleted file mode 100644
index 283ccf1..0000000
--- a/qwt/qthelpers.py
+++ /dev/null
@@ -1,52 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the MIT License
-# Copyright (c) 2015 Pierre Raybaut
-# (see LICENSE file for more details)
-
-"""Qt helpers"""
-
-import os
-
-from qtpy import QtCore as QC
-from qtpy import QtGui as QG
-from qtpy import QtWidgets as QW
-
-QT_API = os.environ["QT_API"]
-
-
-def qcolor_from_str(color, default):
- """Return QColor object from str
-
- :param color: Input color
- :type color: QColor or str or None
- :param QColor default: Default color (returned if color is None)
-
- If color is already a QColor instance, simply return color.
- If color is None, return default color.
- If color is neither an str nor a QColor instance nor None, raise TypeError.
- """
- if color is None:
- return default
- elif isinstance(color, str):
- try:
- return getattr(QC.Qt, color)
- except AttributeError:
- raise ValueError("Unknown Qt color %r" % color)
- else:
- try:
- return QG.QColor(color)
- except TypeError:
- raise TypeError("Invalid color %r" % color)
-
-
-def take_screenshot(widget, path, size=None, quit=True):
- """Take screenshot of widget"""
- if size is not None:
- widget.resize(*size)
- widget.show()
- QW.QApplication.processEvents()
- pixmap = widget.grab()
- pixmap.save(path)
- if quit:
- QC.QTimer.singleShot(0, QW.QApplication.instance().quit)
diff --git a/qwt/scale_div.py b/qwt/scale_div.py
deleted file mode 100644
index 9ff2e5e..0000000
--- a/qwt/scale_div.py
+++ /dev/null
@@ -1,316 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the Qwt License
-# Copyright (c) 2002 Uwe Rathmann, for the original C++ code
-# Copyright (c) 2015 Pierre Raybaut, for the Python translation/optimization
-# (see LICENSE file for more details)
-
-"""
-QwtScaleDiv
------------
-
-.. autoclass:: QwtScaleDiv
- :members:
-"""
-
-import copy
-
-from qwt.interval import QwtInterval
-
-
-class QwtScaleDiv(object):
- """
- A class representing a scale division
-
- A Qwt scale is defined by its boundaries and 3 list
- for the positions of the major, medium and minor ticks.
-
- The `upperLimit()` might be smaller than the `lowerLimit()`
- to indicate inverted scales.
-
- Scale divisions can be calculated from a `QwtScaleEngine`.
-
- .. seealso::
-
- :py:meth:`qwt.scale_engine.QwtScaleEngine.divideScale()`,
- :py:meth:`qwt.plot.QwtPlot.setAxisScaleDiv()`
-
- Scale tick types:
-
- * `QwtScaleDiv.NoTick`: No ticks
- * `QwtScaleDiv.MinorTick`: Minor ticks
- * `QwtScaleDiv.MediumTick`: Medium ticks
- * `QwtScaleDiv.MajorTick`: Major ticks
- * `QwtScaleDiv.NTickTypes`: Number of valid tick types
-
- .. py:class:: QwtScaleDiv()
-
- Basic constructor. Lower bound = Upper bound = 0.
-
- .. py:class:: QwtScaleDiv(interval, ticks)
- :noindex:
-
- :param qwt.interval.QwtInterval interval: Interval
- :param list ticks: list of major, medium and minor ticks
-
- .. py:class:: QwtScaleDiv(lowerBound, upperBound)
- :noindex:
-
- :param float lowerBound: First boundary
- :param float upperBound: Second boundary
-
- .. py:class:: QwtScaleDiv(lowerBound, upperBound, ticks)
- :noindex:
-
- :param float lowerBound: First boundary
- :param float upperBound: Second boundary
- :param list ticks: list of major, medium and minor ticks
-
- .. py:class:: QwtScaleDiv(lowerBound, upperBound, minorTicks, mediumTicks, majorTicks)
- :noindex:
-
- :param float lowerBound: First boundary
- :param float upperBound: Second boundary
- :param list minorTicks: list of minor ticks
- :param list mediumTicks: list of medium ticks
- :param list majorTicks: list of major ticks
-
- .. note::
-
- lowerBound might be greater than upperBound for inverted scales
- """
-
- # enum TickType
- NoTick = -1
- MinorTick, MediumTick, MajorTick, NTickTypes = list(range(4))
-
- def __init__(self, *args):
- self.__ticks = None
- if len(args) == 2 and isinstance(args[1], (tuple, list)):
- interval, ticks = args
- self.__lowerBound = interval.minValue()
- self.__upperBound = interval.maxValue()
- self.__ticks = ticks[:]
- elif len(args) == 2:
- self.__lowerBound, self.__upperBound = args
- elif len(args) == 3:
- self.__lowerBound, self.__upperBound, ticks = args
- self.__ticks = ticks[:]
- elif len(args) == 5:
- (
- self.__lowerBound,
- self.__upperBound,
- minorTicks,
- mediumTicks,
- majorTicks,
- ) = args
- self.__ticks = [0] * self.NTickTypes
- self.__ticks[self.MinorTick] = minorTicks
- self.__ticks[self.MediumTick] = mediumTicks
- self.__ticks[self.MajorTick] = majorTicks
- elif len(args) == 0:
- self.__lowerBound, self.__upperBound = 0.0, 0.0
- else:
- raise TypeError(
- "%s() takes 0, 2, 3 or 5 argument(s) (%s given)"
- % (self.__class__.__name__, len(args))
- )
-
- def setInterval(self, *args):
- """
- Change the interval
-
- .. py:method:: setInterval(lowerBound, upperBound)
- :noindex:
-
- :param float lowerBound: First boundary
- :param float upperBound: Second boundary
-
- .. py:method:: setInterval(interval)
- :noindex:
-
- :param qwt.interval.QwtInterval interval: Interval
-
- .. note::
-
- lowerBound might be greater than upperBound for inverted scales
- """
- if len(args) == 2:
- self.__lowerBound, self.__upperBound = args
- elif len(args) == 1:
- (interval,) = args
- self.__lowerBound = interval.minValue()
- self.__upperBound = interval.maxValue()
- else:
- raise TypeError(
- "%s().setInterval() takes 1 or 2 argument(s) (%s "
- "given)" % (self.__class__.__name__, len(args))
- )
-
- def interval(self):
- """
- :return: Interval
- """
- return QwtInterval(self.__lowerBound, self.__upperBound)
-
- def setLowerBound(self, lowerBound):
- """
- Set the first boundary
-
- :param float lowerBound: First boundary
-
- .. seealso::
-
- :py:meth:`lowerBound()`, :py:meth:`setUpperBound()`
- """
- self.__lowerBound = lowerBound
-
- def lowerBound(self):
- """
- :return: the first boundary
-
- .. seealso::
-
- :py:meth:`upperBound()`
- """
- return self.__lowerBound
-
- def setUpperBound(self, upperBound):
- """
- Set the second boundary
-
- :param float lowerBound: Second boundary
-
- .. seealso::
-
- :py:meth:`upperBound()`, :py:meth:`setLowerBound()`
- """
- self.__upperBound = upperBound
-
- def upperBound(self):
- """
- :return: the second boundary
-
- .. seealso::
-
- :py:meth:`lowerBound()`
- """
- return self.__upperBound
-
- def range(self):
- """
- :return: upperBound() - lowerBound()
- """
- return self.__upperBound - self.__lowerBound
-
- def __eq__(self, other):
- if self.__ticks is None:
- return False
- if (
- self.__lowerBound != other.__lowerBound
- or self.__upperBound != other.__upperBound
- ):
- return False
- return self.__ticks == other.__ticks
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
- def isEmpty(self):
- """
- Check if the scale division is empty( lowerBound() == upperBound() )
- """
- return self.__lowerBound == self.__upperBound
-
- def isIncreasing(self):
- """
- Check if the scale division is increasing( lowerBound() <= upperBound() )
- """
- return self.__lowerBound <= self.__upperBound
-
- def contains(self, value):
- """
- Return if a value is between lowerBound() and upperBound()
-
- :param float value: Value
- :return: True/False
- """
- lb = self.__lowerBound
- ub = self.__upperBound
- if lb <= ub:
- return lb <= value <= ub
- return ub <= value <= lb
-
- def invert(self):
- """
- Invert the scale division
-
- .. seealso::
-
- :py:meth:`inverted()`
- """
- (self.__lowerBound, self.__upperBound) = self.__upperBound, self.__lowerBound
- for index in range(self.NTickTypes):
- self.__ticks[index].reverse()
-
- def inverted(self):
- """
- :return: A scale division with inverted boundaries and ticks
-
- .. seealso::
-
- :py:meth:`invert()`
- """
- other = copy.deepcopy(self)
- other.invert()
- return other
-
- def bounded(self, lowerBound, upperBound):
- """
- Return a scale division with an interval [lowerBound, upperBound]
- where all ticks outside this interval are removed
-
- :param float lowerBound: First boundary
- :param float lowerBound: Second boundary
- :return: Scale division with all ticks inside of the given interval
-
- .. note::
-
- lowerBound might be greater than upperBound for inverted scales
- """
- min_ = min([self.__lowerBound, self.__upperBound])
- max_ = max([self.__lowerBound, self.__upperBound])
- sd = QwtScaleDiv()
- sd.setInterval(lowerBound, upperBound)
- for tickType in range(self.NTickTypes):
- sd.setTicks(
- tickType,
- [
- tick
- for tick in self.__ticks[tickType]
- if tick >= min_ and tick <= max_
- ],
- )
- return sd
-
- def setTicks(self, tickType, ticks):
- """
- Assign ticks
-
- :param int type: MinorTick, MediumTick or MajorTick
- :param list ticks: Values of the tick positions
- """
- if tickType in range(self.NTickTypes):
- self.__ticks[tickType] = ticks
-
- def ticks(self, tickType):
- """
- Return a list of ticks
-
- :param int type: MinorTick, MediumTick or MajorTick
- :return: Tick list
- """
- if self.__ticks is not None and tickType in range(self.NTickTypes):
- return self.__ticks[tickType]
- else:
- return []
diff --git a/qwt/scale_draw.py b/qwt/scale_draw.py
deleted file mode 100644
index dd049ac..0000000
--- a/qwt/scale_draw.py
+++ /dev/null
@@ -1,1342 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the Qwt License
-# Copyright (c) 2002 Uwe Rathmann, for the original C++ code
-# Copyright (c) 2015 Pierre Raybaut, for the Python translation/optimization
-# (see LICENSE file for more details)
-
-"""
-QwtAbstractScaleDraw
---------------------
-
-.. autoclass:: QwtAbstractScaleDraw
- :members:
-
-QwtScaleDraw
-------------
-
-.. autoclass:: QwtScaleDraw
- :members:
-"""
-
-import math
-from datetime import datetime
-
-from qtpy.QtCore import (
- QLineF,
- QPoint,
- QPointF,
- QRect,
- QRectF,
- Qt,
- qFuzzyCompare,
-)
-from qtpy.QtGui import QFontMetrics, QPalette, QTransform
-
-from qwt._math import qwtRadians
-from qwt.scale_div import QwtScaleDiv
-from qwt.scale_map import QwtScaleMap
-from qwt.text import QwtText
-
-# Plain-int aliases for Qt alignment flags. Qt6 exposes alignment flags as
-# IntEnum members and bitwise operations on them go through Python's
-# enum machinery (`__and__`/`__call__`), which is one of the dominant costs
-# of label layout. Casting to int once and using these constants makes the
-# bitwise tests in `labelTransformation` ~10x cheaper without changing
-# semantics.
-_ALIGN_LEFT = int(Qt.AlignLeft)
-_ALIGN_RIGHT = int(Qt.AlignRight)
-_ALIGN_TOP = int(Qt.AlignTop)
-_ALIGN_BOTTOM = int(Qt.AlignBottom)
-
-
-class QwtAbstractScaleDraw_PrivateData(object):
- # See QwtText_PrivateData: ``QObject`` inheritance is unused and the
- # base class' ``__init__`` is a measurable cost in tick-heavy renders.
- __slots__ = (
- "spacing",
- "penWidth",
- "minExtent",
- "components",
- "tick_length",
- "tick_lighter_factor",
- "map",
- "scaleDiv",
- "labelCache",
- )
-
- def __init__(self):
- self.spacing = 4
- self.penWidth = 0
- self.minExtent = 0.0
-
- self.components = (
- QwtAbstractScaleDraw.Backbone
- | QwtAbstractScaleDraw.Ticks
- | QwtAbstractScaleDraw.Labels
- )
- self.tick_length = {
- QwtScaleDiv.MinorTick: 4.0,
- QwtScaleDiv.MediumTick: 6.0,
- QwtScaleDiv.MajorTick: 8.0,
- }
- self.tick_lighter_factor = {
- QwtScaleDiv.MinorTick: 100,
- QwtScaleDiv.MediumTick: 100,
- QwtScaleDiv.MajorTick: 100,
- }
-
- self.map = QwtScaleMap()
- self.scaleDiv = QwtScaleDiv()
-
- self.labelCache = {}
-
-
-class QwtAbstractScaleDraw(object):
- """
- A abstract base class for drawing scales
-
- `QwtAbstractScaleDraw` can be used to draw linear or logarithmic scales.
-
- After a scale division has been specified as a `QwtScaleDiv` object
- using `setScaleDiv()`, the scale can be drawn with the `draw()` member.
-
- Scale components:
-
- * `QwtAbstractScaleDraw.Backbone`: Backbone = the line where the ticks are located
- * `QwtAbstractScaleDraw.Ticks`: Ticks
- * `QwtAbstractScaleDraw.Labels`: Labels
-
- .. py:class:: QwtAbstractScaleDraw()
-
- The range of the scale is initialized to [0, 100],
- The spacing (distance between ticks and labels) is
- set to 4, the tick lengths are set to 4,6 and 8 pixels
- """
-
- # enum ScaleComponent
- Backbone = 0x01
- Ticks = 0x02
- Labels = 0x04
-
- def __init__(self):
- self.__data = QwtAbstractScaleDraw_PrivateData()
-
- def extent(self, font):
- """
- Calculate the extent
-
- The extent is the distance from the baseline to the outermost
- pixel of the scale draw in opposite to its orientation.
- It is at least minimumExtent() pixels.
-
- :param QFont font: Font used for drawing the tick labels
- :return: Number of pixels
-
- .. seealso::
-
- :py:meth:`setMinimumExtent()`, :py:meth:`minimumExtent()`
- """
- return 0.0
-
- def drawTick(self, painter, value, len_):
- """
- Draw a tick
-
- :param QPainter painter: Painter
- :param float value: Value of the tick
- :param float len: Length of the tick
-
- .. seealso::
-
- :py:meth:`drawBackbone()`, :py:meth:`drawLabel()`
- """
- pass
-
- def drawBackbone(self, painter):
- """
- Draws the baseline of the scale
-
- :param QPainter painter: Painter
-
- .. seealso::
-
- :py:meth:`drawTick()`, :py:meth:`drawLabel()`
- """
- pass
-
- def drawLabel(self, painter, value):
- """
- Draws the label for a major scale tick
-
- :param QPainter painter: Painter
- :param float value: Value
-
- .. seealso::
-
- :py:meth:`drawTick()`, :py:meth:`drawBackbone()`
- """
- pass
-
- def enableComponent(self, component, enable):
- """
- En/Disable a component of the scale
-
- :param int component: Scale component
- :param bool enable: On/Off
-
- .. seealso::
-
- :py:meth:`hasComponent()`
- """
- if enable:
- self.__data.components |= component
- else:
- self.__data.components &= ~component
-
- def hasComponent(self, component):
- """
- Check if a component is enabled
-
- :param int component: Component type
- :return: True, when component is enabled
-
- .. seealso::
-
- :py:meth:`enableComponent()`
- """
- return self.__data.components & component
-
- def setScaleDiv(self, scaleDiv):
- """
- Change the scale division
-
- :param qwt.scale_div.QwtScaleDiv scaleDiv: New scale division
- """
- self.__data.scaleDiv = scaleDiv
- self.__data.map.setScaleInterval(scaleDiv.lowerBound(), scaleDiv.upperBound())
- self.invalidateCache()
-
- def setTransformation(self, transformation):
- """
- Change the transformation of the scale
-
- :param qwt.transform.QwtTransform transformation: New scale transformation
- """
- self.__data.map.setTransformation(transformation)
-
- def scaleMap(self):
- """
- :return: Map how to translate between scale and pixel values
- """
- return self.__data.map
-
- def scaleDiv(self):
- """
- :return: scale division
- """
- return self.__data.scaleDiv
-
- def setPenWidth(self, width):
- """
- Specify the width of the scale pen
-
- :param int width: Pen width
-
- .. seealso::
-
- :py:meth:`penWidth()`
- """
- if width < 0:
- width = 0
- if width != self.__data.penWidth:
- self.__data.penWidth = width
-
- def penWidth(self):
- """
- :return: Scale pen width
-
- .. seealso::
-
- :py:meth:`setPenWidth()`
- """
- return self.__data.penWidth
-
- def draw(self, painter, palette):
- """
- Draw the scale
-
- :param QPainter painter: The painter
- :param QPalette palette: Palette, text color is used for the labels,
- foreground color for ticks and backbone
- """
- painter.save()
-
- pen = painter.pen()
- pen.setWidth(self.__data.penWidth)
- pen.setCosmetic(False)
- painter.setPen(pen)
-
- if self.hasComponent(QwtAbstractScaleDraw.Labels):
- painter.save()
- painter.setPen(palette.color(QPalette.Text))
- majorTicks = self.__data.scaleDiv.ticks(QwtScaleDiv.MajorTick)
- for v in majorTicks:
- if self.__data.scaleDiv.contains(v):
- self.drawLabel(painter, v)
- painter.restore()
-
- if self.hasComponent(QwtAbstractScaleDraw.Ticks):
- painter.save()
- pen = painter.pen()
- pen.setCapStyle(Qt.FlatCap)
- default_color = palette.color(QPalette.WindowText)
- for tickType in range(QwtScaleDiv.NTickTypes):
- tickLen = self.__data.tick_length[tickType]
- if tickLen <= 0.0:
- continue
- factor = self.__data.tick_lighter_factor[tickType]
- pen.setColor(default_color.lighter(factor))
- painter.setPen(pen)
- ticks = self.__data.scaleDiv.ticks(tickType)
- for v in ticks:
- if self.__data.scaleDiv.contains(v):
- self.drawTick(painter, v, tickLen)
- painter.restore()
-
- if self.hasComponent(QwtAbstractScaleDraw.Backbone):
- painter.save()
- pen = painter.pen()
- pen.setColor(palette.color(QPalette.WindowText))
- pen.setCapStyle(Qt.FlatCap)
- painter.setPen(pen)
- self.drawBackbone(painter)
- painter.restore()
-
- painter.restore()
-
- def setSpacing(self, spacing):
- """
- Set the spacing between tick and labels
-
- The spacing is the distance between ticks and labels.
- The default spacing is 4 pixels.
-
- :param float spacing: Spacing
-
- .. seealso::
-
- :py:meth:`spacing()`
- """
- if spacing < 0:
- spacing = 0
- self.__data.spacing = spacing
-
- def spacing(self):
- """
- Get the spacing
-
- The spacing is the distance between ticks and labels.
- The default spacing is 4 pixels.
-
- :return: Spacing
-
- .. seealso::
-
- :py:meth:`setSpacing()`
- """
- return self.__data.spacing
-
- def setMinimumExtent(self, minExtent):
- """
- Set a minimum for the extent
-
- The extent is calculated from the components of the
- scale draw. In situations, where the labels are
- changing and the layout depends on the extent (f.e scrolling
- a scale), setting an upper limit as minimum extent will
- avoid jumps of the layout.
-
- :param float minExtent: Minimum extent
-
- .. seealso::
-
- :py:meth:`extent()`, :py:meth:`minimumExtent()`
- """
- if minExtent < 0.0:
- minExtent = 0.0
- self.__data.minExtent = minExtent
-
- def minimumExtent(self):
- """
- Get the minimum extent
-
- :return: Minimum extent
-
- .. seealso::
-
- :py:meth:`extent()`, :py:meth:`setMinimumExtent()`
- """
- return self.__data.minExtent
-
- def setTickLength(self, tick_type, length):
- """
- Set the length of the ticks
-
- :param int tick_type: Tick type
- :param float length: New length
-
- .. warning::
-
- the length is limited to [0..1000]
- """
- if tick_type not in self.__data.tick_length:
- raise ValueError("Invalid tick type: %r" % tick_type)
- self.__data.tick_length[tick_type] = min([1000.0, max([0.0, length])])
-
- def tickLength(self, tick_type):
- """
- :param int tick_type: Tick type
- :return: Length of the ticks
-
- .. seealso::
-
- :py:meth:`setTickLength()`, :py:meth:`maxTickLength()`
- """
- if tick_type not in self.__data.tick_length:
- raise ValueError("Invalid tick type: %r" % tick_type)
- return self.__data.tick_length[tick_type]
-
- def maxTickLength(self):
- """
- :return: Length of the longest tick
-
- Useful for layout calculations
-
- .. seealso::
-
- :py:meth:`tickLength()`, :py:meth:`setTickLength()`
- """
- return max([0.0] + list(self.__data.tick_length.values()))
-
- def setTickLighterFactor(self, tick_type, factor):
- """
- Set the color lighter factor of the ticks
-
- :param int tick_type: Tick type
- :param int factor: New factor
- """
- if tick_type not in self.__data.tick_length:
- raise ValueError("Invalid tick type: %r" % tick_type)
- self.__data.tick_lighter_factor[tick_type] = min([0, factor])
-
- def tickLighterFactor(self, tick_type):
- """
- :param int tick_type: Tick type
- :return: Color lighter factor of the ticks
-
- .. seealso::
-
- :py:meth:`setTickLighterFactor()`
- """
- if tick_type not in self.__data.tick_length:
- raise ValueError("Invalid tick type: %r" % tick_type)
- return self.__data.tick_lighter_factor[tick_type]
-
- def label(self, value):
- """
- Convert a value into its representing label
-
- The value is converted to a plain text using
- `QLocale().toString(value)`.
- This method is often overloaded by applications to have individual
- labels.
-
- :param float value: Value
- :return: Label string
- """
- # Adding a space before the value is a way to add a margin on the left
- # of the scale. This helps to avoid truncating the first digit of the
- # tick labels while keeping a tight layout.
- return " %g" % value
-
- def tickLabel(self, font, value):
- """
- Convert a value into its representing label and cache it.
-
- The conversion between value and label is called very often
- in the layout and painting code. Unfortunately the
- calculation of the label sizes might be slow (really slow
- for rich text in Qt4), so it's necessary to cache the labels.
-
- :param QFont font: Font
- :param float value: Value
- :return: Tuple (tick label, text size)
- """
- lbl = self.__data.labelCache.get(value)
- if lbl is None:
- lbl = QwtText(self.label(value))
- lbl.setRenderFlags(0)
- lbl.setLayoutAttribute(QwtText.MinimumLayout)
- self.__data.labelCache[value] = lbl
- return lbl, lbl.textSize(font)
-
- def invalidateCache(self):
- """
- Invalidate the cache used by `tickLabel()`
-
- The cache is invalidated, when a new `QwtScaleDiv` is set. If
- the labels need to be changed. while the same `QwtScaleDiv` is set,
- `invalidateCache()` needs to be called manually.
- """
- self.__data.labelCache.clear()
-
-
-class QwtScaleDraw_PrivateData(object):
- # See QwtText_PrivateData: ``QObject`` inheritance is unused and the
- # base class' ``__init__`` is a measurable cost in tick-heavy renders.
- __slots__ = (
- "len",
- "alignment",
- "orientation",
- "labelAlignment",
- "labelRotation",
- "labelAutoSize",
- "pos",
- )
-
- def __init__(self):
- self.len = 0
- self.alignment = QwtScaleDraw.BottomScale
- # Cached orientation - kept in sync by ``QwtScaleDraw.setAlignment``
- # so that the very hot ``orientation()`` accessor avoids any test.
- self.orientation = Qt.Horizontal
- self.labelAlignment = 0
- self.labelRotation = 0.0
- self.labelAutoSize = True
- self.pos = QPointF()
-
-
-class QwtScaleDraw(QwtAbstractScaleDraw):
- """
- A class for drawing scales
-
- QwtScaleDraw can be used to draw linear or logarithmic scales.
- A scale has a position, an alignment and a length, which can be specified .
- The labels can be rotated and aligned
- to the ticks using `setLabelRotation()` and `setLabelAlignment()`.
-
- After a scale division has been specified as a QwtScaleDiv object
- using `QwtAbstractScaleDraw.setScaleDiv(scaleDiv)`,
- the scale can be drawn with the `QwtAbstractScaleDraw.draw()` member.
-
- Alignment of the scale draw:
-
- * `QwtScaleDraw.BottomScale`: The scale is below
- * `QwtScaleDraw.TopScale`: The scale is above
- * `QwtScaleDraw.LeftScale`: The scale is left
- * `QwtScaleDraw.RightScale`: The scale is right
-
- .. py:class:: QwtScaleDraw()
-
- The range of the scale is initialized to [0, 100],
- The position is at (0, 0) with a length of 100.
- The orientation is `QwtAbstractScaleDraw.Bottom`.
- """
-
- # enum Alignment
- BottomScale, TopScale, LeftScale, RightScale = list(range(4))
- Flags = (
- Qt.AlignHCenter | Qt.AlignBottom, # BottomScale
- Qt.AlignHCenter | Qt.AlignTop, # TopScale
- Qt.AlignLeft | Qt.AlignVCenter, # LeftScale
- Qt.AlignRight | Qt.AlignVCenter, # RightScale
- )
-
- def __init__(self):
- QwtAbstractScaleDraw.__init__(self)
- self.__data = QwtScaleDraw_PrivateData()
- self.setLength(100)
- self._max_label_sizes = {}
-
- def alignment(self):
- """
- :return: Alignment of the scale
-
- .. seealso::
-
- :py:meth:`setAlignment()`
- """
- return self.__data.alignment
-
- def setAlignment(self, align):
- """
- Set the alignment of the scale
-
- :param int align: Alignment of the scale
-
- Alignment of the scale draw:
-
- * `QwtScaleDraw.BottomScale`: The scale is below
- * `QwtScaleDraw.TopScale`: The scale is above
- * `QwtScaleDraw.LeftScale`: The scale is left
- * `QwtScaleDraw.RightScale`: The scale is right
-
- The default alignment is `QwtScaleDraw.BottomScale`
-
- .. seealso::
-
- :py:meth:`alignment()`
- """
- self.__data.alignment = align
- # Keep cached orientation in sync (see ``orientation()``).
- if align == self.BottomScale or align == self.TopScale:
- self.__data.orientation = Qt.Horizontal
- else:
- self.__data.orientation = Qt.Vertical
-
- def orientation(self):
- """
- Return the orientation
-
- TopScale, BottomScale are horizontal (`Qt.Horizontal`) scales,
- LeftScale, RightScale are vertical (`Qt.Vertical`) scales.
-
- :return: Orientation of the scale
-
- .. seealso::
-
- :py:meth:`alignment()`
- """
- # Pre-computed by ``setAlignment`` - this method is called per tick.
- return self.__data.orientation
-
- def getBorderDistHint(self, font):
- """
- Determine the minimum border distance
-
- This member function returns the minimum space
- needed to draw the mark labels at the scale's endpoints.
-
- :param QFont font: Font
- :return: tuple `(start, end)`
-
- Returned tuple:
-
- * start: Start border distance
- * end: End border distance
- """
- start, end = 0, 1.0
-
- if not self.hasComponent(QwtAbstractScaleDraw.Labels):
- return start, end
-
- ticks = self.scaleDiv().ticks(QwtScaleDiv.MajorTick)
- if len(ticks) == 0:
- return start, end
-
- scale_map = self.scaleMap()
- transform = scale_map.transform
- minTick = ticks[0]
- minPos = transform(minTick)
- maxTick = minTick
- maxPos = minPos
-
- for tick in ticks:
- tickPos = transform(tick)
- if tickPos < minPos:
- minTick = tick
- minPos = tickPos
- if tickPos > maxPos:
- maxTick = tick
- maxPos = tickPos
-
- s = 0.0
- e = 0.0
- if self.orientation() == Qt.Vertical:
- s = -self.labelRect(font, minTick).top()
- s -= abs(minPos - round(scale_map.p2()))
-
- e = self.labelRect(font, maxTick).bottom()
- e -= abs(maxPos - scale_map.p1())
- else:
- s = -self.labelRect(font, minTick).left()
- s -= abs(minPos - scale_map.p1())
-
- e = self.labelRect(font, maxTick).right()
- e -= abs(maxPos - scale_map.p2())
-
- return max(math.ceil(s), 0), max(math.ceil(e), 0)
-
- def minLabelDist(self, font):
- """
- Determine the minimum distance between two labels, that is necessary
- that the texts don't overlap.
-
- :param QFont font: Font
- :return: The maximum width of a label
-
- .. seealso::
-
- :py:meth:`getBorderDistHint()`
- """
- if not self.hasComponent(QwtAbstractScaleDraw.Labels):
- return 0
-
- ticks = self.scaleDiv().ticks(QwtScaleDiv.MajorTick)
- if not ticks:
- return 0
-
- fm = QFontMetrics(font)
- vertical = self.orientation() == Qt.Vertical
-
- bRect1 = QRectF()
- bRect2 = self.labelRect(font, ticks[0])
- if vertical:
- bRect2.setRect(-bRect2.bottom(), 0.0, bRect2.height(), bRect2.width())
-
- maxDist = 0.0
-
- for tick in ticks:
- bRect1 = bRect2
- bRect2 = self.labelRect(font, tick)
- if vertical:
- bRect2.setRect(-bRect2.bottom(), 0.0, bRect2.height(), bRect2.width())
-
- dist = fm.leading()
- if bRect1.right() > 0:
- dist += bRect1.right()
- if bRect2.left() < 0:
- dist += -bRect2.left()
-
- if dist > maxDist:
- maxDist = dist
-
- angle = qwtRadians(self.labelRotation())
- if vertical:
- angle += math.pi / 2
-
- sinA = math.sin(angle)
- if qFuzzyCompare(sinA + 1.0, 1.0):
- return math.ceil(maxDist)
-
- fmHeight = fm.ascent() - 2
-
- labelDist = fmHeight / math.sin(angle) * math.cos(angle)
- if labelDist < 0:
- labelDist = -labelDist
-
- if labelDist > maxDist:
- labelDist = maxDist
-
- if labelDist < fmHeight:
- labelDist = fmHeight
-
- return math.ceil(labelDist)
-
- def extent(self, font):
- """
- Calculate the width/height that is needed for a
- vertical/horizontal scale.
-
- The extent is calculated from the pen width of the backbone,
- the major tick length, the spacing and the maximum width/height
- of the labels.
-
- :param QFont font: Font used for painting the labels
- :return: Extent
-
- .. seealso::
-
- :py:meth:`minLength()`
- """
- d = 0.0
- if self.hasComponent(QwtAbstractScaleDraw.Labels):
- if self.orientation() == Qt.Vertical:
- d = self.maxLabelWidth(font)
- else:
- d = self.maxLabelHeight(font)
- if d > 0:
- d += self.spacing()
- if self.hasComponent(QwtAbstractScaleDraw.Ticks):
- d += self.maxTickLength()
- if self.hasComponent(QwtAbstractScaleDraw.Backbone):
- pw = max([1, self.penWidth()])
- d += pw
- return max([d, self.minimumExtent()])
-
- def minLength(self, font):
- """
- Calculate the minimum length that is needed to draw the scale
-
- :param QFont font: Font used for painting the labels
- :return: Minimum length that is needed to draw the scale
-
- .. seealso::
-
- :py:meth:`extent()`
- """
- startDist, endDist = self.getBorderDistHint(font)
- sd = self.scaleDiv()
- minorCount = len(sd.ticks(QwtScaleDiv.MinorTick)) + len(
- sd.ticks(QwtScaleDiv.MediumTick)
- )
- majorCount = len(sd.ticks(QwtScaleDiv.MajorTick))
- lengthForLabels = 0
- if self.hasComponent(QwtAbstractScaleDraw.Labels):
- lengthForLabels = self.minLabelDist(font) * majorCount
- lengthForTicks = 0
- if self.hasComponent(QwtAbstractScaleDraw.Ticks):
- pw = max([1, self.penWidth()])
- lengthForTicks = math.ceil((majorCount + minorCount) * (pw + 1.0))
- return startDist + endDist + max([lengthForLabels, lengthForTicks])
-
- def labelPosition(self, value):
- """
- Find the position, where to paint a label
-
- The position has a distance that depends on the length of the ticks
- in direction of the `alignment()`.
-
- :param float value: Value
- :return: Position, where to paint a label
- """
- tval = self.scaleMap().transform(value)
- dist = self.spacing()
- hasComponent = self.hasComponent
- if hasComponent(QwtAbstractScaleDraw.Backbone):
- dist += max(1, self.penWidth())
- if hasComponent(QwtAbstractScaleDraw.Ticks):
- dist += self.tickLength(QwtScaleDiv.MajorTick)
-
- alignment = self.alignment()
- pos = self.__data.pos
- if alignment == self.RightScale:
- return QPointF(pos.x() + dist, tval)
- if alignment == self.LeftScale:
- return QPointF(pos.x() - dist, tval)
- if alignment == self.BottomScale:
- return QPointF(tval, pos.y() + dist)
- # TopScale
- return QPointF(tval, pos.y() - dist)
-
- def drawTick(self, painter, value, len_):
- """
- Draw a tick
-
- :param QPainter painter: Painter
- :param float value: Value of the tick
- :param float len: Length of the tick
-
- .. seealso::
-
- :py:meth:`drawBackbone()`, :py:meth:`drawLabel()`
- """
- if len_ <= 0:
- return
- pos = self.__data.pos
- tval = self.scaleMap().transform(value)
- pw = self.penWidth()
- a = 0
- if self.alignment() == self.LeftScale:
- x1 = pos.x() + a
- x2 = pos.x() + a - pw - len_
- painter.drawLine(QLineF(x1, tval, x2, tval))
- elif self.alignment() == self.RightScale:
- x1 = pos.x()
- x2 = pos.x() + pw + len_
- painter.drawLine(QLineF(x1, tval, x2, tval))
- elif self.alignment() == self.BottomScale:
- y1 = pos.y()
- y2 = pos.y() + pw + len_
- painter.drawLine(QLineF(tval, y1, tval, y2))
- elif self.alignment() == self.TopScale:
- y1 = pos.y() + a
- y2 = pos.y() - pw - len_ + a
- painter.drawLine(QLineF(tval, y1, tval, y2))
-
- def drawBackbone(self, painter):
- """
- Draws the baseline of the scale
-
- :param QPainter painter: Painter
-
- .. seealso::
-
- :py:meth:`drawTick()`, :py:meth:`drawLabel()`
- """
- pos = self.__data.pos
- len_ = self.__data.len
- off = 0.5 * self.penWidth()
- if self.alignment() == self.LeftScale:
- x = pos.x() - off
- painter.drawLine(QLineF(x, pos.y(), x, pos.y() + len_))
- elif self.alignment() == self.RightScale:
- x = pos.x() + off
- painter.drawLine(QLineF(x, pos.y(), x, pos.y() + len_))
- elif self.alignment() == self.TopScale:
- y = pos.y() - off
- painter.drawLine(QLineF(pos.x(), y, pos.x() + len_, y))
- elif self.alignment() == self.BottomScale:
- y = pos.y() + off
- painter.drawLine(QLineF(pos.x(), y, pos.x() + len_, y))
-
- def move(self, *args):
- """
- Move the position of the scale
-
- The meaning of the parameter pos depends on the alignment:
-
- * `QwtScaleDraw.LeftScale`:
-
- The origin is the topmost point of the backbone. The backbone is a
- vertical line. Scale marks and labels are drawn at the left of the
- backbone.
-
- * `QwtScaleDraw.RightScale`:
-
- The origin is the topmost point of the backbone. The backbone is a
- vertical line. Scale marks and labels are drawn at the right of
- the backbone.
-
- * `QwtScaleDraw.TopScale`:
-
- The origin is the leftmost point of the backbone. The backbone is
- a horizontal line. Scale marks and labels are drawn above the
- backbone.
-
- * `QwtScaleDraw.BottomScale`:
-
- The origin is the leftmost point of the backbone. The backbone is
- a horizontal line Scale marks and labels are drawn below the
- backbone.
-
- .. py:method:: move(x, y)
- :noindex:
-
- :param float x: X coordinate
- :param float y: Y coordinate
-
- .. py:method:: move(pos)
- :noindex:
-
- :param QPointF pos: position
-
- .. seealso::
-
- :py:meth:`pos()`, :py:meth:`setLength()`
- """
- if len(args) == 2:
- x, y = args
- self.move(QPointF(x, y))
- elif len(args) == 1:
- (pos,) = args
- self.__data.pos = pos
- self.updateMap()
- else:
- raise TypeError(
- "%s().move() takes 1 or 2 argument(s) (%s given)"
- % (self.__class__.__name__, len(args))
- )
-
- def pos(self):
- """
- :return: Origin of the scale
-
- .. seealso::
-
- :py:meth:`pos()`, :py:meth:`setLength()`
- """
- return self.__data.pos
-
- def setLength(self, length):
- """
- Set the length of the backbone.
-
- The length doesn't include the space needed for overlapping labels.
-
- :param float length: Length of the backbone
-
- .. seealso::
-
- :py:meth:`move()`, :py:meth:`minLabelDist()`
- """
- if length >= 0 and length < 10:
- length = 10
- if length < 0 and length > -10:
- length = -10
- self.__data.len = length
- self.updateMap()
-
- def length(self):
- """
- :return: the length of the backbone
-
- .. seealso::
-
- :py:meth:`setLength()`, :py:meth:`pos()`
- """
- return self.__data.len
-
- def drawLabel(self, painter, value):
- """
- Draws the label for a major scale tick
-
- :param QPainter painter: Painter
- :param float value: Value
-
- .. seealso::
-
- :py:meth:`drawTick()`, :py:meth:`drawBackbone()`,
- :py:meth:`boundingLabelRect()`
- """
- lbl, labelSize = self.tickLabel(painter.font(), value)
- if lbl is None or lbl.isEmpty():
- return
- pos = self.labelPosition(value)
- transform = self.labelTransformation(pos, labelSize)
- painter.save()
- painter.setWorldTransform(transform, True)
- lbl.draw(painter, QRect(QPoint(0, 0), labelSize.toSize()))
- painter.restore()
-
- def boundingLabelRect(self, font, value):
- """
- Find the bounding rectangle for the label.
-
- The coordinates of the rectangle are absolute (calculated from
- `pos()`) in direction of the tick.
-
- :param QFont font: Font used for painting
- :param float value: Value
- :return: Bounding rectangle
-
- .. seealso::
-
- :py:meth:`labelRect()`
- """
- lbl, labelSize = self.tickLabel(font, value)
- if lbl.isEmpty():
- return QRect()
- pos = self.labelPosition(value)
- transform = self.labelTransformation(pos, labelSize)
- return transform.mapRect(QRect(QPoint(0, 0), labelSize.toSize()))
-
- def labelTransformation(self, pos, size):
- """
- Calculate the transformation that is needed to paint a label
- depending on its alignment and rotation.
-
- :param QPointF pos: Position where to paint the label
- :param QSizeF size: Size of the label
- :return: Transformation matrix
-
- .. seealso::
-
- :py:meth:`setLabelAlignment()`, :py:meth:`setLabelRotation()`
- """
- transform = QTransform()
- transform.translate(pos.x(), pos.y())
- transform.rotate(self.labelRotation())
-
- flags = self.labelAlignment()
- if flags == 0:
- flags = self.Flags[self.alignment()]
- # Cast to plain int once to avoid the per-bit Qt6 enum overhead.
- flags = int(flags)
-
- if flags & _ALIGN_LEFT:
- x = -size.width()
- elif flags & _ALIGN_RIGHT:
- x = 0.0
- else:
- x = -(0.5 * size.width())
-
- if flags & _ALIGN_TOP:
- y = -size.height()
- elif flags & _ALIGN_BOTTOM:
- y = 0
- else:
- y = -(0.5 * size.height())
-
- transform.translate(x, y)
-
- return transform
-
- def labelRect(self, font, value):
- """
- Find the bounding rectangle for the label. The coordinates of
- the rectangle are relative to spacing + tick length from the backbone
- in direction of the tick.
-
- :param QFont font: Font used for painting
- :param float value: Value
- :return: Bounding rectangle that is needed to draw a label
- """
- lbl, labelSize = self.tickLabel(font, value)
- if not lbl or lbl.isEmpty():
- return QRectF(0.0, 0.0, 0.0, 0.0)
- # Fast path: when the label is not rotated, the contribution of
- # ``pos`` cancels out (transform.translate(pos) followed by
- # br.translate(-pos)). This avoids ``labelPosition``,
- # ``labelTransformation`` and ``QTransform.mapRect`` entirely - all
- # of which are dominant costs in tick-heavy layouts.
- if self.labelRotation() == 0.0:
- flags = self.labelAlignment()
- if flags == 0:
- flags = self.Flags[self.alignment()]
- flags = int(flags)
- w = labelSize.width()
- h = labelSize.height()
- if flags & _ALIGN_LEFT:
- x = -w
- elif flags & _ALIGN_RIGHT:
- x = 0.0
- else:
- x = -0.5 * w
- if flags & _ALIGN_TOP:
- y = -h
- elif flags & _ALIGN_BOTTOM:
- y = 0.0
- else:
- y = -0.5 * h
- return QRectF(x, y, w, h)
- pos = self.labelPosition(value)
- transform = self.labelTransformation(pos, labelSize)
- br = transform.mapRect(QRectF(QPointF(0, 0), labelSize))
- br.translate(-pos.x(), -pos.y())
- return br
-
- def labelSize(self, font, value):
- """
- Calculate the size that is needed to draw a label
-
- :param QFont font: Label font
- :param float value: Value
- :return: Size that is needed to draw a label
- """
- return self.labelRect(font, value).size()
-
- def setLabelRotation(self, rotation):
- """
- Rotate all labels.
-
- When changing the rotation, it might be necessary to
- adjust the label flags too. Finding a useful combination is
- often the result of try and error.
-
- :param float rotation: Angle in degrees. When changing the label rotation, the
- label flags often needs to be adjusted too.
-
- .. seealso::
-
- :py:meth:`setLabelAlignment()`, :py:meth:`labelRotation()`,
- :py:meth:`labelAlignment()`
- """
- self.__data.labelRotation = rotation
-
- def labelRotation(self):
- """
- :return: the label rotation
-
- .. seealso::
-
- :py:meth:`setLabelRotation()`, :py:meth:`labelAlignment()`
- """
- return self.__data.labelRotation
-
- def setLabelAlignment(self, alignment):
- """
- Change the label flags
-
- Labels are aligned to the point tick length + spacing away from the
- backbone.
-
- The alignment is relative to the orientation of the label text.
- In case of an flags of 0 the label will be aligned
- depending on the orientation of the scale:
-
- * `QwtScaleDraw.TopScale`: `Qt.AlignHCenter | Qt.AlignTop`
- * `QwtScaleDraw.BottomScale`: `Qt.AlignHCenter | Qt.AlignBottom`
- * `QwtScaleDraw.LeftScale`: `Qt.AlignLeft | Qt.AlignVCenter`
- * `QwtScaleDraw.RightScale`: `Qt.AlignRight | Qt.AlignVCenter`
-
- Changing the alignment is often necessary for rotated labels.
-
- :param Qt.Alignment alignment Or'd `Qt.AlignmentFlags`
-
- .. seealso::
-
- :py:meth:`setLabelRotation()`, :py:meth:`labelRotation()`,
- :py:meth:`labelAlignment()`
-
- .. warning::
-
- The various alignments might be confusing. The alignment of the
- label is not the alignment of the scale and is not the alignment
- of the flags (`QwtText.flags()`) returned from
- `QwtAbstractScaleDraw.label()`.
- """
- self.__data.labelAlignment = alignment
-
- def labelAlignment(self):
- """
- :return: the label flags
-
- .. seealso::
-
- :py:meth:`setLabelAlignment()`, :py:meth:`labelRotation()`
- """
- return self.__data.labelAlignment
-
- def setLabelAutoSize(self, state):
- """
- Set label automatic size option state
-
- When drawing text labels, if automatic size mode is enabled (default
- behavior), the axes are drawn in order to optimize layout space and
- depends on text label individual sizes. Otherwise, width and height
- won't change when axis range is changing.
-
- This option is not implemented in Qwt C++ library: this may be used
- either as an optimization (updating plot layout is faster when this
- option is enabled) or as an appearance preference (with Qwt default
- behavior, the size of axes may change when zooming and/or panning
- plot canvas which in some cases may not be desired).
-
- :param bool state: On/off
-
- .. seealso::
-
- :py:meth:`labelAutoSize()`
- """
- self.__data.labelAutoSize = state
-
- def labelAutoSize(self):
- """
- :return: True if automatic size option is enabled for labels
-
- .. seealso::
-
- :py:meth:`setLabelAutoSize()`
- """
- return self.__data.labelAutoSize
-
- def _get_max_label_size(self, font):
- key = (font.toString(), self.labelRotation())
- size = self._max_label_sizes.get(key)
- if size is None:
- size = self.labelSize(font, -999999) # -999999 is the biggest label
- size.setWidth(math.ceil(size.width()))
- size.setHeight(math.ceil(size.height()))
- return self._max_label_sizes.setdefault(key, size)
- else:
- return size
-
- def maxLabelWidth(self, font):
- """
- :param QFont font: Font
- :return: the maximum width of a label
- """
- ticks = self.scaleDiv().ticks(QwtScaleDiv.MajorTick)
- if not ticks:
- return 0
- if self.labelAutoSize():
- vmax = sorted(
- [v for v in ticks if self.scaleDiv().contains(v)],
- key=lambda obj: len("%g" % obj),
- )[-1]
- return math.ceil(self.labelSize(font, vmax).width())
- ## Original implementation (closer to Qwt's C++ code, but slower):
- # return math.ceil(max([self.labelSize(font, v).width()
- # for v in ticks if self.scaleDiv().contains(v)]))
- else:
- return self._get_max_label_size(font).width()
-
- def maxLabelHeight(self, font):
- """
- :param QFont font: Font
- :return: the maximum height of a label
- """
- ticks = self.scaleDiv().ticks(QwtScaleDiv.MajorTick)
- if not ticks:
- return 0
- if self.labelAutoSize():
- vmax = sorted(
- [v for v in ticks if self.scaleDiv().contains(v)],
- key=lambda obj: len("%g" % obj),
- )[-1]
- return math.ceil(self.labelSize(font, vmax).height())
- ## Original implementation (closer to Qwt's C++ code, but slower):
- # return math.ceil(max([self.labelSize(font, v).height()
- # for v in ticks if self.scaleDiv().contains(v)]))
- else:
- return self._get_max_label_size(font).height()
-
- def updateMap(self):
- pos = self.__data.pos
- len_ = self.__data.len
- sm = self.scaleMap()
- if self.orientation() == Qt.Vertical:
- sm.setPaintInterval(pos.y() + len_, pos.y())
- else:
- sm.setPaintInterval(pos.x(), pos.x() + len_)
-
-
-class QwtDateTimeScaleDraw(QwtScaleDraw):
- """Scale draw for datetime axis
-
- This class formats axis labels as date/time strings from Unix timestamps.
-
- Args:
- format: Format string for datetime display (default: "%Y-%m-%d %H:%M:%S").
- Uses Python datetime.strftime() format codes.
- spacing: Spacing between labels (default: 4)
-
- Examples:
- >>> # Create a datetime scale with default format
- >>> scale = QwtDateTimeScaleDraw()
-
- >>> # Create a datetime scale with custom format (time only)
- >>> scale = QwtDateTimeScaleDraw(format="%H:%M:%S")
-
- >>> # Create a datetime scale with date only
- >>> scale = QwtDateTimeScaleDraw(format="%Y-%m-%d", spacing=4)
- """
-
- def __init__(self, format: str = "%Y-%m-%d %H:%M:%S", spacing: int = 4) -> None:
- super().__init__()
- self._format = format
- self.setSpacing(spacing)
-
- def get_format(self) -> str:
- """Get the current datetime format string
-
- Returns:
- str: Format string
- """
- return self._format
-
- def set_format(self, format: str) -> None:
- """Set the datetime format string
-
- Args:
- format: Format string for datetime display
- """
- self._format = format
-
- def label(self, value: float) -> QwtText:
- """Convert a timestamp value to a formatted date/time label
-
- Args:
- value: Unix timestamp (seconds since epoch)
-
- Returns:
- QwtText: Formatted label
- """
- try:
- dt = datetime.fromtimestamp(value)
- return QwtText(dt.strftime(self._format))
- except (ValueError, OSError):
- # Handle invalid timestamps
- return QwtText("")
diff --git a/qwt/scale_engine.py b/qwt/scale_engine.py
deleted file mode 100644
index 8b6545f..0000000
--- a/qwt/scale_engine.py
+++ /dev/null
@@ -1,1087 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the Qwt License
-# Copyright (c) 2002 Uwe Rathmann, for the original C++ code
-# Copyright (c) 2015 Pierre Raybaut, for the Python translation/optimization
-# (see LICENSE file for more details)
-
-"""
-QwtScaleEngine
---------------
-
-.. autoclass:: QwtScaleEngine
- :members:
-
-QwtLinearScaleEngine
---------------------
-
-.. autoclass:: QwtLinearScaleEngine
- :members:
-
-QwtLogScaleEngine
------------------
-
-.. autoclass:: QwtLogScaleEngine
- :members:
-"""
-
-import math
-import sys
-
-import numpy as np
-from qtpy.QtCore import qFuzzyCompare
-
-from qwt._math import qwtFuzzyCompare
-from qwt.interval import QwtInterval
-from qwt.scale_div import QwtScaleDiv
-from qwt.transform import QwtLogTransform, QwtTransform
-
-DBL_MAX = sys.float_info.max
-LOG_MIN = 1.0e-150
-LOG_MAX = 1.0e150
-
-
-def qwtLogInterval(base, interval):
- return QwtInterval(
- math.log(interval.minValue(), base), math.log(interval.maxValue(), base)
- )
-
-
-def qwtPowInterval(base, interval):
- return QwtInterval(
- math.pow(base, interval.minValue()), math.pow(base, interval.maxValue())
- )
-
-
-def qwtStepSize(intervalSize, maxSteps, base):
- """this version often doesn't find the best ticks: f.e for 15: 5, 10"""
- minStep = divideInterval(intervalSize, maxSteps, base)
- if minStep != 0.0:
- # # ticks per interval
- numTicks = math.ceil(abs(intervalSize / minStep)) - 1
- # Do the minor steps fit into the interval?
- if (
- qwtFuzzyCompare(
- (numTicks + 1) * abs(minStep), abs(intervalSize), intervalSize
- )
- > 0
- ):
- # The minor steps doesn't fit into the interval
- return 0.5 * intervalSize
- return minStep
-
-
-EPS = 1.0e-6
-
-
-def ceilEps(value, intervalSize):
- """
- Ceil a value, relative to an interval
-
- :param float value: Value to be ceiled
- :param float intervalSize: Interval size
- :return: Rounded value
-
- .. seealso::
-
- :py:func:`qwt.scale_engine.floorEps()`
- """
- eps = EPS * intervalSize
- value = (value - eps) / intervalSize
- return math.ceil(value) * intervalSize
-
-
-def floorEps(value, intervalSize):
- """
- Floor a value, relative to an interval
-
- :param float value: Value to be floored
- :param float intervalSize: Interval size
- :return: Rounded value
-
- .. seealso::
-
- :py:func:`qwt.scale_engine.ceilEps()`
- """
- eps = EPS * intervalSize
- value = (value + eps) / intervalSize
- return math.floor(value) * intervalSize
-
-
-def divideEps(intervalSize, numSteps):
- """
- Divide an interval into steps
-
- `stepSize = (intervalSize - intervalSize * 10**-6) / numSteps`
-
- :param float intervalSize: Interval size
- :param float numSteps: Number of steps
- :return: Step size
- """
- if numSteps == 0.0 or intervalSize == 0.0:
- return 0.0
- return (intervalSize - (EPS * intervalSize)) / numSteps
-
-
-def divideInterval(intervalSize, numSteps, base):
- """
- Calculate a step size for a given interval
-
- :param float intervalSize: Interval size
- :param float numSteps: Number of steps
- :param int base: Base for the division (usually 10)
- :return: Calculated step size
- """
- if numSteps <= 0:
- return 0.0
- v = divideEps(intervalSize, numSteps)
- if v == 0.0:
- return 0.0
-
- lx = math.log(abs(v), base)
- p = math.floor(lx)
- fraction = math.pow(base, lx - p)
- n = base
- while n > 1 and fraction <= n // 2:
- n //= 2
-
- stepSize = n * math.pow(base, p)
- if v < 0:
- stepSize = -stepSize
-
- return stepSize
-
-
-class QwtScaleEngine_PrivateData(object):
- def __init__(self):
- self.attributes = QwtScaleEngine.NoAttribute
- self.lowerMargin = 0.0
- self.upperMargin = 0.0
- self.referenceValue = 0.0
- self.base = 10
- self.transform = None # QwtTransform
-
-
-class QwtScaleEngine(object):
- """
- Base class for scale engines.
-
- A scale engine tries to find "reasonable" ranges and step sizes
- for scales.
-
- The layout of the scale can be varied with `setAttribute()`.
-
- `PythonQwt` offers implementations for logarithmic and linear scales.
-
- Layout attributes:
-
- * `QwtScaleEngine.NoAttribute`: No attributes
- * `QwtScaleEngine.IncludeReference`: Build a scale which includes the
- `reference()` value
- * `QwtScaleEngine.Symmetric`: Build a scale which is symmetric to the
- `reference()` value
- * `QwtScaleEngine.Floating`: The endpoints of the scale are supposed to
- be equal the outmost included values plus the specified margins (see
- `setMargins()`). If this attribute is *not* set, the endpoints of the
- scale will be integer multiples of the step size.
- * `QwtScaleEngine.Inverted`: Turn the scale upside down
- """
-
- # enum Attribute
- NoAttribute = 0x00
- IncludeReference = 0x01
- Symmetric = 0x02
- Floating = 0x04
- Inverted = 0x08
-
- def __init__(self, base=10):
- self.__data = QwtScaleEngine_PrivateData()
- self.setBase(base)
-
- def autoScale(self, maxNumSteps, x1, x2, stepSize, relativeMargin=0.0):
- """
- Align and divide an interval
-
- :param int maxNumSteps: Max. number of steps
- :param float x1: First limit of the interval (In/Out)
- :param float x2: Second limit of the interval (In/Out)
- :param float stepSize: Step size
- :param float relativeMargin: Margin as a fraction of the interval width
- :return: tuple (x1, x2, stepSize)
- """
- pass
-
- def divideScale(self, x1, x2, maxMajorSteps, maxMinorSteps, stepSize=0.0):
- """
- Calculate a scale division
-
- :param float x1: First interval limit
- :param float x2: Second interval limit
- :param int maxMajorSteps: Maximum for the number of major steps
- :param int maxMinorSteps: Maximum number of minor steps
- :param float stepSize: Step size. If stepSize == 0.0, the scaleEngine calculates one
- :return: Calculated scale division
- """
- pass
-
- def setTransformation(self, transform):
- """
- Assign a transformation
-
- :param qwt.transform.QwtTransform transform: Transformation
-
- The transformation object is used as factory for clones
- that are returned by `transformation()`
-
- The scale engine takes ownership of the transformation.
-
- .. seealso::
-
- :py:meth:`QwtTransform.copy()`, :py:meth:`transformation()`
- """
- assert transform is None or isinstance(transform, QwtTransform)
- if transform != self.__data.transform:
- self.__data.transform = transform
-
- def transformation(self):
- """
- Create and return a clone of the transformation
- of the engine. When the engine has no special transformation
- None is returned, indicating no transformation.
-
- :return: A clone of the transfomation
-
- .. seealso::
-
- :py:meth:`setTransformation()`
- """
- if self.__data.transform:
- return self.__data.transform.copy()
-
- def lowerMargin(self):
- """
- :return: the margin at the lower end of the scale
-
- The default margin is 0.
-
- .. seealso::
-
- :py:meth:`setMargins()`
- """
- return self.__data.lowerMargin
-
- def upperMargin(self):
- """
- :return: the margin at the upper end of the scale
-
- The default margin is 0.
-
- .. seealso::
-
- :py:meth:`setMargins()`
- """
- return self.__data.upperMargin
-
- def setMargins(self, lower, upper):
- """
- Specify margins at the scale's endpoints
-
- :param float lower: minimum distance between the scale's lower boundary and the smallest enclosed value
- :param float upper: minimum distance between the scale's upper boundary and the greatest enclosed value
- :return: A clone of the transfomation
-
- Margins can be used to leave a minimum amount of space between
- the enclosed intervals and the boundaries of the scale.
-
- .. warning::
-
- `QwtLogScaleEngine` measures the margins in decades.
-
- .. seealso::
-
- :py:meth:`upperMargin()`, :py:meth:`lowerMargin()`
- """
- self.__data.lowerMargin = max([lower, 0.0])
- self.__data.upperMargin = max([upper, 0.0])
-
- def divideInterval(self, intervalSize, numSteps):
- """
- Calculate a step size for a given interval
-
- :param float intervalSize: Interval size
- :param float numSteps: Number of steps
- :return: Step size
- """
- return divideInterval(intervalSize, numSteps, self.__data.base)
-
- def contains(self, interval, value):
- """
- Check if an interval "contains" a value
-
- :param float intervalSize: Interval size
- :param float value: Value
- :return: True, when the value is inside the interval
- """
- if not interval.isValid():
- return False
- min_v = interval.minValue()
- max_v = interval.maxValue()
- eps = abs(1.0e-6 * (max_v - min_v))
- return not (min_v - value > eps or value - max_v > eps)
-
- def strip(self, ticks, interval):
- """
- Remove ticks from a list, that are not inside an interval
-
- :param list ticks: Tick list
- :param qwt.interval.QwtInterval interval: Interval
- :return: Stripped tick list
- """
- if not interval.isValid() or not ticks:
- return []
- # Inline ``contains`` to avoid one Python call per tick: ``strip`` is
- # called by buildTicks for every layout pass and is one of the
- # dominant costs in tick-heavy plots.
- min_v = interval.minValue()
- max_v = interval.maxValue()
- eps = abs(1.0e-6 * (max_v - min_v))
- lo = min_v - eps
- hi = max_v + eps
- if lo <= ticks[0] and ticks[-1] <= hi:
- return ticks
- return [tick for tick in ticks if lo <= tick <= hi]
-
- def buildInterval(self, value):
- """
- Build an interval around a value
-
- In case of v == 0.0 the interval is [-0.5, 0.5],
- otherwide it is [0.5 * v, 1.5 * v]
-
- :param float value: Initial value
- :return: Calculated interval
- """
- if value == 0.0:
- delta = 0.5
- else:
- delta = abs(0.5 * value)
- if DBL_MAX - delta < value:
- return QwtInterval(DBL_MAX - delta, DBL_MAX)
- if -DBL_MAX + delta > value:
- return QwtInterval(-DBL_MAX, -DBL_MAX + delta)
- return QwtInterval(value - delta, value + delta)
-
- def setAttribute(self, attribute, on=True):
- """
- Change a scale attribute
-
- :param int attribute: Attribute to change
- :param bool on: On/Off
- :return: Calculated interval
-
- .. seealso::
-
- :py:meth:`testAttribute()`
- """
- if on:
- self.__data.attributes |= attribute
- else:
- self.__data.attributes &= ~attribute
-
- def testAttribute(self, attribute):
- """
- :param int attribute: Attribute to be tested
- :return: True, if attribute is enabled
-
- .. seealso::
-
- :py:meth:`setAttribute()`
- """
- return self.__data.attributes & attribute
-
- def setAttributes(self, attributes):
- """
- Change the scale attribute
-
- :param attributes: Set scale attributes
-
- .. seealso::
-
- :py:meth:`attributes()`
- """
- self.__data.attributes = attributes
-
- def attributes(self):
- """
- :return: Scale attributes
-
- .. seealso::
-
- :py:meth:`setAttributes()`, :py:meth:`testAttribute()`
- """
- return self.__data.attributes
-
- def setReference(self, r):
- """
- Specify a reference point
-
- :param float r: new reference value
-
- The reference point is needed if options `IncludeReference` or
- `Symmetric` are active. Its default value is 0.0.
- """
- self.__data.referenceValue = r
-
- def reference(self):
- """
- :return: the reference value
-
- .. seealso::
-
- :py:meth:`setReference()`, :py:meth:`setAttribute()`
- """
- return self.__data.referenceValue
-
- def setBase(self, base):
- """
- Set the base of the scale engine
-
- While a base of 10 is what 99.9% of all applications need
- certain scales might need a different base: f.e 2
-
- The default setting is 10
-
- :param int base: Base of the engine
-
- .. seealso::
-
- :py:meth:`base()`
- """
- self.__data.base = max([base, 2])
-
- def base(self):
- """
- :return: Base of the scale engine
-
- .. seealso::
-
- :py:meth:`setBase()`
- """
- return self.__data.base
-
-
-class QwtLinearScaleEngine(QwtScaleEngine):
- r"""
- A scale engine for linear scales
-
- The step size will fit into the pattern
- \f$\left\{ 1,2,5\right\} \cdot 10^{n}\f$, where n is an integer.
- """
-
- def __init__(self, base=10):
- super(QwtLinearScaleEngine, self).__init__(base)
-
- def autoScale(self, maxNumSteps, x1, x2, stepSize, relativeMargin=0.0):
- """
- Align and divide an interval
-
- :param int maxNumSteps: Max. number of steps
- :param float x1: First limit of the interval (In/Out)
- :param float x2: Second limit of the interval (In/Out)
- :param float stepSize: Step size
- :param float relativeMargin: Margin as a fraction of the interval width
- :return: tuple (x1, x2, stepSize)
-
- .. seealso::
-
- :py:meth:`setAttribute()`
- """
- # Apply the relative margin (fraction of the interval width) in linear space:
- if relativeMargin > 0.0:
- margin = (x2 - x1) * relativeMargin
- x1 -= margin
- x2 += margin
-
- interval = QwtInterval(x1, x2)
- interval = interval.normalized()
- interval.setMinValue(interval.minValue() - self.lowerMargin())
- interval.setMaxValue(interval.maxValue() + self.upperMargin())
- if self.testAttribute(QwtScaleEngine.Symmetric):
- interval = interval.symmetrize(self.reference())
- if self.testAttribute(QwtScaleEngine.IncludeReference):
- interval = interval.extend(self.reference())
- if interval.width() == 0.0:
- interval = self.buildInterval(interval.minValue())
- stepSize = divideInterval(interval.width(), max([maxNumSteps, 1]), self.base())
- if not self.testAttribute(QwtScaleEngine.Floating):
- interval = self.align(interval, stepSize)
- x1 = interval.minValue()
- x2 = interval.maxValue()
- if self.testAttribute(QwtScaleEngine.Inverted):
- x1, x2 = x2, x1
- stepSize = -stepSize
- return x1, x2, stepSize
-
- def divideScale(self, x1, x2, maxMajorSteps, maxMinorSteps, stepSize=0.0):
- """
- Calculate a scale division for an interval
-
- :param float x1: First interval limit
- :param float x2: Second interval limit
- :param int maxMajorSteps: Maximum for the number of major steps
- :param int maxMinorSteps: Maximum number of minor steps
- :param float stepSize: Step size. If stepSize == 0.0, the scaleEngine calculates one
- :return: Calculated scale division
- """
- interval = QwtInterval(x1, x2).normalized()
- if interval.width() <= 0:
- return QwtScaleDiv()
- stepSize = abs(stepSize)
- if stepSize == 0.0:
- if maxMajorSteps < 1:
- maxMajorSteps = 1
- stepSize = divideInterval(interval.width(), maxMajorSteps, self.base())
- scaleDiv = QwtScaleDiv()
- if stepSize != 0.0:
- ticks = self.buildTicks(interval, stepSize, maxMinorSteps)
- scaleDiv = QwtScaleDiv(interval, ticks)
- if x1 > x2:
- scaleDiv.invert()
- return scaleDiv
-
- def buildTicks(self, interval, stepSize, maxMinorSteps):
- """
- Calculate ticks for an interval
-
- :param qwt.interval.QwtInterval interval: Interval
- :param float stepSize: Step size
- :param int maxMinorSteps: Maximum number of minor steps
- :return: Calculated ticks
- """
- ticks = [[] for _i in range(QwtScaleDiv.NTickTypes)]
- boundingInterval = self.align(interval, stepSize)
- ticks[QwtScaleDiv.MajorTick] = self.buildMajorTicks(boundingInterval, stepSize)
- if maxMinorSteps > 0:
- self.buildMinorTicks(ticks, maxMinorSteps, stepSize)
- for i in range(QwtScaleDiv.NTickTypes):
- ticks[i] = self.strip(ticks[i], interval)
- for j in range(len(ticks[i])):
- if qwtFuzzyCompare(ticks[i][j], 0.0, stepSize) == 0:
- ticks[i][j] = 0.0
- return ticks
-
- def buildMajorTicks(self, interval, stepSize):
- """
- Calculate major ticks for an interval
-
- :param qwt.interval.QwtInterval interval: Interval
- :param float stepSize: Step size
- :return: Calculated ticks
- """
- numTicks = min([round(interval.width() / stepSize) + 1, 10000])
- if np.isnan(numTicks):
- numTicks = 0
- ticks = [interval.minValue()]
- for i in range(1, int(numTicks - 1)):
- ticks += [interval.minValue() + i * stepSize]
- ticks += [interval.maxValue()]
- return ticks
-
- def buildMinorTicks(self, ticks, maxMinorSteps, stepSize):
- """
- Calculate minor ticks for an interval
-
- :param list ticks: Major ticks (returned)
- :param int maxMinorSteps: Maximum number of minor steps
- :param float stepSize: Step size
- """
- minStep = qwtStepSize(stepSize, maxMinorSteps, self.base())
- if minStep == 0.0:
- return
- numTicks = int(math.ceil(abs(stepSize / minStep)) - 1)
- medIndex = -1
- if numTicks % 2:
- medIndex = numTicks // 2
- for val in ticks[QwtScaleDiv.MajorTick]:
- for k in range(numTicks):
- val += minStep
- alignedValue = val
- if qwtFuzzyCompare(val, 0.0, stepSize) == 0:
- alignedValue = 0.0
- if k == medIndex:
- ticks[QwtScaleDiv.MediumTick] += [alignedValue]
- else:
- ticks[QwtScaleDiv.MinorTick] += [alignedValue]
-
- def align(self, interval, stepSize):
- """
- Align an interval to a step size
-
- The limits of an interval are aligned that both are integer
- multiples of the step size.
-
- :param qwt.interval.QwtInterval interval: Interval
- :param float stepSize: Step size
- :return: Aligned interval
- """
- x1 = interval.minValue()
- x2 = interval.maxValue()
- eps = 0.000000000001
- if -DBL_MAX + stepSize <= x1:
- x = floorEps(x1, stepSize)
- if abs(x) <= eps or not qFuzzyCompare(x1, x):
- x1 = x
- if DBL_MAX - stepSize >= x2:
- x = ceilEps(x2, stepSize)
- if abs(x) <= eps or not qFuzzyCompare(x2, x):
- x2 = x
- return QwtInterval(x1, x2)
-
-
-class QwtLogScaleEngine(QwtScaleEngine):
- """
- A scale engine for logarithmic scales
-
- The step size is measured in *decades* and the major step size will be
- adjusted to fit the pattern {1,2,3,5}.10**n, where n is a natural number
- including zero.
-
- .. warning::
-
- The step size as well as the margins are measured in *decades*.
- """
-
- def __init__(self, base=10):
- super(QwtLogScaleEngine, self).__init__(base)
- self.setTransformation(QwtLogTransform())
-
- def autoScale(self, maxNumSteps, x1, x2, stepSize, relativeMargin=0.0):
- """
- Align and divide an interval
-
- :param int maxNumSteps: Max. number of steps
- :param float x1: First limit of the interval (In/Out)
- :param float x2: Second limit of the interval (In/Out)
- :param float stepSize: Step size
- :param float relativeMargin: Margin as a fraction of the interval width
- :return: tuple (x1, x2, stepSize)
-
- .. seealso::
-
- :py:meth:`setAttribute()`
- """
- if x1 > x2:
- x1, x2 = x2, x1
- logBase = self.base()
-
- # Apply the relative margin (fraction of the interval width) in logarithmic
- # space, and convert back to linear space.
- if relativeMargin is not None:
- x1 = min(max([x1, LOG_MIN]), LOG_MAX)
- x2 = min(max([x2, LOG_MIN]), LOG_MAX)
- log_margin = math.log(x2 / x1, logBase) * relativeMargin
- x1 /= math.pow(logBase, log_margin)
- x2 *= math.pow(logBase, log_margin)
-
- interval = QwtInterval(
- x1 / math.pow(logBase, self.lowerMargin()),
- x2 * math.pow(logBase, self.upperMargin()),
- )
- if interval.maxValue() / interval.minValue() < logBase:
- linearScaler = QwtLinearScaleEngine()
- linearScaler.setAttributes(self.attributes())
- linearScaler.setReference(self.reference())
- linearScaler.setMargins(self.lowerMargin(), self.upperMargin())
-
- x1, x2, stepSize = linearScaler.autoScale(maxNumSteps, x1, x2, stepSize)
-
- linearInterval = QwtInterval(x1, x2).normalized()
- linearInterval = linearInterval.limited(LOG_MIN, LOG_MAX)
-
- if linearInterval.maxValue() / linearInterval.minValue() < logBase:
- # The min / max interval is too short to be represented as a log scale.
- # Set the step to 0, so that a new step is calculated and a linear scale is used.
- stepSize = 0.0
- return x1, x2, stepSize
-
- logRef = 1.0
- if self.reference() > LOG_MIN / 2:
- logRef = min([self.reference(), LOG_MAX / 2])
-
- if self.testAttribute(QwtScaleEngine.Symmetric):
- delta = max([interval.maxValue() / logRef, logRef / interval.minValue()])
- interval.setInterval(logRef / delta, logRef * delta)
-
- if self.testAttribute(QwtScaleEngine.IncludeReference):
- interval = interval.extend(logRef)
-
- interval = interval.limited(LOG_MIN, LOG_MAX)
-
- if interval.width() == 0.0:
- interval = self.buildInterval(interval.minValue())
-
- stepSize = self.divideInterval(
- qwtLogInterval(logBase, interval).width(), max([maxNumSteps, 1])
- )
- if stepSize < 1.0:
- stepSize = 1.0
-
- if not self.testAttribute(QwtScaleEngine.Floating):
- interval = self.align(interval, stepSize)
-
- x1 = interval.minValue()
- x2 = interval.maxValue()
-
- if self.testAttribute(QwtScaleEngine.Inverted):
- x1, x2 = x2, x1
- stepSize = -stepSize
-
- return x1, x2, stepSize
-
- def divideScale(self, x1, x2, maxMajorSteps, maxMinorSteps, stepSize=0.0):
- """
- Calculate a scale division for an interval
-
- :param float x1: First interval limit
- :param float x2: Second interval limit
- :param int maxMajorSteps: Maximum for the number of major steps
- :param int maxMinorSteps: Maximum number of minor steps
- :param float stepSize: Step size. If stepSize == 0.0, the scaleEngine calculates one
- :return: Calculated scale division
- """
- interval = QwtInterval(x1, x2).normalized()
- interval = interval.limited(LOG_MIN, LOG_MAX)
-
- if interval.width() <= 0:
- return QwtScaleDiv()
-
- logBase = self.base()
-
- if interval.maxValue() / interval.minValue() < logBase:
- linearScaler = QwtLinearScaleEngine()
- linearScaler.setAttributes(self.attributes())
- linearScaler.setReference(self.reference())
- linearScaler.setMargins(self.lowerMargin(), self.upperMargin())
- return linearScaler.divideScale(
- x1, x2, maxMajorSteps, maxMinorSteps, stepSize
- )
-
- stepSize = abs(stepSize)
- if stepSize == 0.0:
- if maxMajorSteps < 1:
- maxMajorSteps = 1
- stepSize = self.divideInterval(
- qwtLogInterval(logBase, interval).width(), maxMajorSteps
- )
- if stepSize < 1.0:
- stepSize = 1.0
-
- scaleDiv = QwtScaleDiv()
- if stepSize != 0.0:
- ticks = self.buildTicks(interval, stepSize, maxMinorSteps)
- scaleDiv = QwtScaleDiv(interval, ticks)
-
- if x1 > x2:
- scaleDiv.invert()
-
- return scaleDiv
-
- def buildTicks(self, interval, stepSize, maxMinorSteps):
- """
- Calculate ticks for an interval
-
- :param qwt.interval.QwtInterval interval: Interval
- :param float stepSize: Step size
- :param int maxMinorSteps: Maximum number of minor steps
- :return: Calculated ticks
- """
- ticks = [[] for _i in range(QwtScaleDiv.NTickTypes)]
- boundingInterval = self.align(interval, stepSize)
- ticks[QwtScaleDiv.MajorTick] = self.buildMajorTicks(boundingInterval, stepSize)
- if maxMinorSteps > 0:
- self.buildMinorTicks(ticks, maxMinorSteps, stepSize)
- for i in range(QwtScaleDiv.NTickTypes):
- ticks[i] = self.strip(ticks[i], interval)
- return ticks
-
- def buildMajorTicks(self, interval, stepSize):
- """
- Calculate major ticks for an interval
-
- :param qwt.interval.QwtInterval interval: Interval
- :param float stepSize: Step size
- :return: Calculated ticks
- """
- width = qwtLogInterval(self.base(), interval).width()
- numTicks = min([int(round(width / stepSize)) + 1, 10000])
-
- lxmin = math.log(interval.minValue())
- lxmax = math.log(interval.maxValue())
- lstep = (lxmax - lxmin) / float(numTicks - 1)
-
- ticks = [interval.minValue()]
- for i in range(1, numTicks - 1):
- ticks += [math.exp(lxmin + float(i) * lstep)]
- ticks += [interval.maxValue()]
- return ticks
-
- def buildMinorTicks(self, ticks, maxMinorSteps, stepSize):
- """
- Calculate minor ticks for an interval
-
- :param list ticks: Major ticks (returned)
- :param int maxMinorSteps: Maximum number of minor steps
- :param float stepSize: Step size
- """
- logBase = self.base()
-
- if stepSize < 1.1:
- minStep = self.divideInterval(stepSize, maxMinorSteps + 1)
- if minStep == 0.0:
- return
-
- numSteps = int(round(stepSize / minStep))
-
- mediumTickIndex = -1
- if numSteps > 2 and numSteps % 2 == 0:
- mediumTickIndex = numSteps // 2
-
- for v in ticks[QwtScaleDiv.MajorTick]:
- s = logBase / numSteps
- if s >= 1.0:
- if not qFuzzyCompare(s, 1.0):
- ticks[QwtScaleDiv.MinorTick] += [v * s]
- for j in range(2, numSteps):
- ticks[QwtScaleDiv.MinorTick] += [v * j * s]
- else:
- for j in range(1, numSteps):
- tick = v + j * v * (logBase - 1) / numSteps
- if j == mediumTickIndex:
- ticks[QwtScaleDiv.MediumTick] += [tick]
- else:
- ticks[QwtScaleDiv.MinorTick] += [tick]
-
- else:
- minStep = self.divideInterval(stepSize, maxMinorSteps)
- if minStep == 0.0:
- return
-
- if minStep < 1.0:
- minStep = 1.0
-
- numTicks = int(round(stepSize / minStep)) - 1
-
- if qwtFuzzyCompare((numTicks + 1) * minStep, stepSize, stepSize) > 0:
- numTicks = 0
-
- if numTicks < 1:
- return
-
- mediumTickIndex = -1
- if numTicks > 2 and numTicks % 2:
- mediumTickIndex = numTicks // 2
-
- minFactor = max([math.pow(logBase, minStep), float(logBase)])
-
- for tick in ticks[QwtScaleDiv.MajorTick]:
- for j in range(numTicks):
- tick *= minFactor
- if j == mediumTickIndex:
- ticks[QwtScaleDiv.MediumTick] += [tick]
- else:
- ticks[QwtScaleDiv.MinorTick] += [tick]
-
- def align(self, interval, stepSize):
- """
- Align an interval to a step size
-
- The limits of an interval are aligned that both are integer
- multiples of the step size.
-
- :param qwt.interval.QwtInterval interval: Interval
- :param float stepSize: Step size
- :return: Aligned interval
- """
- intv = qwtLogInterval(self.base(), interval)
-
- x1 = floorEps(intv.minValue(), stepSize)
- if qwtFuzzyCompare(interval.minValue(), x1, stepSize) == 0:
- x1 = interval.minValue()
-
- x2 = ceilEps(intv.maxValue(), stepSize)
- if qwtFuzzyCompare(interval.maxValue(), x2, stepSize) == 0:
- x2 = interval.maxValue()
-
- return qwtPowInterval(self.base(), QwtInterval(x1, x2))
-
-
-class QwtDateTimeScaleEngine(QwtLinearScaleEngine):
- """
- A scale engine for datetime scales that creates intelligent time-based tick intervals.
-
- This engine calculates tick intervals that correspond to meaningful time units
- (seconds, minutes, hours, days, weeks, months, years) rather than arbitrary
- numerical spacing.
- """
-
- # Time intervals in seconds
- TIME_INTERVALS = [
- 1, # 1 second
- 5, # 5 seconds
- 10, # 10 seconds
- 15, # 15 seconds
- 30, # 30 seconds
- 60, # 1 minute
- 2 * 60, # 2 minutes
- 5 * 60, # 5 minutes
- 10 * 60, # 10 minutes
- 15 * 60, # 15 minutes
- 30 * 60, # 30 minutes
- 60 * 60, # 1 hour
- 2 * 60 * 60, # 2 hours
- 3 * 60 * 60, # 3 hours
- 6 * 60 * 60, # 6 hours
- 12 * 60 * 60, # 12 hours
- 24 * 60 * 60, # 1 day
- 2 * 24 * 60 * 60, # 2 days
- 7 * 24 * 60 * 60, # 1 week
- 2 * 7 * 24 * 60 * 60, # 2 weeks
- 30 * 24 * 60 * 60, # 1 month (approx)
- 3 * 30 * 24 * 60 * 60, # 3 months (approx)
- 6 * 30 * 24 * 60 * 60, # 6 months (approx)
- 365 * 24 * 60 * 60, # 1 year (approx)
- ]
-
- def __init__(self, base=10):
- super(QwtDateTimeScaleEngine, self).__init__(base)
-
- def divideScale(self, x1, x2, maxMajorSteps, maxMinorSteps, stepSize=0.0):
- """
- Calculate a scale division for a datetime interval
-
- :param float x1: First interval limit (Unix timestamp)
- :param float x2: Second interval limit (Unix timestamp)
- :param int maxMajorSteps: Maximum for the number of major steps
- :param int maxMinorSteps: Maximum number of minor steps
- :param float stepSize: Step size. If stepSize == 0.0, calculates intelligent datetime step
- :return: Calculated scale division
- """
- interval = QwtInterval(x1, x2).normalized()
- if interval.width() <= 0:
- return QwtScaleDiv()
-
- # If stepSize is provided and > 0, use parent implementation
- if stepSize > 0.0:
- return super(QwtDateTimeScaleEngine, self).divideScale(
- x1, x2, maxMajorSteps, maxMinorSteps, stepSize
- )
-
- # Calculate intelligent datetime step size
- duration = interval.width() # Duration in seconds
-
- # Find the best time interval for the given duration and max steps
- best_step = self._find_best_time_step(duration, maxMajorSteps)
-
- # Use the calculated datetime step
- scaleDiv = QwtScaleDiv()
- if best_step > 0.0:
- ticks = self.buildTicks(interval, best_step, maxMinorSteps)
- scaleDiv = QwtScaleDiv(interval, ticks)
-
- if x1 > x2:
- scaleDiv.invert()
-
- return scaleDiv
-
- def _find_best_time_step(self, duration, max_steps):
- """
- Find the best time interval step for the given duration and maximum steps.
-
- :param float duration: Total duration in seconds
- :param int max_steps: Maximum number of major ticks
- :return: Best step size in seconds
- """
- if max_steps < 1:
- max_steps = 1
-
- # Calculate the target step size
- target_step = duration / max_steps
-
- # Find the time interval that is closest to our target
- best_step = self.TIME_INTERVALS[0]
- min_error = abs(target_step - best_step)
-
- for interval in self.TIME_INTERVALS:
- error = abs(target_step - interval)
- if error < min_error:
- min_error = error
- best_step = interval
- # If the interval is getting much larger than target, stop
- elif interval > target_step * 2:
- break
-
- return float(best_step)
-
- def buildMinorTicks(self, ticks, maxMinorSteps, stepSize):
- """
- Calculate minor ticks for datetime intervals
-
- :param list ticks: List of tick arrays
- :param int maxMinorSteps: Maximum number of minor steps
- :param float stepSize: Major tick step size
- """
- if maxMinorSteps < 1:
- return
-
- # For datetime, create intelligent minor tick intervals
- minor_step = self._get_minor_step(stepSize, maxMinorSteps)
-
- if minor_step <= 0:
- return
-
- major_ticks = ticks[QwtScaleDiv.MajorTick]
- if len(major_ticks) < 2:
- return
-
- minor_ticks = []
-
- # Generate minor ticks between each pair of major ticks
- for i in range(len(major_ticks) - 1):
- start = major_ticks[i]
- end = major_ticks[i + 1]
-
- # Add minor ticks between start and end
- current = start + minor_step
- while current < end:
- minor_ticks.append(current)
- current += minor_step
-
- ticks[QwtScaleDiv.MinorTick] = minor_ticks
-
- def _get_minor_step(self, major_step, max_minor_steps):
- """
- Calculate appropriate minor tick step size for datetime intervals
-
- :param float major_step: Major tick step size in seconds
- :param int max_minor_steps: Maximum number of minor steps
- :return: Minor tick step size in seconds
- """
- # Define sensible minor tick divisions for different time scales
- if major_step >= 365 * 24 * 60 * 60: # 1 year or more
- return 30 * 24 * 60 * 60 # 1 month
- elif major_step >= 30 * 24 * 60 * 60: # 1 month or more
- return 7 * 24 * 60 * 60 # 1 week
- elif major_step >= 7 * 24 * 60 * 60: # 1 week or more
- return 24 * 60 * 60 # 1 day
- elif major_step >= 24 * 60 * 60: # 1 day or more
- return 6 * 60 * 60 # 6 hours
- elif major_step >= 60 * 60: # 1 hour or more
- return 15 * 60 # 15 minutes
- elif major_step >= 10 * 60: # 10 minutes or more
- return 2 * 60 # 2 minutes
- elif major_step >= 60: # 1 minute or more
- return 15 # 15 seconds
- elif major_step >= 10: # 10 seconds or more
- return 2 # 2 seconds
- else: # Less than 10 seconds
- return major_step / max(max_minor_steps, 2)
diff --git a/qwt/scale_map.py b/qwt/scale_map.py
deleted file mode 100644
index b99fe65..0000000
--- a/qwt/scale_map.py
+++ /dev/null
@@ -1,308 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the Qwt License
-# Copyright (c) 2002 Uwe Rathmann, for the original C++ code
-# Copyright (c) 2015 Pierre Raybaut, for the Python translation/optimization
-# (see LICENSE file for more details)
-
-"""
-QwtScaleMap
------------
-
-.. autoclass:: QwtScaleMap
- :members:
-"""
-
-from qtpy.QtCore import QPointF, QRectF
-
-from qwt._math import qwtFuzzyCompare
-
-
-class QwtScaleMap(object):
- """
- A scale map
-
- `QwtScaleMap` offers transformations from the coordinate system
- of a scale into the linear coordinate system of a paint device
- and vice versa.
-
- The scale and paint device intervals are both set to [0,1].
-
- .. py:class:: QwtScaleMap([other=None])
-
- Constructor (eventually, copy constructor)
-
- :param qwt.scale_map.QwtScaleMap other: Other scale map
-
- .. py:class:: QwtScaleMap(p1, p2, s1, s2)
- :noindex:
-
- Constructor (was provided by `PyQwt` but not by `Qwt`)
-
- :param int p1: First border of the paint interval
- :param int p2: Second border of the paint interval
- :param float s1: First border of the scale interval
- :param float s2: Second border of the scale interval
- """
-
- def __init__(self, *args):
- self.__transform = None # QwtTransform
- self.__s1 = 0.0
- self.__s2 = 1.0
- self.__p1 = 0.0
- self.__p2 = 1.0
- other = None
- if len(args) == 1:
- (other,) = args
- elif len(args) == 4:
- p1, p2, s1, s2 = args
- self.__s1 = s1
- self.__s2 = s2
- self.__p1 = p1
- self.__p2 = p2
- elif len(args) != 0:
- raise TypeError(
- "%s() takes 0, 1, or 4 argument(s) (%s given)"
- % (self.__class__.__name__, len(args))
- )
- if other is None:
- self.__cnv = 1.0
- self.__ts1 = 0.0
- else:
- self.__s1 = other.__s1
- self.__s2 = other.__s2
- self.__p1 = other.__p1
- self.__p2 = other.__p2
- self.__cnv = other.__cnv
- self.__ts1 = other.__ts1
- if other.__transform:
- self.__transform = other.__transform.copy()
-
- def __eq__(self, other):
- return (
- self.__s1 == other.__s1
- and self.__s2 == other.__s2
- and self.__p1 == other.__p1
- and self.__p2 == other.__p2
- and self.__cnv == other.__cnv
- and self.__ts1 == other.__ts1
- )
-
- def s1(self):
- """
- :return: First border of the scale interval
- """
- return self.__s1
-
- def s2(self):
- """
- :return: Second border of the scale interval
- """
- return self.__s2
-
- def p1(self):
- """
- :return: First border of the paint interval
- """
- return self.__p1
-
- def p2(self):
- """
- :return: Second border of the paint interval
- """
- return self.__p2
-
- def pDist(self):
- """
- :return: `abs(p2() - p1())`
- """
- return abs(self.__p2 - self.__p1)
-
- def sDist(self):
- """
- :return: `abs(s2() - s1())`
- """
- return abs(self.__s2 - self.__s1)
-
- def transform_scalar(self, s):
- """
- Transform a point related to the scale interval into an point
- related to the interval of the paint device
-
- :param float s: Value relative to the coordinates of the scale
- :return: Transformed value
-
- .. seealso::
-
- :py:meth:`invTransform_scalar()`
- """
- if self.__transform:
- s = self.__transform.transform(s)
- return self.__p1 + (s - self.__ts1) * self.__cnv
-
- def invTransform_scalar(self, p):
- """
- Transform an paint device value into a value in the
- interval of the scale.
-
- :param float p: Value relative to the coordinates of the paint device
- :return: Transformed value
-
- .. seealso::
-
- :py:meth:`transform_scalar()`
- """
- if self.__cnv == 0:
- s = self.__ts1 # avoid divide by zero
- else:
- s = self.__ts1 + (p - self.__p1) / self.__cnv
- if self.__transform:
- s = self.__transform.invTransform(s)
- return s
-
- def isInverting(self):
- """
- :return: True, when ( p1() < p2() ) != ( s1() < s2() )
- """
- return (self.__p1 < self.__p2) != (self.__s1 < self.__s2)
-
- def setTransformation(self, transform):
- """
- Initialize the map with a transformation
-
- :param qwt.transform.QwtTransform transform: Transformation
- """
- if self.__transform != transform:
- self.__transform = transform
- self.setScaleInterval(self.__s1, self.__s2)
-
- def transformation(self):
- """
- :return: the transformation
- """
- return self.__transform
-
- def setScaleInterval(self, s1, s2):
- """
- Specify the borders of the scale interval
-
- :param float s1: first border
- :param float s2: second border
-
- .. warning::
-
- Scales might be aligned to transformation depending boundaries
- """
- self.__s1 = s1
- self.__s2 = s2
- if self.__transform:
- self.__s1 = self.__transform.bounded(self.__s1)
- self.__s2 = self.__transform.bounded(self.__s2)
- self.updateFactor()
-
- def setPaintInterval(self, p1, p2):
- """
- Specify the borders of the paint device interval
-
- :param float p1: first border
- :param float p2: second border
- """
- self.__p1 = p1
- self.__p2 = p2
- self.updateFactor()
-
- def updateFactor(self):
- self.__ts1 = self.__s1
- ts2 = self.__s2
- if self.__transform:
- self.__ts1 = self.__transform.transform(self.__ts1)
- ts2 = self.__transform.transform(ts2)
- if self.__ts1 == ts2:
- # Degenerate scale: collapse every value to ``p1`` (matches the
- # symmetric guard in ``invTransform_scalar`` and the C++ Qwt
- # behaviour).
- self.__cnv = 0.0
- else:
- self.__cnv = (self.__p2 - self.__p1) / (ts2 - self.__ts1)
-
- def transform(self, *args):
- """
- Transform a rectangle from scale to paint coordinates.
-
- Transfom a scalar:
-
- :param float scalar: Scalar
-
- Transfom a rectangle:
-
- :param qwt.scale_map.QwtScaleMap xMap: X map
- :param qwt.scale_map.QwtScaleMap yMap: Y map
- :param QRectF rect: Rectangle in paint coordinates
-
- Transfom a point:
-
- :param qwt.scale_map.QwtScaleMap xMap: X map
- :param qwt.scale_map.QwtScaleMap yMap: Y map
- :param QPointF pos: Position in scale coordinates
-
- .. seealso::
-
- :py:meth:`invTransform()`
- """
- nargs = len(args)
- if nargs == 1:
- # Scalar transform: inline the fast path for the dominant case
- # (avoids one Python call frame per tick label).
- s = args[0]
- if self.__transform:
- s = self.__transform.transform(s)
- return self.__p1 + (s - self.__ts1) * self.__cnv
- elif nargs == 3 and isinstance(args[2], QPointF):
- xMap, yMap, pos = args
- return QPointF(xMap.transform(pos.x()), yMap.transform(pos.y()))
- elif nargs == 3 and isinstance(args[2], QRectF):
- xMap, yMap, rect = args
- x1 = xMap.transform(rect.left())
- x2 = xMap.transform(rect.right())
- y1 = yMap.transform(rect.top())
- y2 = yMap.transform(rect.bottom())
- if x2 < x1:
- x1, x2 = x2, x1
- if y2 < y1:
- y1, y2 = y2, y1
- if qwtFuzzyCompare(x1, 0.0, x2 - x1) == 0:
- x1 = 0.0
- if qwtFuzzyCompare(x2, 0.0, x2 - x1) == 0:
- x2 = 0.0
- if qwtFuzzyCompare(y1, 0.0, y2 - y1) == 0:
- y1 = 0.0
- if qwtFuzzyCompare(y2, 0.0, y2 - y1) == 0:
- y2 = 0.0
- return QRectF(x1, y1, x2 - x1, y2 - y1)
- else:
- raise TypeError(
- "%s().transform() takes 1 or 3 argument(s) (%s "
- "given)" % (self.__class__.__name__, len(args))
- )
-
- def invTransform(self, *args):
- """Transform from paint to scale coordinates
-
- Scalar: scalemap.invTransform(scalar)
- Point (QPointF): scalemap.invTransform(xMap, yMap, pos)
- Rectangle (QRectF): scalemap.invTransform(xMap, yMap, rect)
- """
- if len(args) == 1:
- # Scalar transform
- return self.invTransform_scalar(args[0])
- elif isinstance(args[2], QPointF):
- xMap, yMap, pos = args
- return QPointF(xMap.invTransform(pos.x()), yMap.invTransform(pos.y()))
- elif isinstance(args[2], QRectF):
- xMap, yMap, rect = args
- x1 = xMap.invTransform(rect.left())
- x2 = xMap.invTransform(rect.right())
- y1 = yMap.invTransform(rect.top())
- y2 = yMap.invTransform(rect.bottom())
- r = QRectF(x1, y1, x2 - x1, y2 - y1)
- return r.normalized()
diff --git a/qwt/scale_widget.py b/qwt/scale_widget.py
deleted file mode 100644
index fb3eac6..0000000
--- a/qwt/scale_widget.py
+++ /dev/null
@@ -1,853 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the Qwt License
-# Copyright (c) 2002 Uwe Rathmann, for the original C++ code
-# Copyright (c) 2015 Pierre Raybaut, for the Python translation/optimization
-# (see LICENSE file for more details)
-
-"""
-QwtScaleWidget
---------------
-
-.. autoclass:: QwtScaleWidget
- :members:
-"""
-
-import math
-
-from qtpy.QtCore import QObject, QRectF, QSize, Qt, Signal
-from qtpy.QtGui import QPainter, QPalette
-from qtpy.QtWidgets import QSizePolicy, QStyle, QStyleOption, QWidget
-
-from qwt.color_map import QwtColorMap, QwtLinearColorMap
-from qwt.interval import QwtInterval
-from qwt.painter import QwtPainter
-from qwt.scale_draw import QwtScaleDraw
-from qwt.scale_engine import QwtLinearScaleEngine
-from qwt.text import QwtText
-
-
-class ColorBar(object):
- def __init__(self):
- self.isEnabled = None
- self.width = None
- self.interval = QwtInterval()
- self.colorMap = QwtColorMap()
-
-
-class QwtScaleWidget_PrivateData(QObject):
- def __init__(self):
- QObject.__init__(self)
-
- self.scaleDraw = None
- self.borderDist = [None] * 2
- self.minBorderDist = [None] * 2
- self.scaleLength = None
- self.margin = None
- self.titleOffset = None
- self.spacing = None
- self.title = QwtText()
- self.layoutFlags = None
- self.colorBar = ColorBar()
-
-
-class QwtScaleWidget(QWidget):
- """
- A Widget which contains a scale
-
- This Widget can be used to decorate composite widgets with
- a scale.
-
- Layout flags:
-
- * `QwtScaleWidget.TitleInverted`: The title of vertical scales is painted from top to bottom. Otherwise it is painted from bottom to top.
-
- .. py:class:: QwtScaleWidget([parent=None])
-
- Alignment default is `QwtScaleDraw.LeftScale`.
-
- :param parent: Parent widget
- :type parent: QWidget or None
-
- .. py:class:: QwtScaleWidget(align, parent)
- :noindex:
-
- :param int align: Alignment
- :param QWidget parent: Parent widget
- """
-
- scaleDivChanged = Signal()
-
- # enum LayoutFlag
- TitleInverted = 1
-
- def __init__(self, *args):
- self.__data = None
- align = QwtScaleDraw.LeftScale
- if len(args) == 0:
- parent = None
- elif len(args) == 1:
- (parent,) = args
- elif len(args) == 2:
- align, parent = args
- else:
- raise TypeError(
- "%s() takes 0, 1 or 2 argument(s) (%s given)"
- % (self.__class__.__name__, len(args))
- )
- super(QwtScaleWidget, self).__init__(parent)
- self.initScale(align)
-
- def initScale(self, align):
- """
- Initialize the scale
-
- :param int align: Alignment
- """
- self.__data = QwtScaleWidget_PrivateData()
- self.__data.layoutFlags = 0
- if align == QwtScaleDraw.RightScale:
- self.__data.layoutFlags |= self.TitleInverted
-
- self.__data.borderDist = [0, 0]
- self.__data.minBorderDist = [0, 0]
- self.__data.margin = 4
- self.__data.titleOffset = 0
- self.__data.spacing = 2
-
- self.__data.scaleDraw = QwtScaleDraw()
- self.__data.scaleDraw.setAlignment(align)
- self.__data.scaleDraw.setLength(10)
-
- self.__data.scaleDraw.setScaleDiv(
- QwtLinearScaleEngine().divideScale(0.0, 100.0, 10, 5)
- )
-
- self.__data.colorBar.colorMap = QwtLinearColorMap()
- self.__data.colorBar.isEnabled = False
- self.__data.colorBar.width = 10
-
- flags = Qt.AlignmentFlag(Qt.AlignHCenter | Qt.TextExpandTabs | Qt.TextWordWrap)
- self.__data.title.setRenderFlags(flags)
- self.__data.title.setFont(self.font())
-
- policy = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)
- if self.__data.scaleDraw.orientation() == Qt.Vertical:
- policy.transpose()
-
- self.setSizePolicy(policy)
-
- self.setAttribute(Qt.WA_WState_OwnSizePolicy, False)
-
- def setLayoutFlag(self, flag, on=True):
- """
- Toggle an layout flag
-
- :param int flag: Layout flag
- :param bool on: True/False
-
- .. seealso::
-
- :py:meth:`testLayoutFlag()`
- """
- if (self.__data.layoutFlags & flag != 0) != on:
- if on:
- self.__data.layoutFlags |= flag
- else:
- self.__data.layoutFlags &= ~flag
- self.update()
-
- def testLayoutFlag(self, flag):
- """
- Test a layout flag
-
- :param int flag: Layout flag
- :return: True/False
-
- .. seealso::
-
- :py:meth:`setLayoutFlag()`
- """
- return self.__data.layoutFlags & flag
-
- def setTitle(self, title):
- """
- Give title new text contents
-
- :param title: New title
- :type title: qwt.text.QwtText or str
-
- .. seealso::
-
- :py:meth:`title()`
- """
- if isinstance(title, QwtText):
- flags = title.renderFlags() & (~int(Qt.AlignTop | Qt.AlignBottom))
- title.setRenderFlags(flags)
- if title != self.__data.title:
- self.__data.title = title
- self.layoutScale()
- else:
- if self.__data.title.text() != title:
- self.__data.title.setText(title)
- self.layoutScale()
-
- def setAlignment(self, alignment):
- """
- Change the alignment
-
- :param int alignment: New alignment
-
- Valid alignment values: see :py:class:`qwt.scale_draw.QwtScaleDraw`
-
- .. seealso::
-
- :py:meth:`alignment()`
- """
- if self.__data.scaleDraw:
- self.__data.scaleDraw.setAlignment(alignment)
- if not self.testAttribute(Qt.WA_WState_OwnSizePolicy):
- policy = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)
- if self.__data.scaleDraw.orientation() == Qt.Vertical:
- policy.transpose()
- self.setSizePolicy(policy)
- self.setAttribute(Qt.WA_WState_OwnSizePolicy, False)
- self.layoutScale()
-
- def alignment(self):
- """
- :return: position
-
- .. seealso::
-
- :py:meth:`setAlignment()`
- """
- if not self.scaleDraw():
- return QwtScaleDraw.LeftScale
- return self.scaleDraw().alignment()
-
- def setBorderDist(self, dist1, dist2):
- """
- Specify distances of the scale's endpoints from the
- widget's borders. The actual borders will never be less
- than minimum border distance.
-
- :param int dist1: Left or top Distance
- :param int dist2: Right or bottom distance
-
- .. seealso::
-
- :py:meth:`borderDist()`
- """
- if dist1 != self.__data.borderDist[0] or dist2 != self.__data.borderDist[1]:
- self.__data.borderDist = [dist1, dist2]
- self.layoutScale()
-
- def setMargin(self, margin):
- """
- Specify the margin to the colorBar/base line.
-
- :param int margin: Margin
-
- .. seealso::
-
- :py:meth:`margin()`
- """
- margin = max([0, margin])
- if margin != self.__data.margin:
- self.__data.margin = margin
- self.layoutScale()
-
- def setSpacing(self, spacing):
- """
- Specify the distance between color bar, scale and title
-
- :param int spacing: Spacing
-
- .. seealso::
-
- :py:meth:`spacing()`
- """
- spacing = max([0, spacing])
- if spacing != self.__data.spacing:
- self.__data.spacing = spacing
- self.layoutScale()
-
- def setLabelAlignment(self, alignment):
- """
- Change the alignment for the labels.
-
- :param int spacing: Spacing
-
- .. seealso::
-
- :py:meth:`qwt.scale_draw.QwtScaleDraw.setLabelAlignment()`,
- :py:meth:`setLabelRotation()`
- """
- self.__data.scaleDraw.setLabelAlignment(alignment)
- self.layoutScale()
-
- def setLabelRotation(self, rotation):
- """
- Change the rotation for the labels.
-
- :param float rotation: Rotation
-
- .. seealso::
-
- :py:meth:`qwt.scale_draw.QwtScaleDraw.setLabelRotation()`,
- :py:meth:`setLabelFlags()`
- """
- self.__data.scaleDraw.setLabelRotation(rotation)
- self.layoutScale()
-
- def setLabelAutoSize(self, state):
- """
- Set the automatic size option for labels (default: on).
-
- :param bool state: On/off
-
- .. seealso::
-
- :py:meth:`qwt.scale_draw.QwtScaleDraw.setLabelAutoSize()`
- """
- self.__data.scaleDraw.setLabelAutoSize(state)
- self.layoutScale()
-
- def setScaleDraw(self, scaleDraw):
- """
- Set a scale draw
-
- scaleDraw has to be created with new and will be deleted in
- class destructor or the next call of `setScaleDraw()`.
- scaleDraw will be initialized with the attributes of
- the previous scaleDraw object.
-
- :param qwt.scale_draw.QwtScaleDraw scaleDraw: ScaleDraw object
-
- .. seealso::
-
- :py:meth:`scaleDraw()`
- """
- if scaleDraw is None or scaleDraw == self.__data.scaleDraw:
- return
- sd = self.__data.scaleDraw
- if sd is not None:
- scaleDraw.setAlignment(sd.alignment())
- scaleDraw.setScaleDiv(sd.scaleDiv())
- transform = None
- if sd.scaleMap().transformation():
- transform = sd.scaleMap().transformation().copy()
- scaleDraw.setTransformation(transform)
- self.__data.scaleDraw = scaleDraw
- self.layoutScale()
-
- def scaleDraw(self):
- """
- :return: scaleDraw of this scale
-
- .. seealso::
-
- :py:meth:`qwt.scale_draw.QwtScaleDraw.setScaleDraw()`
- """
- return self.__data.scaleDraw
-
- def title(self):
- """
- :return: title
-
- .. seealso::
-
- :py:meth:`setTitle`
- """
- return self.__data.title
-
- def startBorderDist(self):
- """
- :return: start border distance
-
- .. seealso::
-
- :py:meth:`setBorderDist`
- """
- return self.__data.borderDist[0]
-
- def endBorderDist(self):
- """
- :return: end border distance
-
- .. seealso::
-
- :py:meth:`setBorderDist`
- """
- return self.__data.borderDist[1]
-
- def margin(self):
- """
- :return: margin
-
- .. seealso::
-
- :py:meth:`setMargin`
- """
- return self.__data.margin
-
- def spacing(self):
- """
- :return: distance between scale and title
-
- .. seealso::
-
- :py:meth:`setSpacing`
- """
- return self.__data.spacing
-
- def paintEvent(self, event):
- painter = QPainter(self)
- painter.setClipRegion(event.region())
- opt = QStyleOption()
- opt.initFrom(self)
- self.style().drawPrimitive(QStyle.PE_Widget, opt, painter, self)
- self.draw(painter)
-
- def draw(self, painter):
- """
- Draw the scale
-
- :param QPainter painter: Painter
- """
- self.__data.scaleDraw.draw(painter, self.palette())
- if (
- self.__data.colorBar.isEnabled
- and self.__data.colorBar.width > 0
- and self.__data.colorBar.interval.isValid()
- ):
- self.drawColorBar(painter, self.colorBarRect(self.contentsRect()))
-
- r = QRectF(self.contentsRect())
- if self.__data.scaleDraw.orientation() == Qt.Horizontal:
- r.setLeft(r.left() + self.__data.borderDist[0])
- r.setWidth(r.width() - self.__data.borderDist[1])
- else:
- r.setTop(r.top() + self.__data.borderDist[0])
- r.setHeight(r.height() - self.__data.borderDist[1])
-
- if not self.__data.title.isEmpty():
- self.drawTitle(painter, self.__data.scaleDraw.alignment(), r)
-
- def colorBarRect(self, rect):
- """
- Calculate the the rectangle for the color bar
-
- :param QRectF rect: Bounding rectangle for all components of the scale
- :return: Rectangle for the color bar
- """
- cr = QRectF(rect)
- if self.__data.scaleDraw.orientation() == Qt.Horizontal:
- cr.setLeft(cr.left() + self.__data.borderDist[0])
- cr.setWidth(cr.width() - self.__data.borderDist[1] + 1)
- else:
- cr.setTop(cr.top() + self.__data.borderDist[0])
- cr.setHeight(cr.height() - self.__data.borderDist[1] + 1)
- sda = self.__data.scaleDraw.alignment()
- if sda == QwtScaleDraw.LeftScale:
- cr.setLeft(cr.right() - self.__data.margin - self.__data.colorBar.width)
- cr.setWidth(self.__data.colorBar.width)
- elif sda == QwtScaleDraw.RightScale:
- cr.setLeft(cr.left() + self.__data.margin)
- cr.setWidth(self.__data.colorBar.width)
- elif sda == QwtScaleDraw.BottomScale:
- cr.setTop(cr.top() + self.__data.margin)
- cr.setHeight(self.__data.colorBar.width)
- elif sda == QwtScaleDraw.TopScale:
- cr.setTop(cr.bottom() - self.__data.margin - self.__data.colorBar.width)
- cr.setHeight(self.__data.colorBar.width)
- return cr
-
- def resizeEvent(self, event):
- self.layoutScale(False)
-
- def layoutScale(self, update_geometry=True):
- """
- Recalculate the scale's geometry and layout based on
- the current geometry and fonts.
-
- :param bool update_geometry: Notify the layout system and call update to redraw the scale
- """
- bd0, bd1 = self.getBorderDistHint()
- if self.__data.borderDist[0] > bd0:
- bd0 = self.__data.borderDist[0]
- if self.__data.borderDist[1] > bd1:
- bd1 = self.__data.borderDist[1]
-
- colorBarWidth = 0
- if self.__data.colorBar.isEnabled and self.__data.colorBar.interval.isValid():
- colorBarWidth = self.__data.colorBar.width + self.__data.spacing
-
- r = self.contentsRect()
- if self.__data.scaleDraw.orientation() == Qt.Vertical:
- y = r.top() + bd0
- length = r.height() - (bd0 + bd1)
- if self.__data.scaleDraw.alignment() == QwtScaleDraw.LeftScale:
- x = r.right() - 1.0 - self.__data.margin - colorBarWidth
- else:
- x = r.left() + self.__data.margin + colorBarWidth
- else:
- x = r.left() + bd0
- length = r.width() - (bd0 + bd1)
- if self.__data.scaleDraw.alignment() == QwtScaleDraw.BottomScale:
- y = r.top() + self.__data.margin + colorBarWidth
- else:
- y = r.bottom() - 1.0 - self.__data.margin - colorBarWidth
-
- self.__data.scaleDraw.move(x, y)
- self.__data.scaleDraw.setLength(length)
-
- extent = math.ceil(self.__data.scaleDraw.extent(self.font()))
- self.__data.titleOffset = (
- self.__data.margin + self.__data.spacing + colorBarWidth + extent
- )
-
- if update_geometry:
- self.updateGeometry()
-
- # The following was removed because it caused a high CPU usage
- # in guiqwt.ImageWidget. The origin of these lines was an
- # attempt to transpose PythonQwt from Qwt 6.1.2 to Qwt 6.1.5.
-
- # --> Begin of removed lines <--------------------------------------
- # # for some reason updateGeometry does not send a LayoutRequest
- # # event when the parent is not visible and has no layout
- # widget = self.parentWidget()
- # if widget and not widget.isVisible() and widget.layout() is None:
- # if widget.testAttribute(Qt.WA_WState_Polished):
- # QApplication.postEvent(
- # self.parentWidget(), QEvent(QEvent.LayoutRequest)
- # )
- # --> End of removed lines <----------------------------------------
-
- self.update()
-
- def drawColorBar(self, painter, rect):
- """
- Draw the color bar of the scale widget
-
- :param QPainter painter: Painter
- :param QRectF rect: Bounding rectangle for the color bar
-
- .. seealso::
-
- :py:meth:`setColorBarEnabled()`
- """
- if not self.__data.colorBar.interval.isValid():
- return
- sd = self.__data.scaleDraw
- QwtPainter.drawColorBar(
- painter,
- self.__data.colorBar.colorMap,
- self.__data.colorBar.interval.normalized(),
- sd.scaleMap(),
- sd.orientation(),
- rect,
- )
-
- def drawTitle(self, painter, align, rect):
- """
- Rotate and paint a title according to its position into a given rectangle.
-
- :param QPainter painter: Painter
- :param int align: Alignment
- :param QRectF rect: Bounding rectangle
- """
- r = rect
- flags = self.__data.title.renderFlags() & (
- ~int(Qt.AlignTop | Qt.AlignBottom | Qt.AlignVCenter)
- )
- if align == QwtScaleDraw.LeftScale:
- angle = -90.0
- flags |= Qt.AlignTop
- r.setRect(
- r.left(), r.bottom(), r.height(), r.width() - self.__data.titleOffset
- )
- elif align == QwtScaleDraw.RightScale:
- angle = -90.0
- flags |= Qt.AlignTop
- r.setRect(
- r.left() + self.__data.titleOffset,
- r.bottom(),
- r.height(),
- r.width() - self.__data.titleOffset,
- )
- elif align == QwtScaleDraw.BottomScale:
- angle = 0.0
- flags |= Qt.AlignBottom
- r.setTop(r.top() + self.__data.titleOffset)
- else:
- angle = 0.0
- flags |= Qt.AlignTop
- r.setBottom(r.bottom() - self.__data.titleOffset)
-
- if self.__data.layoutFlags & self.TitleInverted:
- if align in (QwtScaleDraw.LeftScale, QwtScaleDraw.RightScale):
- angle = -angle
- r.setRect(r.x() + r.height(), r.y() - r.width(), r.width(), r.height())
-
- painter.save()
- painter.setFont(self.font())
- painter.setPen(self.palette().color(QPalette.Text))
-
- painter.translate(r.x(), r.y())
- if angle != 0.0:
- painter.rotate(angle)
-
- title = self.__data.title
- title.setRenderFlags(flags)
- title.draw(painter, QRectF(0.0, 0.0, r.width(), r.height()))
-
- painter.restore()
-
- def scaleChange(self):
- """
- Notify a change of the scale
-
- This method can be overloaded by derived classes. The default
- implementation updates the geometry and repaints the widget.
- """
- self.layoutScale()
-
- def sizeHint(self):
- return self.minimumSizeHint()
-
- def minimumSizeHint(self):
- o = self.__data.scaleDraw.orientation()
- length = 0
- mbd1, mbd2 = self.getBorderDistHint()
- length += max([0, self.__data.borderDist[0] - mbd1])
- length += max([0, self.__data.borderDist[1] - mbd2])
- length += self.__data.scaleDraw.minLength(self.font())
-
- dim = self.dimForLength(length, self.font())
- if length < dim:
- length = dim
- dim = self.dimForLength(length, self.font())
-
- size = QSize(length + 2, dim)
- if o == Qt.Vertical:
- size.transpose()
-
- if self.layout() is None:
- left, top, right, bottom = 0, 0, 0, 0
- else:
- mgn = self.layout().contentsMargins()
- left, top, right, bottom = (
- mgn.left(),
- mgn.top(),
- mgn.right(),
- mgn.bottom(),
- )
- return size + QSize(left + right, top + bottom)
-
- def titleHeightForWidth(self, width):
- """
- Find the height of the title for a given width.
-
- :param int width: Width
- :return: Height
- """
- return math.ceil(self.__data.title.heightForWidth(width, self.font()))
-
- def dimForLength(self, length, scaleFont):
- """
- Find the minimum dimension for a given length.
- dim is the height, length the width seen in direction of the title.
-
- :param int length: width for horizontal, height for vertical scales
- :param QFont scaleFont: Font of the scale
- :return: height for horizontal, width for vertical scales
- """
- extent = math.ceil(self.__data.scaleDraw.extent(scaleFont))
- dim = self.__data.margin + extent + 1
- if not self.__data.title.isEmpty():
- dim += self.titleHeightForWidth(length) + self.__data.spacing
- if self.__data.colorBar.isEnabled and self.__data.colorBar.interval.isValid():
- dim += self.__data.colorBar.width + self.__data.spacing
- return dim
-
- def getBorderDistHint(self):
- """
- Calculate a hint for the border distances.
-
- This member function calculates the distance
- of the scale's endpoints from the widget borders which
- is required for the mark labels to fit into the widget.
- The maximum of this distance an the minimum border distance
- is returned.
-
- :param int start: Return parameter for the border width at the beginning of the scale
- :param int end: Return parameter for the border width at the end of the scale
-
- .. warning::
-
- The minimum border distance depends on the font.
-
- .. seealso::
-
- :py:meth:`setMinBorderDist()`, :py:meth:`getMinBorderDist()`,
- :py:meth:`setBorderDist()`
- """
- start, end = self.__data.scaleDraw.getBorderDistHint(self.font())
- if start < self.__data.minBorderDist[0]:
- start = self.__data.minBorderDist[0]
- if end < self.__data.minBorderDist[1]:
- end = self.__data.minBorderDist[1]
- return start, end
-
- def setMinBorderDist(self, start, end):
- """
- Set a minimum value for the distances of the scale's endpoints from
- the widget borders. This is useful to avoid that the scales
- are "jumping", when the tick labels or their positions change
- often.
-
- :param int start: Minimum for the start border
- :param int end: Minimum for the end border
-
- .. seealso::
-
- :py:meth:`getMinBorderDist()`, :py:meth:`getBorderDistHint()`
- """
- self.__data.minBorderDist = [start, end]
-
- def getMinBorderDist(self):
- """
- Get the minimum value for the distances of the scale's endpoints from
- the widget borders.
-
- :param int start: Return parameter for the border width at the beginning of the scale
- :param int end: Return parameter for the border width at the end of the scale
-
- .. seealso::
-
- :py:meth:`setMinBorderDist()`, :py:meth:`getBorderDistHint()`
- """
- return self.__data.minBorderDist
-
- def setScaleDiv(self, scaleDiv):
- """
- Assign a scale division
-
- The scale division determines where to set the tick marks.
-
- :param qwt.scale_div.QwtScaleDiv scaleDiv: Scale Division
-
- .. seealso::
-
- For more information about scale divisions,
- see :py:class:`qwt.scale_div.QwtScaleDiv`.
- """
- sd = self.__data.scaleDraw
- if sd.scaleDiv() != scaleDiv:
- sd.setScaleDiv(scaleDiv)
- self.layoutScale()
- self.scaleDivChanged.emit()
-
- def setTransformation(self, transformation):
- """
- Set the transformation
-
- :param qwt.transform.Transform transformation: Transformation
-
- .. seealso::
-
- :py:meth:`qwt.scale_draw.QwtAbstractScaleDraw.scaleDraw()`,
- :py:class:`qwt.scale_map.QwtScaleMap`
- """
- self.__data.scaleDraw.setTransformation(transformation)
- self.layoutScale()
-
- def setColorBarEnabled(self, on):
- """
- En/disable a color bar associated to the scale
-
- :param bool on: On/Off
-
- .. seealso::
-
- :py:meth:`isColorBarEnabled()`, :py:meth:`setColorBarWidth()`
- """
- if on != self.__data.colorBar.isEnabled:
- self.__data.colorBar.isEnabled = on
- self.layoutScale()
-
- def isColorBarEnabled(self):
- """
- :return: True, when the color bar is enabled
-
- .. seealso::
-
- :py:meth:`setColorBarEnabled()`, :py:meth:`setColorBarWidth()`
- """
- return self.__data.colorBar.isEnabled
-
- def setColorBarWidth(self, width):
- """
- Set the width of the color bar
-
- :param int width: Width
-
- .. seealso::
-
- :py:meth:`colorBarWidth()`, :py:meth:`setColorBarEnabled()`
- """
- if width != self.__data.colorBar.width:
- self.__data.colorBar.width = width
- if self.isColorBarEnabled():
- self.layoutScale()
-
- def colorBarWidth(self):
- """
- :return: Width of the color bar
-
- .. seealso::
-
- :py:meth:`setColorBarWidth()`, :py:meth:`setColorBarEnabled()`
- """
- return self.__data.colorBar.width
-
- def colorBarInterval(self):
- """
- :return: Value interval for the color bar
-
- .. seealso::
-
- :py:meth:`setColorMap()`, :py:meth:`colorMap()`
- """
- return self.__data.colorBar.interval
-
- def setColorMap(self, interval, colorMap):
- """
- Set the color map and value interval, that are used for displaying
- the color bar.
-
- :param qwt.interval.QwtInterval interval: Value interval
- :param qwt.color_map.QwtColorMap colorMap: Color map
-
- .. seealso::
-
- :py:meth:`colorMap()`, :py:meth:`colorBarInterval()`
- """
- self.__data.colorBar.interval = interval
- if colorMap != self.__data.colorBar.colorMap:
- self.__data.colorBar.colorMap = colorMap
- if self.isColorBarEnabled():
- self.layoutScale()
-
- def colorMap(self):
- """
- :return: Color map
-
- .. seealso::
-
- :py:meth:`setColorMap()`, :py:meth:`colorBarInterval()`
- """
- return self.__data.colorBar.colorMap
diff --git a/qwt/symbol.py b/qwt/symbol.py
deleted file mode 100644
index 22f7ccb..0000000
--- a/qwt/symbol.py
+++ /dev/null
@@ -1,1272 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the Qwt License
-# Copyright (c) 2002 Uwe Rathmann, for the original C++ code
-# Copyright (c) 2015 Pierre Raybaut, for the Python translation/optimization
-# (see LICENSE file for more details)
-
-"""
-QwtSymbol
----------
-
-.. autoclass:: QwtSymbol
- :members:
-"""
-
-import math
-
-from qtpy.QtCore import (
- QLineF,
- QObject,
- QPoint,
- QPointF,
- QRect,
- QRectF,
- QSize,
- QSizeF,
- Qt,
-)
-from qtpy.QtGui import (
- QBrush,
- QPainter,
- QPen,
- QPixmap,
- QPolygonF,
- QTransform,
-)
-from qtpy.QtSvg import QSvgRenderer
-
-from qwt.graphic import QwtGraphic
-
-
-class QwtTriangle(object):
- # enum Type
- Left, Right, Up, Down = list(range(4))
-
-
-def qwtPathGraphic(path, pen, brush):
- graphic = QwtGraphic()
- graphic.setRenderHint(QwtGraphic.RenderPensUnscaled)
- painter = QPainter(graphic)
- painter.setPen(pen)
- painter.setBrush(brush)
- painter.drawPath(path)
- painter.end()
- return graphic
-
-
-def qwtScaleBoundingRect(graphic, size):
- scaledSize = QSize(size)
- if scaledSize.isEmpty():
- scaledSize = graphic.defaultSize()
- sz = graphic.controlPointRect().size()
- sx = 1.0
- if sz.width() > 0.0:
- sx = scaledSize.width() / sz.width()
- sy = 1.0
- if sz.height() > 0.0:
- sy = scaledSize.height() / sz.height()
- return graphic.scaledBoundingRect(sx, sy)
-
-
-def qwtDrawPixmapSymbols(painter, points, symbol):
- size = symbol.size()
- if size.isEmpty():
- size = symbol.pixmap().size()
- transform = QTransform(painter.transform())
- if transform.isScaling():
- r = QRect(0, 0, size.width(), size.height())
- size = transform.mapRect(r).size()
- pm = QPixmap(symbol.pixmap())
- if pm.size() != size:
- pm = pm.scaled(size)
- pinPoint = QPointF(0.5 * size.width(), 0.5 * size.height())
- if symbol.isPinPointEnabled():
- pinPoint = symbol.pinPoint()
- painter.resetTransform()
- for pos in points:
- pos = QPointF(transform.map(pos)) - pinPoint
- painter.drawPixmap(QRect(pos.toPoint(), pm.size()), pm)
-
-
-def qwtDrawSvgSymbols(painter, points, renderer, symbol):
- if renderer is None or not renderer.isValid():
- return
- viewBox = QRectF(renderer.viewBoxF())
- if viewBox.isEmpty():
- return
- sz = QSizeF(symbol.size())
- if not sz.isValid():
- sz = viewBox.size()
- sx = sz.width() / viewBox.width()
- sy = sz.height() / viewBox.height()
- pinPoint = QPointF(viewBox.center())
- if symbol.isPinPointEnabled():
- pinPoint = symbol.pinPoint()
- dx = sx * (pinPoint.x() - viewBox.left())
- dy = sy * (pinPoint.y() - viewBox.top())
- for pos in points:
- x = pos.x() - dx
- y = pos.y() - dy
- renderer.render(painter, QRectF(x, y, sz.width(), sz.height()))
-
-
-def qwtDrawGraphicSymbols(painter, points, graphic, symbol):
- pointRect = QRectF(graphic.controlPointRect())
- if pointRect.isEmpty():
- return
- sx = 1.0
- sy = 1.0
- sz = symbol.size()
- if sz.isValid():
- sx = sz.width() / pointRect.width()
- sy = sz.height() / pointRect.height()
- pinPoint = QPointF(pointRect.center())
- if symbol.isPinPointEnabled():
- pinPoint = symbol.pinPoint()
- transform = QTransform(painter.transform())
- for pos in points:
- tr = QTransform(transform)
- tr.translate(pos.x(), pos.y())
- tr.scale(sx, sy)
- tr.translate(-pinPoint.x(), -pinPoint.y())
- painter.setTransform(tr)
- graphic.render(painter)
- painter.setTransform(transform)
-
-
-def qwtDrawEllipseSymbols(painter, points, symbol):
- painter.setBrush(symbol.brush())
- painter.setPen(symbol.pen())
- size = symbol.size()
- sw = size.width()
- sh = size.height()
- sw2 = 0.5 * size.width()
- sh2 = 0.5 * size.height()
- for pos in points:
- x = pos.x()
- y = pos.y()
- r = QRectF(x - sw2, y - sh2, sw, sh)
- painter.drawEllipse(r)
-
-
-def qwtDrawRectSymbols(painter, points, symbol):
- size = symbol.size()
- pen = QPen(symbol.pen())
- pen.setJoinStyle(Qt.MiterJoin)
- painter.setPen(pen)
- painter.setBrush(symbol.brush())
- painter.setRenderHint(QPainter.Antialiasing, False)
- sw = size.width()
- sh = size.height()
- sw2 = 0.5 * size.width()
- sh2 = 0.5 * size.height()
- for pos in points:
- x = pos.x()
- y = pos.y()
- r = QRectF(x - sw2, y - sh2, sw, sh)
- painter.drawRect(r)
-
-
-def qwtDrawDiamondSymbols(painter, points, symbol):
- size = symbol.size()
- pen = QPen(symbol.pen())
- pen.setJoinStyle(Qt.MiterJoin)
- painter.setPen(pen)
- painter.setBrush(symbol.brush())
- for pos in points:
- x1 = pos.x() - 0.5 * size.width()
- y1 = pos.y() - 0.5 * size.height()
- x2 = x1 + size.width()
- y2 = y1 + size.height()
- polygon = QPolygonF()
- polygon.append(QPointF(pos.x(), y1))
- polygon.append(QPointF(x1, pos.y()))
- polygon.append(QPointF(pos.x(), y2))
- polygon.append(QPointF(x2, pos.y()))
- painter.drawPolygon(polygon)
-
-
-def qwtDrawTriangleSymbols(painter, type, points, symbol):
- size = symbol.size()
- pen = QPen(symbol.pen())
- pen.setJoinStyle(Qt.MiterJoin)
- painter.setPen(pen)
- painter.setBrush(symbol.brush())
- sw2 = 0.5 * size.width()
- sh2 = 0.5 * size.height()
- for pos in points:
- x = pos.x()
- y = pos.y()
- x1 = x - sw2
- x2 = x1 + size.width()
- y1 = y - sh2
- y2 = y1 + size.height()
- if type == QwtTriangle.Left:
- triangle = [QPointF(x2, y1), QPointF(x1, y), QPointF(x2, y2)]
- elif type == QwtTriangle.Right:
- triangle = [QPointF(x1, y1), QPointF(x2, y), QPointF(x1, y2)]
- elif type == QwtTriangle.Up:
- triangle = [QPointF(x1, y2), QPointF(x, y1), QPointF(x2, y2)]
- elif type == QwtTriangle.Down:
- triangle = [QPointF(x1, y1), QPointF(x, y2), QPointF(x2, y1)]
- else:
- raise TypeError("Unknown triangle type %s" % type)
- painter.drawPolygon(QPolygonF(triangle))
-
-
-def qwtDrawLineSymbols(painter, orientations, points, symbol):
- size = symbol.size()
- pen = QPen(symbol.pen())
- if pen.width() > 1:
- pen.setCapStyle(Qt.FlatCap)
- painter.setPen(pen)
- painter.setRenderHint(QPainter.Antialiasing, False)
- sw = size.width()
- sh = size.height()
- sw2 = 0.5 * size.width()
- sh2 = 0.5 * size.height()
- for pos in points:
- if orientations & Qt.Horizontal:
- x = round(pos.x()) - sw2
- y = round(pos.y())
- painter.drawLine(QLineF(x, y, x + sw, y))
- if orientations & Qt.Vertical:
- x = round(pos.x())
- y = round(pos.y()) - sh2
- painter.drawLine(QLineF(x, y, x, y + sh))
-
-
-def qwtDrawXCrossSymbols(painter, points, symbol):
- size = symbol.size()
- pen = QPen(symbol.pen())
- if pen.width() > 1:
- pen.setCapStyle(Qt.FlatCap)
- painter.setPen(pen)
- sw = size.width()
- sh = size.height()
- sw2 = 0.5 * size.width()
- sh2 = 0.5 * size.height()
- for pos in points:
- x1 = pos.x() - sw2
- x2 = x1 + sw
- y1 = pos.y() - sh2
- y2 = y1 + sh
- painter.drawLine(QLineF(x1, y1, x2, y2))
- painter.drawLine(QLineF(x2, y1, x1, y2))
-
-
-def qwtDrawStar1Symbols(painter, points, symbol):
- size = symbol.size()
- painter.setPen(symbol.pen())
- sqrt1_2 = math.sqrt(0.5)
- r = QRectF(0, 0, size.width(), size.height())
- for pos in points:
- r.moveCenter(pos)
- c = QPointF(r.center())
- d1 = r.width() / 2.0 * (1.0 - sqrt1_2)
- painter.drawLine(
- QLineF(r.left() + d1, r.top() + d1, r.right() - d1, r.bottom() - d1)
- )
- painter.drawLine(
- QLineF(r.left() + d1, r.bottom() - d1, r.right() - d1, r.top() + d1)
- )
- painter.drawLine(QLineF(c.x(), r.top(), c.x(), r.bottom()))
- painter.drawLine(QLineF(r.left(), c.y(), r.right(), c.y()))
-
-
-def qwtDrawStar2Symbols(painter, points, symbol):
- pen = QPen(symbol.pen())
- if pen.width() > 1:
- pen.setCapStyle(Qt.FlatCap)
- pen.setJoinStyle(Qt.MiterJoin)
- painter.setPen(pen)
- painter.setBrush(symbol.brush())
- cos30 = math.cos(30 * math.pi / 180.0)
- dy = 0.25 * symbol.size().height()
- dx = 0.5 * symbol.size().width() * cos30 / 3.0
- for pos in points:
- x = pos.x()
- y = pos.y()
- x1 = x - 3 * dx
- y1 = y - 2 * dy
- x2 = x1 + 1 * dx
- x3 = x1 + 2 * dx
- x4 = x1 + 3 * dx
- x5 = x1 + 4 * dx
- x6 = x1 + 5 * dx
- x7 = x1 + 6 * dx
- y2 = y1 + 1 * dy
- y3 = y1 + 2 * dy
- y4 = y1 + 3 * dy
- y5 = y1 + 4 * dy
- star = [
- QPointF(x4, y1),
- QPointF(x5, y2),
- QPointF(x7, y2),
- QPointF(x6, y3),
- QPointF(x7, y4),
- QPointF(x5, y4),
- QPointF(x4, y5),
- QPointF(x3, y4),
- QPointF(x1, y4),
- QPointF(x2, y3),
- QPointF(x1, y2),
- QPointF(x3, y2),
- ]
- painter.drawPolygon(QPolygonF(star))
-
-
-def qwtDrawHexagonSymbols(painter, points, symbol):
- painter.setBrush(symbol.brush())
- painter.setPen(symbol.pen())
- cos30 = math.cos(30 * math.pi / 180.0)
- dx = 0.5 * (symbol.size().width() - cos30)
- dy = 0.25 * symbol.size().height()
- for pos in points:
- x = pos.x()
- y = pos.y()
- x1 = x - dx
- y1 = y - 2 * dy
- x2 = x1 + 1 * dx
- x3 = x1 + 2 * dx
- y2 = y1 + 1 * dy
- y3 = y1 + 3 * dy
- y4 = y1 + 4 * dy
- hexa = [
- QPointF(x2, y1),
- QPointF(x3, y2),
- QPointF(x3, y3),
- QPointF(x2, y4),
- QPointF(x1, y3),
- QPointF(x1, y2),
- ]
- painter.drawPolygon(QPolygonF(hexa))
-
-
-class QwtSymbol_PrivateData(QObject):
- def __init__(self, st, br, pn, sz):
- QObject.__init__(self)
- self.style = st
- self.size = sz
- self.brush = br
- self.pen = pn
- self.isPinPointEnabled = False
- self.pinPoint = None
-
- class Path(object):
- def __init__(self):
- self.path = None # QPainterPath()
- self.graphic = QwtGraphic()
-
- self.path = Path()
-
- self.pixmap = None
-
- class Graphic(object):
- def __init__(self):
- self.graphic = QwtGraphic()
-
- self.graphic = Graphic()
-
- class SVG(object):
- def __init__(self):
- self.renderer = QSvgRenderer()
-
- self.svg = SVG()
-
- class PaintCache(object):
- def __init__(self):
- self.policy = 0
- self.pixmap = None # QPixmap()
-
- self.cache = PaintCache()
-
-
-class QwtSymbol(object):
- """
- A class for drawing symbols
-
- Symbol styles:
-
- * `QwtSymbol.NoSymbol`: No Style. The symbol cannot be drawn.
- * `QwtSymbol.Ellipse`: Ellipse or circle
- * `QwtSymbol.Rect`: Rectangle
- * `QwtSymbol.Diamond`: Diamond
- * `QwtSymbol.Triangle`: Triangle pointing upwards
- * `QwtSymbol.DTriangle`: Triangle pointing downwards
- * `QwtSymbol.UTriangle`: Triangle pointing upwards
- * `QwtSymbol.LTriangle`: Triangle pointing left
- * `QwtSymbol.RTriangle`: Triangle pointing right
- * `QwtSymbol.Cross`: Cross (+)
- * `QwtSymbol.XCross`: Diagonal cross (X)
- * `QwtSymbol.HLine`: Horizontal line
- * `QwtSymbol.VLine`: Vertical line
- * `QwtSymbol.Star1`: X combined with +
- * `QwtSymbol.Star2`: Six-pointed star
- * `QwtSymbol.Hexagon`: Hexagon
- * `QwtSymbol.Path`: The symbol is represented by a painter path, where
- the origin (0, 0) of the path coordinate system is mapped to the
- position of the symbol
-
- ..seealso::
-
- :py:meth:`setPath()`, :py:meth:`path()`
- * `QwtSymbol.Pixmap`: The symbol is represented by a pixmap.
- The pixmap is centered or aligned to its pin point.
-
- ..seealso::
-
- :py:meth:`setPinPoint()`
- * `QwtSymbol.Graphic`: The symbol is represented by a graphic.
- The graphic is centered or aligned to its pin point.
-
- ..seealso::
-
- :py:meth:`setPinPoint()`
- * `QwtSymbol.SvgDocument`: The symbol is represented by a SVG graphic.
- The graphic is centered or aligned to its pin point.
-
- ..seealso::
-
- :py:meth:`setPinPoint()`
- * `QwtSymbol.UserStyle`: Styles >= `QwtSymbol.UserStyle` are reserved
- for derived classes of `QwtSymbol` that overload `drawSymbols()` with
- additional application specific symbol types.
-
- Cache policies:
-
- Depending on the render engine and the complexity of the
- symbol shape it might be faster to render the symbol
- to a pixmap and to paint this pixmap.
-
- F.e. the raster paint engine is a pure software renderer
- where in cache mode a draw operation usually ends in
- raster operation with the the backing store, that are usually
- faster, than the algorithms for rendering polygons.
- But the opposite can be expected for graphic pipelines
- that can make use of hardware acceleration.
-
- The default setting is AutoCache
-
- ..seealso::
-
- :py:meth:`setCachePolicy()`, :py:meth:`cachePolicy()`
-
- .. note::
-
- The policy has no effect, when the symbol is painted
- to a vector graphics format (PDF, SVG).
-
- .. warning::
-
- Since Qt 4.8 raster is the default backend on X11
-
- Valid cache policies:
-
- * `QwtSymbol.NoCache`: Don't use a pixmap cache
- * `QwtSymbol.Cache`: Always use a pixmap cache
- * `QwtSymbol.AutoCache`: Use a cache when the symbol is rendered
- with the software renderer (`QPaintEngine.Raster`)
-
- .. py:class:: QwtSymbol([style=QwtSymbol.NoSymbol])
-
- The symbol is constructed with gray interior,
- black outline with zero width, no size and style 'NoSymbol'.
-
- :param int style: Symbol Style
-
- .. py:class:: QwtSymbol(style, brush, pen, size)
- :noindex:
-
- :param int style: Symbol Style
- :param QBrush brush: Brush to fill the interior
- :param QPen pen: Outline pen
- :param QSize size: Size
-
- .. py:class:: QwtSymbol(path, brush, pen)
- :noindex:
-
- :param QPainterPath path: Painter path
- :param QBrush brush: Brush to fill the interior
- :param QPen pen: Outline pen
-
- .. seealso::
-
- :py:meth:`setPath()`, :py:meth:`setBrush()`,
- :py:meth:`setPen()`, :py:meth:`setSize()`
- """
-
- # enum Style
- Style = int
- NoSymbol = -1
- (
- Ellipse,
- Rect,
- Diamond,
- Triangle,
- DTriangle,
- UTriangle,
- LTriangle,
- RTriangle,
- Cross,
- XCross,
- HLine,
- VLine,
- Star1,
- Star2,
- Hexagon,
- Path,
- Pixmap,
- Graphic,
- SvgDocument,
- ) = list(range(19))
- UserStyle = 1000
-
- # enum CachePolicy
- NoCache, Cache, AutoCache = list(range(3))
-
- def __init__(self, *args):
- if len(args) in (0, 1):
- if args:
- (style,) = args
- else:
- style = QwtSymbol.NoSymbol
- self.__data = QwtSymbol_PrivateData(
- style, QBrush(Qt.gray), QPen(Qt.black, 0), QSize()
- )
- elif len(args) == 4:
- style, brush, pen, size = args
- self.__data = QwtSymbol_PrivateData(style, brush, pen, size)
- elif len(args) == 3:
- path, brush, pen = args
- self.__data = QwtSymbol_PrivateData(QwtSymbol.Path, brush, pen, QSize())
- self.setPath(path)
- else:
- raise TypeError(
- "%s() takes 1, 3, or 4 argument(s) (%s given)"
- % (self.__class__.__name__, len(args))
- )
-
- @classmethod
- def make(
- cls,
- style=None,
- brush=None,
- pen=None,
- size=None,
- path=None,
- pixmap=None,
- graphic=None,
- svgdocument=None,
- pinpoint=None,
- ):
- """
- Create and setup a new `QwtSymbol` object (convenience function).
-
- :param style: Symbol Style
- :type style: int or None
- :param brush: Brush to fill the interior
- :type brush: QBrush or None
- :param pen: Outline pen
- :type pen: QPen or None
- :param size: Size
- :type size: QSize or None
- :param path: Painter path
- :type path: QPainterPath or None
- :param path: Painter path
- :type path: QPainterPath or None
- :param pixmap: Pixmap as symbol
- :type pixmap: QPixmap or None
- :param graphic: Graphic
- :type graphic: qwt.graphic.QwtGraphic or None
- :param svgdocument: SVG icon as symbol
-
- .. seealso::
-
- :py:meth:`setPixmap()`, :py:meth:`setGraphic()`, :py:meth:`setPath()`
- """
- style = QwtSymbol.NoSymbol if style is None else style
- brush = QBrush(Qt.gray) if brush is None else QBrush(brush)
- pen = QPen(Qt.black, 0) if pen is None else QPen(pen)
- size = QSize() if size is None else size
- if not isinstance(size, QSize):
- if isinstance(size, tuple) and len(size) == 2:
- size = QSize(size[0], size[1])
- else:
- raise TypeError("Invalid size %r" % size)
- item = cls(style, brush, pen, size)
- if path is not None:
- item.setPath(path)
- elif pixmap is not None:
- item.setPixmap(pixmap)
- elif graphic is not None:
- item.setGraphic(graphic)
- elif svgdocument is not None:
- item.setSvgDocument(svgdocument)
- if pinpoint is not None:
- item.setPinPoint(pinpoint)
- return item
-
- def setCachePolicy(self, policy):
- """
- Change the cache policy
-
- The default policy is AutoCache
-
- :param int policy: Cache policy
-
- .. seealso::
-
- :py:meth:`cachePolicy()`
- """
- if self.__data.cache.policy != policy:
- self.__data.cache.policy = policy
- self.invalidateCache()
-
- def cachePolicy(self):
- """
- :return: Cache policy
-
- .. seealso::
-
- :py:meth:`setCachePolicy()`
- """
- return self.__data.cache.policy
-
- def setPath(self, path):
- """
- Set a painter path as symbol
-
- The symbol is represented by a painter path, where the
- origin (0, 0) of the path coordinate system is mapped to
- the position of the symbol.
-
- When the symbol has valid size the painter path gets scaled
- to fit into the size. Otherwise the symbol size depends on
- the bounding rectangle of the path.
-
- The following code defines a symbol drawing an arrow::
-
- from qtpy.QtGui import QApplication, QPen, QPainterPath, QTransform
- from qtpy.QtCore import Qt, QPointF
- from qwt import QwtPlot, QwtPlotCurve, QwtSymbol
- import numpy as np
-
- app = QApplication([])
-
- # --- Construct custom symbol ---
-
- path = QPainterPath()
- path.moveTo(0, 8)
- path.lineTo(0, 5)
- path.lineTo(-3, 5)
- path.lineTo(0, 0)
- path.lineTo(3, 5)
- path.lineTo(0, 5)
-
- transform = QTransform()
- transform.rotate(-30.0)
- path = transform.map(path)
-
- pen = QPen(Qt.black, 2 );
- pen.setJoinStyle(Qt.MiterJoin)
-
- symbol = QwtSymbol()
- symbol.setPen(pen)
- symbol.setBrush(Qt.red)
- symbol.setPath(path)
- symbol.setPinPoint(QPointF(0., 0.))
- symbol.setSize(10, 14)
-
- # --- Test it within a simple plot ---
-
- curve = QwtPlotCurve()
- curve_pen = QPen(Qt.blue)
- curve_pen.setStyle(Qt.DotLine)
- curve.setPen(curve_pen)
- curve.setSymbol(symbol)
- x = np.linspace(0, 10, 10)
- curve.setData(x, np.sin(x))
-
- plot = QwtPlot()
- curve.attach(plot)
- plot.resize(600, 300)
- plot.replot()
- plot.show()
-
- app.exec_()
-
- .. image:: /_static/symbol_path_example.png
-
- :param QPainterPath path: Painter path
-
- .. seealso::
-
- :py:meth:`path()`, :py:meth:`setSize()`
- """
- self.__data.style = QwtSymbol.Path
- self.__data.path.path = path
- self.__data.path.graphic.reset()
-
- def path(self):
- """
- :return: Painter path for displaying the symbol
-
- .. seealso::
-
- :py:meth:`setPath()`
- """
- return self.__data.path.path
-
- def setPixmap(self, pixmap):
- """
- Set a pixmap as symbol
-
- :param QPixmap pixmap: Pixmap
-
- .. seealso::
-
- :py:meth:`pixmap()`, :py:meth:`setGraphic()`
-
- .. note::
-
- The `style()` is set to `QwtSymbol.Pixmap`
-
- .. note::
-
- `brush()` and `pen()` have no effect
- """
- self.__data.style = QwtSymbol.Pixmap
- self.__data.pixmap = pixmap
-
- def pixmap(self):
- """
- :return: Assigned pixmap
-
- .. seealso::
-
- :py:meth:`setPixmap()`
- """
- if self.__data.pixmap is None:
- return QPixmap()
- return self.__data.pixmap
-
- def setGraphic(self, graphic):
- """
- Set a graphic as symbol
-
- :param qwt.graphic.QwtGraphic graphic: Graphic
-
- .. seealso::
-
- :py:meth:`graphic()`, :py:meth:`setPixmap()`
-
- .. note::
-
- The `style()` is set to `QwtSymbol.Graphic`
-
- .. note::
-
- `brush()` and `pen()` have no effect
- """
- self.__data.style = QwtSymbol.Graphic
- self.__data.graphic.graphic = graphic
-
- def graphic(self):
- """
- :return: Assigned graphic
-
- .. seealso::
-
- :py:meth:`setGraphic()`
- """
- return self.__data.graphic.graphic
-
- def setSvgDocument(self, svgDocument):
- """
- Set a SVG icon as symbol
-
- :param svgDocument: SVG icon
-
- .. seealso::
-
- :py:meth:`setGraphic()`, :py:meth:`setPixmap()`
-
- .. note::
-
- The `style()` is set to `QwtSymbol.SvgDocument`
-
- .. note::
-
- `brush()` and `pen()` have no effect
- """
- self.__data.style = QwtSymbol.SvgDocument
- if self.__data.svg.renderer is None:
- self.__data.svg.renderer = QSvgRenderer()
- self.__data.svg.renderer.load(svgDocument)
-
- def setSize(self, *args):
- """
- Specify the symbol's size
-
- .. py:method:: setSize(width, [height=-1])
- :noindex:
-
- :param int width: Width
- :param int height: Height
-
- .. py:method:: setSize(size)
- :noindex:
-
- :param QSize size: Size
-
- .. seealso::
-
- :py:meth:`size()`
- """
- if len(args) == 2:
- width, height = args
- if width >= 0 and height < 0:
- height = width
- self.setSize(QSize(width, height))
- elif len(args) == 1:
- if isinstance(args[0], QSize):
- (size,) = args
- if size.isValid() and size != self.__data.size:
- self.__data.size = size
- self.invalidateCache()
- else:
- (width,) = args
- self.setSize(width, -1)
- else:
- raise TypeError(
- "%s().setSize() takes 1 or 2 argument(s) (%s given)"
- % (self.__class__.__name__, len(args))
- )
-
- def size(self):
- """
- :return: Size
-
- .. seealso::
-
- :py:meth:`setSize()`
- """
- return self.__data.size
-
- def setBrush(self, brush):
- """
- Assign a brush
-
- The brush is used to draw the interior of the symbol.
-
- :param QBrush brush: Brush
-
- .. seealso::
-
- :py:meth:`brush()`
- """
- if brush != self.__data.brush:
- self.__data.brush = brush
- self.invalidateCache()
- if self.__data.style == QwtSymbol.Path:
- self.__data.path.graphic.reset()
-
- def brush(self):
- """
- :return: Brush
-
- .. seealso::
-
- :py:meth:`setBrush()`
- """
- return self.__data.brush
-
- def setPen(self, *args):
- """
- Build and/or assign a pen, depending on the arguments.
-
- .. py:method:: setPen(color, width, style)
- :noindex:
-
- Build and assign a pen
-
- In Qt5 the default pen width is 1.0 ( 0.0 in Qt4 ) what makes it
- non cosmetic (see `QPen.isCosmetic()`). This method signature has
- been introduced to hide this incompatibility.
-
- :param QColor color: Pen color
- :param float width: Pen width
- :param Qt.PenStyle style: Pen style
-
- .. py:method:: setPen(pen)
- :noindex:
-
- Assign a pen
-
- :param QPen pen: New pen
-
- .. seealso::
-
- :py:meth:`pen()`, :py:meth:`brush()`
- """
- if len(args) == 3:
- color, width, style = args
- self.setPen(QPen(color, width, style))
- elif len(args) == 1:
- (pen,) = args
- if pen != self.__data.pen:
- self.__data.pen = pen
- self.invalidateCache()
- if self.__data.style == QwtSymbol.Path:
- self.__data.path.graphic.reset()
- else:
- raise TypeError(
- "%s().setPen() takes 1 or 3 argument(s) (%s given)"
- % (self.__class__.__name__, len(args))
- )
-
- def pen(self):
- """
- :return: Pen
-
- .. seealso::
-
- :py:meth:`setPen()`, :py:meth:`brush()`
- """
- return self.__data.pen
-
- def setColor(self, color):
- """
- Set the color of the symbol
-
- Change the color of the brush for symbol types with a filled area.
- For all other symbol types the color will be assigned to the pen.
-
- :param QColor color: Color
-
- .. seealso::
-
- :py:meth:`setPen()`, :py:meth:`setBrush()`,
- :py:meth:`brush()`, :py:meth:`pen()`
- """
- if self.__data.style in (
- QwtSymbol.Ellipse,
- QwtSymbol.Rect,
- QwtSymbol.Diamond,
- QwtSymbol.Triangle,
- QwtSymbol.UTriangle,
- QwtSymbol.DTriangle,
- QwtSymbol.RTriangle,
- QwtSymbol.LTriangle,
- QwtSymbol.Star2,
- QwtSymbol.Hexagon,
- ):
- if self.__data.brush.color() != color:
- self.__data.brush.setColor(color)
- self.invalidateCache()
- elif self.__data.style in (
- QwtSymbol.Cross,
- QwtSymbol.XCross,
- QwtSymbol.HLine,
- QwtSymbol.VLine,
- QwtSymbol.Star1,
- ):
- if self.__data.pen.color() != color:
- self.__data.pen.setColor(color)
- self.invalidateCache()
- else:
- if self.__data.brush.color() != color or self.__data.pen.color() != color:
- self.invalidateCache()
- self.__data.brush.setColor(color)
- self.__data.pen.setColor(color)
-
- def setPinPoint(self, pos, enable=True):
- """
- Set and enable a pin point
-
- The position of a complex symbol is not always aligned to its center
- ( f.e an arrow, where the peak points to a position ). The pin point
- defines the position inside of a Pixmap, Graphic, SvgDocument
- or PainterPath symbol where the represented point has to
- be aligned to.
-
- :param QPointF pos: Position
- :enable bool enable: En/Disable the pin point alignment
-
- .. seealso::
-
- :py:meth:`pinPoint()`, :py:meth:`setPinPointEnabled()`
- """
- if self.__data.pinPoint != pos:
- self.__data.pinPoint = pos
- if self.__data.isPinPointEnabled:
- self.invalidateCache()
- self.setPinPointEnabled(enable)
-
- def pinPoint(self):
- """
- :return: Pin point
-
- .. seealso::
-
- :py:meth:`setPinPoint()`, :py:meth:`setPinPointEnabled()`
- """
- return self.__data.pinPoint
-
- def setPinPointEnabled(self, on):
- """
- En/Disable the pin point alignment
-
- :param bool on: Enabled, when on is true
-
- .. seealso::
-
- :py:meth:`setPinPoint()`, :py:meth:`isPinPointEnabled()`
- """
- if self.__data.isPinPointEnabled != on:
- self.__data.isPinPointEnabled = on
- self.invalidateCache()
-
- def isPinPointEnabled(self):
- """
- :return: True, when the pin point translation is enabled
-
- .. seealso::
-
- :py:meth:`setPinPoint()`, :py:meth:`setPinPointEnabled()`
- """
- return self.__data.isPinPointEnabled
-
- def drawSymbols(self, painter, points):
- """
- Render an array of symbols
-
- Painting several symbols is more effective than drawing symbols
- one by one, as a couple of layout calculations and setting of pen/brush
- can be done once for the complete array.
-
- :param QPainter painter: Painter
- :param QPolygonF points: Positions of the symbols in screen coordinates
- """
- painter.save()
- self.renderSymbols(painter, points)
- painter.restore()
-
- def drawSymbol(self, painter, point_or_rect):
- """
- Draw the symbol into a rectangle
-
- The symbol is painted centered and scaled into the target rectangle.
- It is always painted uncached and the pin point is ignored.
-
- This method is primarily intended for drawing a symbol to the legend.
-
- :param QPainter painter: Painter
- :param point_or_rect: Position or target rectangle of the symbol in screen coordinates
- :type point_or_rect: QPointF or QPoint or QRectF
- """
- if isinstance(point_or_rect, (QPointF, QPoint)):
- # drawSymbol( QPainter *, const QPointF & )
- self.drawSymbols(painter, [point_or_rect])
- return
- # drawSymbol( QPainter *, const QRectF & )
- rect = point_or_rect
- assert isinstance(rect, QRectF)
- if self.__data.style == QwtSymbol.NoSymbol:
- return
- if self.__data.style == QwtSymbol.Graphic:
- self.__data.graphic.graphic.render(painter, rect, Qt.KeepAspectRatio)
- elif self.__data.style == QwtSymbol.Path:
- if self.__data.path.graphic.isNull():
- self.__data.path.graphic = qwtPathGraphic(
- self.__data.path.path, self.__data.pen, self.__data.brush
- )
- self.__data.path.graphic.render(painter, rect, Qt.KeepAspectRatio)
- return
- elif self.__data.style == QwtSymbol.SvgDocument:
- if self.__data.svg.renderer is not None:
- scaledRect = QRectF()
- sz = QSizeF(self.__data.svg.renderer.viewBoxF().size())
- if not sz.isEmpty():
- sz.scale(rect.size(), Qt.KeepAspectRatio)
- scaledRect.setSize(sz)
- scaledRect.moveCenter(rect.center())
- else:
- scaledRect = rect
- self.__data.svg.renderer.render(painter, scaledRect)
- else:
- br = QRect(self.boundingRect())
- ratio = min([rect.width() / br.width(), rect.height() / br.height()])
- painter.save()
- painter.translate(rect.center())
- painter.scale(ratio, ratio)
- isPinPointEnabled = self.__data.isPinPointEnabled
- self.__data.isPinPointEnabled = False
- self.renderSymbols(painter, [QPointF()])
- self.__data.isPinPointEnabled = isPinPointEnabled
- painter.restore()
-
- def renderSymbols(self, painter, points):
- """
- Render the symbol to series of points
-
- :param QPainter painter: Painter
- :param point_or_rect: Positions of the symbols
- """
- if self.__data.style == QwtSymbol.Ellipse:
- qwtDrawEllipseSymbols(painter, points, self)
- elif self.__data.style == QwtSymbol.Rect:
- qwtDrawRectSymbols(painter, points, self)
- elif self.__data.style == QwtSymbol.Diamond:
- qwtDrawDiamondSymbols(painter, points, self)
- elif self.__data.style == QwtSymbol.Cross:
- qwtDrawLineSymbols(painter, Qt.Horizontal | Qt.Vertical, points, self)
- elif self.__data.style == QwtSymbol.XCross:
- qwtDrawXCrossSymbols(painter, points, self)
- elif self.__data.style in (QwtSymbol.Triangle, QwtSymbol.UTriangle):
- qwtDrawTriangleSymbols(painter, QwtTriangle.Up, points, self)
- elif self.__data.style == QwtSymbol.DTriangle:
- qwtDrawTriangleSymbols(painter, QwtTriangle.Down, points, self)
- elif self.__data.style == QwtSymbol.RTriangle:
- qwtDrawTriangleSymbols(painter, QwtTriangle.Right, points, self)
- elif self.__data.style == QwtSymbol.LTriangle:
- qwtDrawTriangleSymbols(painter, QwtTriangle.Left, points, self)
- elif self.__data.style == QwtSymbol.HLine:
- qwtDrawLineSymbols(painter, Qt.Horizontal, points, self)
- elif self.__data.style == QwtSymbol.VLine:
- qwtDrawLineSymbols(painter, Qt.Vertical, points, self)
- elif self.__data.style == QwtSymbol.Star1:
- qwtDrawStar1Symbols(painter, points, self)
- elif self.__data.style == QwtSymbol.Star2:
- qwtDrawStar2Symbols(painter, points, self)
- elif self.__data.style == QwtSymbol.Hexagon:
- qwtDrawHexagonSymbols(painter, points, self)
- elif self.__data.style == QwtSymbol.Path:
- if self.__data.path.graphic.isNull():
- self.__data.path.graphic = qwtPathGraphic(
- self.__data.path.path, self.__data.pen, self.__data.brush
- )
- qwtDrawGraphicSymbols(painter, points, self.__data.path.graphic, self)
- elif self.__data.style == QwtSymbol.Pixmap:
- qwtDrawPixmapSymbols(painter, points, self)
- elif self.__data.style == QwtSymbol.Graphic:
- qwtDrawGraphicSymbols(painter, points, self.__data.graphic.graphic, self)
- elif self.__data.style == QwtSymbol.SvgDocument:
- qwtDrawSvgSymbols(painter, points, self.__data.svg.renderer, self)
-
- def boundingRect(self):
- """
- Calculate the bounding rectangle for a symbol at position (0,0).
-
- :return: Bounding rectangle
- """
- rect = QRectF()
- pinPointTranslation = False
- if self.__data.style in (QwtSymbol.Ellipse, QwtSymbol.Rect, QwtSymbol.Hexagon):
- pw = 0.0
- if self.__data.pen.style() != Qt.NoPen:
- pw = max([self.__data.pen.widthF(), 1.0])
- rect.setSize(QSizeF(self.__data.size) + QSizeF(pw, pw))
- rect.moveCenter(QPointF(0.0, 0.0))
- elif self.__data.style in (
- QwtSymbol.XCross,
- QwtSymbol.Diamond,
- QwtSymbol.Triangle,
- QwtSymbol.UTriangle,
- QwtSymbol.DTriangle,
- QwtSymbol.RTriangle,
- QwtSymbol.LTriangle,
- QwtSymbol.Star1,
- QwtSymbol.Star2,
- ):
- pw = 0.0
- if self.__data.pen.style() != Qt.NoPen:
- pw = max([self.__data.pen.widthF(), 1.0])
- rect.setSize(QSizeF(self.__data.size) + QSizeF(2 * pw, 2 * pw))
- rect.moveCenter(QPointF(0.0, 0.0))
- elif self.__data.style == QwtSymbol.Path:
- if self.__data.path.graphic.isNull():
- self.__data.path.graphic = qwtPathGraphic(
- self.__data.path.path, self.__data.pen, self.__data.brush
- )
- rect = qwtScaleBoundingRect(self.__data.path.graphic, self.__data.size)
- pinPointTranslation = True
- elif self.__data.style == QwtSymbol.Pixmap:
- if self.__data.size.isEmpty():
- rect.setSize(QSizeF(self.pixmap().size()))
- else:
- rect.setSize(QSizeF(self.__data.size))
- pinPointTranslation = True
- elif self.__data.style == QwtSymbol.Graphic:
- rect = qwtScaleBoundingRect(self.__data.graphic.graphic, self.__data.size)
- pinPointTranslation = True
- elif self.__data.style == QwtSymbol.SvgDocument:
- if self.__data.svg.renderer is not None:
- rect = self.__data.svg.renderer.viewBoxF()
- if self.__data.size.isValid() and not rect.isEmpty():
- sz = QSizeF(rect.size())
- sx = self.__data.size.width() / sz.width()
- sy = self.__data.size.height() / sz.height()
- transform = QTransform()
- transform.scale(sx, sy)
- rect = transform.mapRect(rect)
- pinPointTranslation = True
- else:
- rect.setSize(QSizeF(self.__data.size))
- rect.moveCenter(QPointF(0.0, 0.0))
- if pinPointTranslation:
- pinPoint = QPointF(0.0, 0.0)
- if self.__data.isPinPointEnabled:
- pinPoint = rect.center() - self.__data.pinPoint
- rect.moveCenter(pinPoint)
- r = QRect()
- r.setLeft(math.floor(rect.left()))
- r.setTop(math.floor(rect.top()))
- r.setRight(math.floor(rect.right()))
- r.setBottom(math.floor(rect.bottom()))
- if self.__data.style != QwtSymbol.Pixmap:
- r.adjust(-1, -1, 1, 1)
- return r
-
- def invalidateCache(self):
- """
- Invalidate the cached symbol pixmap
-
- The symbol invalidates its cache, whenever an attribute is changed
- that has an effect ob how to display a symbol. In case of derived
- classes with individual styles (>= `QwtSymbol.UserStyle`) it
- might be necessary to call invalidateCache() for attributes
- that are relevant for this style.
-
- .. seealso::
-
- :py:meth:`setCachePolicy()`, :py:meth:`drawSymbols()`
- """
- if self.__data.cache.pixmap is not None:
- self.__data.cache.pixmap = None
-
- def setStyle(self, style):
- """
- Specify the symbol style
-
- :param int style: Style
-
- .. seealso::
-
- :py:meth:`style()`
- """
- if self.__data.style != style:
- self.__data.style = style
- self.invalidateCache()
-
- def style(self):
- """
- :return: Current symbol style
-
- .. seealso::
-
- :py:meth:`setStyle()`
- """
- return self.__data.style
diff --git a/qwt/tests/__init__.py b/qwt/tests/__init__.py
deleted file mode 100644
index 6d6348b..0000000
--- a/qwt/tests/__init__.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the MIT License
-# Copyright (c) 2015 Pierre Raybaut
-# (see LICENSE file for more details)
-
-"""
-PythonQwt test package
-======================
-"""
-
-from qtpy import QtCore as QC
-from qtpy import QtWidgets as QW
-
-from qwt.tests.utils import (
- QT_API,
- TestEnvironment,
- TestLauncher,
- run_all_tests,
- take_screenshot,
-)
-
-
-def run(wait=True):
- """Run PythonQwt tests or test launcher"""
- app = QW.QApplication([])
- launcher = TestLauncher()
- launcher.show()
- test_env = TestEnvironment()
- if test_env.screenshots:
- print("Running PythonQwt tests and taking screenshots automatically:")
- QC.QTimer.singleShot(100, lambda: take_screenshot(launcher))
- elif test_env.unattended:
- print("Running PythonQwt tests in unattended mode:")
- QC.QTimer.singleShot(0, QW.QApplication.instance().quit)
- if QT_API == "pyside6":
- app.exec()
- else:
- app.exec_()
- launcher.close()
- if test_env.unattended:
- run_all_tests(wait=wait)
-
-
-if __name__ == "__main__":
- run()
diff --git a/qwt/tests/comparative_benchmarks.py b/qwt/tests/comparative_benchmarks.py
deleted file mode 100644
index 22c0bca..0000000
--- a/qwt/tests/comparative_benchmarks.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the MIT License
-# Copyright (c) 2015 Pierre Raybaut
-# (see LICENSE file for more details)
-
-"""
-PyQwt5 vs. PythonQwt
-====================
-"""
-
-import os
-import os.path as osp
-import subprocess
-import sys
-import time
-
-
-def get_winpython_exe(rootpath, pymajor=None, pyminor=None):
- """Return WinPython exe list from rootpath"""
- exelist = []
- for name1 in os.listdir(rootpath):
- winroot = osp.join(rootpath, name1)
- if osp.isdir(winroot):
- for name2 in os.listdir(winroot):
- pypath = osp.join(winroot, name2, "python.exe")
- if osp.isfile(pypath):
- pymaj, pymin = name2[len("python-") :].split(".")[:2]
- if pymajor is None or pymajor == int(pymaj):
- if pyminor is None or int(pymin) >= pyminor:
- exelist.append(pypath)
- return exelist
-
-
-def run_script(filename, args=None, wait=True, executable=None):
- """Run Python script"""
- os.environ["PYTHONPATH"] = os.pathsep.join(sys.path)
- if executable is None:
- executable = sys.executable
- command = [executable, '"' + filename + '"']
- if args is not None:
- command.append(args)
- print(" ".join(command))
- proc = subprocess.Popen(" ".join(command), shell=True)
- if wait:
- proc.wait()
-
-
-def main():
- for name in (
- "curvebenchmark1.py",
- "curvebenchmark2.py",
- ):
- for executable in get_winpython_exe(r"C:\Apps", pymajor=3, pyminor=6):
- filename = osp.join(osp.dirname(osp.abspath(__file__)), name)
- run_script(filename, wait=False, executable=executable)
- time.sleep(4)
-
-
-if __name__ == "__main__":
- # print(get_winpython_exe(r"C:\Apps", pymajor=3))
- main()
diff --git a/qwt/tests/conftest.py b/qwt/tests/conftest.py
deleted file mode 100644
index e60b9cd..0000000
--- a/qwt/tests/conftest.py
+++ /dev/null
@@ -1,63 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""pytest configuration for PythonQwt package tests."""
-
-import os
-
-import qtpy
-
-import qwt
-from qwt.tests.utils import TestEnvironment
-
-# Set the unattended environment variable to 1 to avoid any user interaction
-os.environ[TestEnvironment.UNATTENDED_ENV] = "1"
-
-
-def pytest_addoption(parser):
- """Add custom command line options to pytest."""
- # See this StackOverflow answer for more information: https://t.ly/9anqz
- parser.addoption(
- "--repeat", action="store", help="Number of times to repeat each test"
- )
- parser.addoption(
- "--show-windows",
- action="store_true",
- default=False,
- help="Display Qt windows during tests (disables QT_QPA_PLATFORM=offscreen)",
- )
-
-
-def pytest_configure(config):
- """Configure pytest based on command line options."""
- if config.option.durations is None:
- config.option.durations = 10 # Default to showing 10 slowest tests
- if not config.getoption("--show-windows"):
- os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
-
-
-def pytest_report_header(config):
- """Add additional information to the pytest report header."""
- qtbindings_version = qtpy.PYSIDE_VERSION
- if qtbindings_version is None:
- qtbindings_version = qtpy.PYQT_VERSION
- return [
- f"PythonQwt {qwt.__version__} [closest Qwt version: {qwt.QWT_VERSION_STR}]",
- f"{qtpy.API_NAME} {qtbindings_version} [Qt version: {qtpy.QT_VERSION}]",
- ]
-
-
-def pytest_generate_tests(metafunc):
- """Generate tests for the given metafunc."""
- # See this StackOverflow answer for more information: https://t.ly/9anqz
- if metafunc.config.option.repeat is not None:
- count = int(metafunc.config.option.repeat)
-
- # We're going to duplicate these tests by parametrizing them,
- # which requires that each test has a fixture to accept the parameter.
- # We can add a new fixture like so:
- metafunc.fixturenames.append("tmp_ct")
-
- # Now we parametrize. This is what happens when we do e.g.,
- # @pytest.mark.parametrize('tmp_ct', range(count))
- # def test_foo(): pass
- metafunc.parametrize("tmp_ct", range(count))
diff --git a/qwt/tests/data/PythonQwt.svg b/qwt/tests/data/PythonQwt.svg
deleted file mode 100644
index 92bbe2c..0000000
--- a/qwt/tests/data/PythonQwt.svg
+++ /dev/null
@@ -1,484 +0,0 @@
-
-
-
-
diff --git a/qwt/tests/data/bodedemo.png b/qwt/tests/data/bodedemo.png
deleted file mode 100644
index e3ca87e..0000000
Binary files a/qwt/tests/data/bodedemo.png and /dev/null differ
diff --git a/qwt/tests/data/cartesian.png b/qwt/tests/data/cartesian.png
deleted file mode 100644
index 449ac14..0000000
Binary files a/qwt/tests/data/cartesian.png and /dev/null differ
diff --git a/qwt/tests/data/cpudemo.png b/qwt/tests/data/cpudemo.png
deleted file mode 100644
index 5f5660b..0000000
Binary files a/qwt/tests/data/cpudemo.png and /dev/null differ
diff --git a/qwt/tests/data/curvebenchmark1.png b/qwt/tests/data/curvebenchmark1.png
deleted file mode 100644
index 261dfe9..0000000
Binary files a/qwt/tests/data/curvebenchmark1.png and /dev/null differ
diff --git a/qwt/tests/data/curvebenchmark2.png b/qwt/tests/data/curvebenchmark2.png
deleted file mode 100644
index 162c27f..0000000
Binary files a/qwt/tests/data/curvebenchmark2.png and /dev/null differ
diff --git a/qwt/tests/data/curvedemo1.png b/qwt/tests/data/curvedemo1.png
deleted file mode 100644
index 8cc3e8e..0000000
Binary files a/qwt/tests/data/curvedemo1.png and /dev/null differ
diff --git a/qwt/tests/data/curvedemo2.png b/qwt/tests/data/curvedemo2.png
deleted file mode 100644
index b060f67..0000000
Binary files a/qwt/tests/data/curvedemo2.png and /dev/null differ
diff --git a/qwt/tests/data/data.png b/qwt/tests/data/data.png
deleted file mode 100644
index f7ae40c..0000000
Binary files a/qwt/tests/data/data.png and /dev/null differ
diff --git a/qwt/tests/data/errorbar.png b/qwt/tests/data/errorbar.png
deleted file mode 100644
index bea587f..0000000
Binary files a/qwt/tests/data/errorbar.png and /dev/null differ
diff --git a/qwt/tests/data/eventfilter.png b/qwt/tests/data/eventfilter.png
deleted file mode 100644
index 5dbcc1b..0000000
Binary files a/qwt/tests/data/eventfilter.png and /dev/null differ
diff --git a/qwt/tests/data/image.png b/qwt/tests/data/image.png
deleted file mode 100644
index d211916..0000000
Binary files a/qwt/tests/data/image.png and /dev/null differ
diff --git a/qwt/tests/data/loadtest.png b/qwt/tests/data/loadtest.png
deleted file mode 100644
index 8b4b8e5..0000000
Binary files a/qwt/tests/data/loadtest.png and /dev/null differ
diff --git a/qwt/tests/data/logcurve.png b/qwt/tests/data/logcurve.png
deleted file mode 100644
index e81de61..0000000
Binary files a/qwt/tests/data/logcurve.png and /dev/null differ
diff --git a/qwt/tests/data/mapdemo.png b/qwt/tests/data/mapdemo.png
deleted file mode 100644
index 623a93e..0000000
Binary files a/qwt/tests/data/mapdemo.png and /dev/null differ
diff --git a/qwt/tests/data/multidemo.png b/qwt/tests/data/multidemo.png
deleted file mode 100644
index 7f7b8a7..0000000
Binary files a/qwt/tests/data/multidemo.png and /dev/null differ
diff --git a/qwt/tests/data/simple.png b/qwt/tests/data/simple.png
deleted file mode 100644
index 2aa8593..0000000
Binary files a/qwt/tests/data/simple.png and /dev/null differ
diff --git a/qwt/tests/data/stylesheet.png b/qwt/tests/data/stylesheet.png
deleted file mode 100644
index 576a43e..0000000
Binary files a/qwt/tests/data/stylesheet.png and /dev/null differ
diff --git a/qwt/tests/data/symbol.svg b/qwt/tests/data/symbol.svg
deleted file mode 100644
index 146b0be..0000000
--- a/qwt/tests/data/symbol.svg
+++ /dev/null
@@ -1,411 +0,0 @@
-
-
-
-
diff --git a/qwt/tests/data/symbols.png b/qwt/tests/data/symbols.png
deleted file mode 100644
index 17cb695..0000000
Binary files a/qwt/tests/data/symbols.png and /dev/null differ
diff --git a/qwt/tests/data/testlauncher.png b/qwt/tests/data/testlauncher.png
deleted file mode 100644
index df1bf76..0000000
Binary files a/qwt/tests/data/testlauncher.png and /dev/null differ
diff --git a/qwt/tests/data/vertical.png b/qwt/tests/data/vertical.png
deleted file mode 100644
index 21a981d..0000000
Binary files a/qwt/tests/data/vertical.png and /dev/null differ
diff --git a/qwt/tests/test_backingstore.py b/qwt/tests/test_backingstore.py
deleted file mode 100644
index 4a0467f..0000000
--- a/qwt/tests/test_backingstore.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# -*- coding: utf-8 -*-
-
-SHOW = False # Do not show test in GUI-based test launcher
-
-from qwt.tests import utils
-from qwt.tests.test_simple import SimplePlot
-
-
-class BackingStorePlot(SimplePlot):
- TEST_EXPORT = False
-
- def __init__(self):
- SimplePlot.__init__(self)
- self.canvas().setPaintAttribute(self.canvas().BackingStore, True)
-
-
-def test_backingstore():
- """Test for backing store"""
- utils.test_widget(BackingStorePlot, size=(600, 400))
-
-
-if __name__ == "__main__":
- test_backingstore()
diff --git a/qwt/tests/test_bodedemo.py b/qwt/tests/test_bodedemo.py
deleted file mode 100644
index 2bbff41..0000000
--- a/qwt/tests/test_bodedemo.py
+++ /dev/null
@@ -1,304 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the PyQwt License
-# Copyright (C) 2003-2009 Gerard Vermeulen, for the original PyQwt example
-# Copyright (c) 2015 Pierre Raybaut, for the PyQt5/PySide port and further
-# developments (e.g. ported to PythonQwt API)
-# (see LICENSE file for more details)
-
-SHOW = True # Show test in GUI-based test launcher
-
-import os
-
-import numpy as np
-from qtpy.QtCore import Qt
-from qtpy.QtGui import QFont, QIcon, QPageLayout, QPen, QPixmap
-from qtpy.QtPrintSupport import QPrintDialog, QPrinter
-from qtpy.QtWidgets import (
- QFrame,
- QHBoxLayout,
- QLabel,
- QMainWindow,
- QToolBar,
- QToolButton,
- QWidget,
-)
-
-from qwt import (
- QwtLegend,
- QwtLogScaleEngine,
- QwtPlot,
- QwtPlotCurve,
- QwtPlotGrid,
- QwtPlotMarker,
- QwtPlotRenderer,
- QwtSymbol,
- QwtText,
-)
-from qwt.tests import utils
-
-print_xpm = [
- "32 32 12 1",
- "a c #ffffff",
- "h c #ffff00",
- "c c #ffffff",
- "f c #dcdcdc",
- "b c #c0c0c0",
- "j c #a0a0a4",
- "e c #808080",
- "g c #808000",
- "d c #585858",
- "i c #00ff00",
- "# c #000000",
- ". c None",
- "................................",
- "................................",
- "...........###..................",
- "..........#abb###...............",
- ".........#aabbbbb###............",
- ".........#ddaaabbbbb###.........",
- "........#ddddddaaabbbbb###......",
- ".......#deffddddddaaabbbbb###...",
- "......#deaaabbbddddddaaabbbbb###",
- ".....#deaaaaaaabbbddddddaaabbbb#",
- "....#deaaabbbaaaa#ddedddfggaaad#",
- "...#deaaaaaaaaaa#ddeeeeafgggfdd#",
- "..#deaaabbbaaaa#ddeeeeabbbbgfdd#",
- ".#deeefaaaaaaa#ddeeeeabbhhbbadd#",
- "#aabbbeeefaaa#ddeeeeabbbbbbaddd#",
- "#bbaaabbbeee#ddeeeeabbiibbadddd#",
- "#bbbbbaaabbbeeeeeeabbbbbbaddddd#",
- "#bjbbbbbbaaabbbbeabbbbbbadddddd#",
- "#bjjjjbbbbbbaaaeabbbbbbaddddddd#",
- "#bjaaajjjbbbbbbaaabbbbadddddddd#",
- "#bbbbbaaajjjbbbbbbaaaaddddddddd#",
- "#bjbbbbbbaaajjjbbbbbbddddddddd#.",
- "#bjjjjbbbbbbaaajjjbbbdddddddd#..",
- "#bjaaajjjbbbbbbjaajjbddddddd#...",
- "#bbbbbaaajjjbbbjbbaabdddddd#....",
- "###bbbbbbaaajjjjbbbbbddddd#.....",
- "...###bbbbbbaaajbbbbbdddd#......",
- "......###bbbbbbjbbbbbddd#.......",
- ".........###bbbbbbbbbdd#........",
- "............###bbbbbbd#.........",
- "...............###bbb#..........",
- "..................###...........",
-]
-
-
-class BodePlot(QwtPlot):
- def __init__(self, *args):
- QwtPlot.__init__(self, *args)
-
- self.setTitle("Frequency Response of a 2nd-order System")
- self.setCanvasBackground(Qt.darkBlue)
-
- # legend
- legend = QwtLegend()
- legend.setFrameStyle(QFrame.Box | QFrame.Sunken)
- self.insertLegend(legend, QwtPlot.BottomLegend)
-
- # grid
- QwtPlotGrid.make(plot=self, enableminor=(True, False), color=Qt.darkGray)
-
- # axes
- self.enableAxis(QwtPlot.yRight)
- self.setAxisTitle(QwtPlot.xBottom, "\u03c9/\u03c90")
- self.setAxisTitle(QwtPlot.yLeft, "Amplitude [dB]")
- self.setAxisTitle(QwtPlot.yRight, "Phase [\u00b0]")
-
- self.setAxisMaxMajor(QwtPlot.xBottom, 6)
- self.setAxisMaxMinor(QwtPlot.xBottom, 10)
- self.setAxisScaleEngine(QwtPlot.xBottom, QwtLogScaleEngine())
-
- # curves
- self.curve1 = QwtPlotCurve.make(
- title="Amplitude", linecolor=Qt.yellow, plot=self, antialiased=True
- )
- self.curve2 = QwtPlotCurve.make(
- title="Phase", linecolor=Qt.cyan, plot=self, antialiased=True
- )
- self.dB3Marker = QwtPlotMarker.make(
- label=QwtText.make(color=Qt.white, brush=Qt.red, weight=QFont.Light),
- linestyle=QwtPlotMarker.VLine,
- align=Qt.AlignRight | Qt.AlignBottom,
- color=Qt.green,
- width=2,
- style=Qt.DashDotLine,
- plot=self,
- )
- self.peakMarker = QwtPlotMarker.make(
- label=QwtText.make(
- color=Qt.red, brush=self.canvasBackground(), weight=QFont.Bold
- ),
- symbol=QwtSymbol.make(QwtSymbol.Diamond, Qt.yellow, Qt.green, (7, 7)),
- linestyle=QwtPlotMarker.HLine,
- align=Qt.AlignRight | Qt.AlignBottom,
- color=Qt.red,
- width=2,
- style=Qt.DashDotLine,
- plot=self,
- )
- QwtPlotMarker.make(
- xvalue=0.1,
- yvalue=-20.0,
- align=Qt.AlignRight | Qt.AlignBottom,
- label=QwtText.make(
- "[1-(\u03c9/\u03c90)2+2j\u03c9/Q]-1",
- color=Qt.white,
- borderradius=2,
- borderpen=QPen(Qt.lightGray, 5),
- brush=Qt.lightGray,
- weight=QFont.Bold,
- ),
- plot=self,
- )
-
- self.setDamp(0.01)
-
- def showData(self, frequency, amplitude, phase):
- self.curve1.setData(frequency, amplitude)
- self.curve2.setData(frequency, phase)
-
- def showPeak(self, frequency, amplitude):
- self.peakMarker.setValue(frequency, amplitude)
- label = self.peakMarker.label()
- label.setText("Peak: %4g dB" % amplitude)
- self.peakMarker.setLabel(label)
-
- def show3dB(self, frequency):
- self.dB3Marker.setValue(frequency, 0.0)
- label = self.dB3Marker.label()
- label.setText("-3dB at f = %4g" % frequency)
- self.dB3Marker.setLabel(label)
-
- def setDamp(self, d):
- self.damping = d
- # Numerical Python: f, g, a and p are NumPy arrays!
- f = np.exp(np.log(10.0) * np.arange(-2, 2.02, 0.04))
- g = 1.0 / (1.0 - f * f + 2j * self.damping * f)
- a = 20.0 * np.log10(abs(g))
- p = 180 * np.arctan2(g.imag, g.real) / np.pi
- # for show3dB
- i3 = np.argmax(np.where(np.less(a, -3.0), a, -100.0))
- f3 = f[i3] - (a[i3] + 3.0) * (f[i3] - f[i3 - 1]) / (a[i3] - a[i3 - 1])
- # for showPeak
- imax = np.argmax(a)
-
- self.showPeak(f[imax], a[imax])
- self.show3dB(f3)
- self.showData(f, a, p)
-
- self.replot()
-
-
-FNAME_PDF = "bode.pdf"
-
-
-class BodeDemo(QMainWindow):
- def __init__(self, *args):
- QMainWindow.__init__(self, *args)
-
- self.plot = BodePlot(self)
- self.plot.setContentsMargins(5, 5, 5, 0)
-
- self.setContextMenuPolicy(Qt.NoContextMenu)
-
- self.setCentralWidget(self.plot)
-
- toolBar = QToolBar(self)
- self.addToolBar(toolBar)
-
- btnPrint = QToolButton(toolBar)
- btnPrint.setText("Print")
- btnPrint.setIcon(QIcon(QPixmap(print_xpm)))
- btnPrint.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
- toolBar.addWidget(btnPrint)
- btnPrint.clicked.connect(self.print_)
-
- btnExport = QToolButton(toolBar)
- btnExport.setText("Export")
- btnExport.setIcon(QIcon(QPixmap(print_xpm)))
- btnExport.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
- toolBar.addWidget(btnExport)
- btnExport.clicked.connect(self.exportDocument)
-
- toolBar.addSeparator()
-
- dampBox = QWidget(toolBar)
- dampLayout = QHBoxLayout(dampBox)
- dampLayout.setSpacing(0)
- dampLayout.addWidget(QWidget(dampBox), 10) # spacer
- dampLayout.addWidget(QLabel("Damping Factor", dampBox), 0)
- dampLayout.addSpacing(10)
-
- toolBar.addWidget(dampBox)
-
- self.statusBar()
-
- self.showInfo()
-
- if utils.TestEnvironment().unattended:
- self.print_(unattended=True)
-
- def print_(self, unattended=False):
- try:
- mode = QPrinter.HighResolution
- printer = QPrinter(mode)
- except AttributeError:
- # Some PySide6 / PyQt6 versions do not have this attribute on Linux
- printer = QPrinter()
-
- printer.setCreator("Bode example")
- printer.setPageOrientation(QPageLayout.Landscape)
- try:
- printer.setColorMode(QPrinter.Color)
- except AttributeError:
- pass
-
- docName = str(self.plot.title().text())
- if not docName:
- docName.replace("\n", " -- ")
- printer.setDocName(docName)
-
- dialog = QPrintDialog(printer)
- if unattended:
- # Configure QPrinter object to print to PDF file
- printer.setPrinterName("")
- printer.setOutputFileName(FNAME_PDF)
- dialog.accept()
- ok = True
- else:
- ok = dialog.exec_()
- if ok:
- renderer = QwtPlotRenderer()
- renderer.renderTo(self.plot, printer)
-
- def exportDocument(self):
- renderer = QwtPlotRenderer(self.plot)
- renderer.exportTo(self.plot, "bode")
-
- def showInfo(self, text=""):
- self.statusBar().showMessage(text)
-
- def moved(self, point):
- info = "Freq=%g, Ampl=%g, Phase=%g" % (
- self.plot.invTransform(QwtPlot.xBottom, point.x()),
- self.plot.invTransform(QwtPlot.yLeft, point.y()),
- self.plot.invTransform(QwtPlot.yRight, point.y()),
- )
- self.showInfo(info)
-
- def selected(self, _):
- self.showInfo()
-
-
-def test_bodedemo():
- """Bode demo"""
- utils.test_widget(BodeDemo, (640, 480))
- if os.path.isfile(FNAME_PDF):
- os.remove(FNAME_PDF)
-
-
-if __name__ == "__main__":
- test_bodedemo()
diff --git a/qwt/tests/test_cartesian.py b/qwt/tests/test_cartesian.py
deleted file mode 100644
index 507b551..0000000
--- a/qwt/tests/test_cartesian.py
+++ /dev/null
@@ -1,109 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the PyQwt License
-# Copyright (C) 2003-2009 Gerard Vermeulen, for the original PyQwt example
-# Copyright (c) 2015 Pierre Raybaut, for the PyQt5/PySide port and further
-# developments (e.g. ported to PythonQwt API)
-# (see LICENSE file for more details)
-
-SHOW = True # Show test in GUI-based test launcher
-
-import numpy as np
-from qtpy.QtCore import Qt
-
-from qwt import QwtPlot, QwtPlotCurve, QwtPlotGrid, QwtPlotItem, QwtScaleDraw
-from qwt.tests import utils
-
-
-class CartesianAxis(QwtPlotItem):
- """Supports a coordinate system similar to
- http://en.wikipedia.org/wiki/Image:Cartesian-coordinate-system.svg"""
-
- def __init__(self, masterAxis, slaveAxis):
- """Valid input values for masterAxis and slaveAxis are QwtPlot.yLeft,
- QwtPlot.yRight, QwtPlot.xBottom, and QwtPlot.xTop. When masterAxis is
- an x-axis, slaveAxis must be an y-axis; and vice versa."""
- QwtPlotItem.__init__(self)
- self.__axis = masterAxis
- if masterAxis in (QwtPlot.yLeft, QwtPlot.yRight):
- self.setAxes(slaveAxis, masterAxis)
- else:
- self.setAxes(masterAxis, slaveAxis)
- self.scaleDraw = QwtScaleDraw()
- self.scaleDraw.setAlignment(
- (
- QwtScaleDraw.LeftScale,
- QwtScaleDraw.RightScale,
- QwtScaleDraw.BottomScale,
- QwtScaleDraw.TopScale,
- )[masterAxis]
- )
-
- def draw(self, painter, xMap, yMap, rect):
- """Draw an axis on the plot canvas"""
- xtr = xMap.transform
- ytr = yMap.transform
- if self.__axis in (QwtPlot.yLeft, QwtPlot.yRight):
- self.scaleDraw.move(round(xtr(0.0)), yMap.p2())
- self.scaleDraw.setLength(yMap.p1() - yMap.p2())
- elif self.__axis in (QwtPlot.xBottom, QwtPlot.xTop):
- self.scaleDraw.move(xMap.p1(), round(ytr(0.0)))
- self.scaleDraw.setLength(xMap.p2() - xMap.p1())
- self.scaleDraw.setScaleDiv(self.plot().axisScaleDiv(self.__axis))
- self.scaleDraw.draw(painter, self.plot().palette())
-
-
-class CartesianPlot(QwtPlot):
- """Creates a coordinate system similar system
- http://en.wikipedia.org/wiki/Image:Cartesian-coordinate-system.svg"""
-
- def __init__(self, *args):
- QwtPlot.__init__(self, *args)
- self.setTitle("Cartesian Coordinate System Demo")
- # create a plot with a white canvas
- self.setCanvasBackground(Qt.white)
- # set plot layout
- self.plotLayout().setCanvasMargin(0)
- self.plotLayout().setAlignCanvasToScales(True)
- # attach a grid
- QwtPlotGrid.make(self, color=Qt.lightGray, width=0, style=Qt.DotLine, z=-1)
- # attach a x-axis
- xaxis = CartesianAxis(QwtPlot.xBottom, QwtPlot.yLeft)
- xaxis.attach(self)
- self.enableAxis(QwtPlot.xBottom, False)
- # attach a y-axis
- yaxis = CartesianAxis(QwtPlot.yLeft, QwtPlot.xBottom)
- yaxis.attach(self)
- self.enableAxis(QwtPlot.yLeft, False)
- # calculate 3 NumPy arrays
- x = np.arange(-2 * np.pi, 2 * np.pi, 0.01)
- # attach a curve
- QwtPlotCurve.make(
- x,
- np.pi * np.sin(x),
- title="y = pi*sin(x)",
- linecolor=Qt.green,
- linewidth=2,
- plot=self,
- antialiased=True,
- )
- # attach another curve
- QwtPlotCurve.make(
- x,
- 4 * np.pi * np.cos(x) * np.cos(x) * np.sin(x),
- title="y = 4*pi*sin(x)*cos(x)**2",
- linecolor=Qt.blue,
- linewidth=2,
- plot=self,
- antialiased=True,
- )
- self.replot()
-
-
-def test_cartesian():
- """Cartesian plot test"""
- utils.test_widget(CartesianPlot, (800, 480))
-
-
-if __name__ == "__main__":
- test_cartesian()
diff --git a/qwt/tests/test_cpudemo.py b/qwt/tests/test_cpudemo.py
deleted file mode 100644
index 18ccdf7..0000000
--- a/qwt/tests/test_cpudemo.py
+++ /dev/null
@@ -1,401 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the PyQwt License
-# Copyright (C) 2003-2009 Gerard Vermeulen, for the original PyQwt example
-# Copyright (c) 2015 Pierre Raybaut, for the PyQt5/PySide port and further
-# developments (e.g. ported to PythonQwt API)
-# (see LICENSE file for more details)
-
-SHOW = True # Show test in GUI-based test launcher
-
-import os
-
-import numpy as np
-from qtpy.QtCore import QRectF, Qt, QTime
-from qtpy.QtGui import QBrush, QColor
-from qtpy.QtWidgets import QLabel, QVBoxLayout, QWidget
-
-from qwt import (
- QwtLegend,
- QwtLegendData,
- QwtPlot,
- QwtPlotCurve,
- QwtPlotItem,
- QwtPlotMarker,
- QwtScaleDraw,
- QwtText,
-)
-from qwt.tests import utils
-
-
-class CpuStat:
- User = 0
- Nice = 1
- System = 2
- Idle = 3
- counter = 0
- dummyValues = (
- (103726, 0, 23484, 819556),
- (103783, 0, 23489, 819604),
- (103798, 0, 23490, 819688),
- (103820, 0, 23490, 819766),
- (103840, 0, 23493, 819843),
- (103875, 0, 23499, 819902),
- (103917, 0, 23504, 819955),
- (103950, 0, 23508, 820018),
- (103987, 0, 23510, 820079),
- (104020, 0, 23513, 820143),
- (104058, 0, 23514, 820204),
- (104099, 0, 23520, 820257),
- (104121, 0, 23525, 820330),
- (104159, 0, 23530, 820387),
- (104176, 0, 23534, 820466),
- (104215, 0, 23538, 820523),
- (104245, 0, 23541, 820590),
- (104267, 0, 23545, 820664),
- (104311, 0, 23555, 820710),
- (104355, 0, 23565, 820756),
- (104367, 0, 23567, 820842),
- (104383, 0, 23572, 820921),
- (104396, 0, 23577, 821003),
- (104413, 0, 23579, 821084),
- (104446, 0, 23588, 821142),
- (104521, 0, 23594, 821161),
- (104611, 0, 23604, 821161),
- (104708, 0, 23607, 821161),
- (104804, 0, 23611, 821161),
- (104895, 0, 23620, 821161),
- (104993, 0, 23622, 821161),
- (105089, 0, 23626, 821161),
- (105185, 0, 23630, 821161),
- (105281, 0, 23634, 821161),
- (105379, 0, 23636, 821161),
- (105472, 0, 23643, 821161),
- (105569, 0, 23646, 821161),
- (105666, 0, 23649, 821161),
- (105763, 0, 23652, 821161),
- (105828, 0, 23661, 821187),
- (105904, 0, 23666, 821206),
- (105999, 0, 23671, 821206),
- (106094, 0, 23676, 821206),
- (106184, 0, 23686, 821206),
- (106273, 0, 23692, 821211),
- (106306, 0, 23700, 821270),
- (106341, 0, 23703, 821332),
- (106392, 0, 23709, 821375),
- (106423, 0, 23715, 821438),
- (106472, 0, 23721, 821483),
- (106531, 0, 23727, 821517),
- (106562, 0, 23732, 821582),
- (106597, 0, 23736, 821643),
- (106633, 0, 23737, 821706),
- (106666, 0, 23742, 821768),
- (106697, 0, 23744, 821835),
- (106730, 0, 23748, 821898),
- (106765, 0, 23751, 821960),
- (106799, 0, 23754, 822023),
- (106831, 0, 23758, 822087),
- (106862, 0, 23761, 822153),
- (106899, 0, 23763, 822214),
- (106932, 0, 23766, 822278),
- (106965, 0, 23768, 822343),
- (107009, 0, 23771, 822396),
- (107040, 0, 23775, 822461),
- (107092, 0, 23780, 822504),
- (107143, 0, 23787, 822546),
- (107200, 0, 23795, 822581),
- (107250, 0, 23803, 822623),
- (107277, 0, 23810, 822689),
- (107286, 0, 23810, 822780),
- (107313, 0, 23817, 822846),
- (107325, 0, 23818, 822933),
- (107332, 0, 23818, 823026),
- (107344, 0, 23821, 823111),
- (107357, 0, 23821, 823198),
- (107368, 0, 23823, 823284),
- (107375, 0, 23824, 823377),
- (107386, 0, 23825, 823465),
- (107396, 0, 23826, 823554),
- (107422, 0, 23830, 823624),
- (107434, 0, 23831, 823711),
- (107456, 0, 23835, 823785),
- (107468, 0, 23838, 823870),
- (107487, 0, 23840, 823949),
- (107515, 0, 23843, 824018),
- (107528, 0, 23846, 824102),
- (107535, 0, 23851, 824190),
- (107548, 0, 23853, 824275),
- (107562, 0, 23857, 824357),
- (107656, 0, 23863, 824357),
- (107751, 0, 23868, 824357),
- (107849, 0, 23870, 824357),
- (107944, 0, 23875, 824357),
- (108043, 0, 23876, 824357),
- (108137, 0, 23882, 824357),
- (108230, 0, 23889, 824357),
- (108317, 0, 23902, 824357),
- (108412, 0, 23907, 824357),
- (108511, 0, 23908, 824357),
- (108608, 0, 23911, 824357),
- (108704, 0, 23915, 824357),
- (108801, 0, 23918, 824357),
- (108891, 0, 23928, 824357),
- (108987, 0, 23932, 824357),
- (109072, 0, 23943, 824361),
- (109079, 0, 23943, 824454),
- (109086, 0, 23944, 824546),
- (109098, 0, 23950, 824628),
- (109108, 0, 23955, 824713),
- (109115, 0, 23957, 824804),
- (109122, 0, 23958, 824896),
- (109132, 0, 23959, 824985),
- (109142, 0, 23961, 825073),
- (109146, 0, 23962, 825168),
- (109153, 0, 23964, 825259),
- (109162, 0, 23966, 825348),
- (109168, 0, 23969, 825439),
- (109176, 0, 23971, 825529),
- (109185, 0, 23974, 825617),
- (109193, 0, 23977, 825706),
- (109198, 0, 23978, 825800),
- (109206, 0, 23978, 825892),
- (109212, 0, 23981, 825983),
- (109219, 0, 23981, 826076),
- (109225, 0, 23981, 826170),
- (109232, 0, 23984, 826260),
- (109242, 0, 23984, 826350),
- (109255, 0, 23986, 826435),
- (109268, 0, 23987, 826521),
- (109283, 0, 23990, 826603),
- (109288, 0, 23991, 826697),
- (109295, 0, 23993, 826788),
- (109308, 0, 23994, 826874),
- (109322, 0, 24009, 826945),
- (109328, 0, 24011, 827037),
- (109338, 0, 24012, 827126),
- (109347, 0, 24012, 827217),
- (109354, 0, 24017, 827305),
- (109367, 0, 24017, 827392),
- (109371, 0, 24019, 827486),
- )
-
- def __init__(self):
- self.procValues = self.__lookup()
-
- def statistic(self):
- values = self.__lookup()
- userDelta = 0.0
- for i in [CpuStat.User, CpuStat.Nice]:
- userDelta += values[i] - self.procValues[i]
- systemDelta = values[CpuStat.System] - self.procValues[CpuStat.System]
- totalDelta = 0.0
- for i in range(len(self.procValues)):
- totalDelta += values[i] - self.procValues[i]
- self.procValues = values
- return 100.0 * userDelta / totalDelta, 100.0 * systemDelta / totalDelta
-
- def upTime(self):
- result = QTime(0, 0, 0)
- for item in self.procValues:
- result = result.addSecs(int(0.01 * item))
- return result
-
- def __lookup(self):
- if os.path.exists("/proc/stat"):
- with open("/proc/stat") as file:
- for line in file:
- words = line.split()
- if words[0] == "cpu" and len(words) >= 5:
- return [float(w) for w in words[1:]]
- else:
- result = CpuStat.dummyValues[CpuStat.counter]
- CpuStat.counter += 1
- CpuStat.counter %= len(CpuStat.dummyValues)
- return result
-
-
-class CpuPieMarker(QwtPlotMarker):
- def __init__(self, *args):
- QwtPlotMarker.__init__(self, *args)
- self.setZ(1000.0)
- self.setRenderHint(QwtPlotItem.RenderAntialiased, True)
-
- def rtti(self):
- return QwtPlotItem.Rtti_PlotUserItem
-
- def draw(self, painter, xMap, yMap, rect):
- margin = 5
- pieRect = QRectF()
- pieRect.setX(rect.x() + margin)
- pieRect.setY(rect.y() + margin)
- pieRect.setHeight(int(yMap.transform(80.0)))
- pieRect.setWidth(pieRect.height())
-
- angle = 3 * 5760 / 4
- for key in ["User", "System", "Idle"]:
- curve = self.plot().cpuPlotCurve(key)
- if curve.dataSize():
- value = int(5760 * curve.sample(0).y() / 100.0)
- painter.save()
- painter.setBrush(QBrush(curve.pen().color(), Qt.SolidPattern))
- painter.drawPie(pieRect, int(-angle), int(-value))
- painter.restore()
- angle += value
-
-
-class TimeScaleDraw(QwtScaleDraw):
- def __init__(self, baseTime, *args):
- QwtScaleDraw.__init__(self, *args)
- self.baseTime = baseTime
-
- def label(self, value):
- upTime = self.baseTime.addSecs(int(value))
- return QwtText(upTime.toString())
-
-
-class Background(QwtPlotItem):
- def __init__(self):
- QwtPlotItem.__init__(self)
- self.setZ(0.0)
-
- def rtti(self):
- return QwtPlotItem.Rtti_PlotUserItem
-
- def draw(self, painter, xMap, yMap, rect):
- c = QColor(Qt.white)
- r = QRectF(rect)
-
- for i in range(100, 0, -10):
- r.setBottom(int(yMap.transform(i - 10)))
- r.setTop(int(yMap.transform(i)))
- painter.fillRect(r, c)
- c = c.darker(110)
-
-
-class CpuCurve(QwtPlotCurve):
- def __init__(self, *args):
- QwtPlotCurve.__init__(self, *args)
- self.setRenderHint(QwtPlotItem.RenderAntialiased)
-
- def setColor(self, color):
- c = QColor(color)
- c.setAlpha(150)
-
- self.setPen(c)
- self.setBrush(c)
-
-
-class CpuPlot(QwtPlot):
- HISTORY = 60
-
- def __init__(self, unattended=False):
- QwtPlot.__init__(self)
-
- self.curves = {}
- self.data = {}
- self.timeData = 1.0 * np.arange(self.HISTORY - 1, -1, -1)
- self.cpuStat = CpuStat()
-
- self.setAutoReplot(False)
-
- self.plotLayout().setAlignCanvasToScales(True)
-
- legend = QwtLegend()
- legend.setDefaultItemMode(QwtLegendData.Checkable)
- self.insertLegend(legend, QwtPlot.RightLegend)
-
- self.setAxisTitle(QwtPlot.xBottom, "System Uptime [h:m:s]")
- self.setAxisScaleDraw(QwtPlot.xBottom, TimeScaleDraw(self.cpuStat.upTime()))
- self.setAxisScale(QwtPlot.xBottom, 0, self.HISTORY)
- self.setAxisLabelRotation(QwtPlot.xBottom, -50.0)
- self.setAxisLabelAlignment(QwtPlot.xBottom, Qt.AlignLeft | Qt.AlignBottom)
-
- self.setAxisTitle(QwtPlot.yLeft, "Cpu Usage [%]")
- self.setAxisScale(QwtPlot.yLeft, 0, 100)
-
- background = Background()
- background.attach(self)
-
- pie = CpuPieMarker()
- pie.attach(self)
-
- curve = CpuCurve("System")
- curve.setColor(Qt.red)
- curve.attach(self)
- self.curves["System"] = curve
- self.data["System"] = np.zeros(self.HISTORY, float)
-
- curve = CpuCurve("User")
- curve.setColor(Qt.blue)
- curve.setZ(curve.z() - 1.0)
- curve.attach(self)
- self.curves["User"] = curve
- self.data["User"] = np.zeros(self.HISTORY, float)
-
- curve = CpuCurve("Total")
- curve.setColor(Qt.black)
- curve.setZ(curve.z() - 2.0)
- curve.attach(self)
- self.curves["Total"] = curve
- self.data["Total"] = np.zeros(self.HISTORY, float)
-
- curve = CpuCurve("Idle")
- curve.setColor(Qt.darkCyan)
- curve.setZ(curve.z() - 3.0)
- curve.attach(self)
- self.curves["Idle"] = curve
- self.data["Idle"] = np.zeros(self.HISTORY, float)
-
- self.showCurve(self.curves["System"], True)
- self.showCurve(self.curves["User"], True)
- self.showCurve(self.curves["Total"], False or unattended)
- self.showCurve(self.curves["Idle"], False or unattended)
-
- self.startTimer(20 if unattended else 1000)
-
- legend.checked.connect(self.showCurve)
- self.replot()
-
- def timerEvent(self, e):
- for data in self.data.values():
- data[1:] = data[0:-1]
- self.data["User"][0], self.data["System"][0] = self.cpuStat.statistic()
- self.data["Total"][0] = self.data["User"][0] + self.data["System"][0]
- self.data["Idle"][0] = 100.0 - self.data["Total"][0]
-
- self.timeData += 1.0
-
- self.setAxisScale(QwtPlot.xBottom, self.timeData[-1], self.timeData[0])
- for key in self.curves.keys():
- self.curves[key].setData(self.timeData, self.data[key])
-
- self.replot()
-
- def showCurve(self, item, on, index=None):
- item.setVisible(on)
- self.legend().legendWidget(item).setChecked(on)
- self.replot()
-
- def cpuPlotCurve(self, key):
- return self.curves[key]
-
-
-class CpuDemo(QWidget):
- def __init__(self, parent=None, unattended=False):
- super(CpuDemo, self).__init__(parent)
- layout = QVBoxLayout()
- self.setLayout(layout)
- plot = CpuPlot(unattended=unattended)
- plot.setTitle("History")
- layout.addWidget(plot)
- label = QLabel("Press the legend to en/disable a curve")
- layout.addWidget(label)
-
-
-def test_cpudemo():
- """CPU demo"""
- utils.test_widget(CpuDemo, (600, 400))
-
-
-if __name__ == "__main__":
- test_cpudemo()
diff --git a/qwt/tests/test_curvebenchmark1.py b/qwt/tests/test_curvebenchmark1.py
deleted file mode 100644
index 8032fa5..0000000
--- a/qwt/tests/test_curvebenchmark1.py
+++ /dev/null
@@ -1,193 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Licensed under the terms of the MIT License
-# Copyright (c) 2015 Pierre Raybaut
-# (see LICENSE file for more details)
-
-"""Curve benchmark example"""
-
-SHOW = True # Show test in GUI-based test launcher
-
-import time
-
-import numpy as np
-from qtpy.QtCore import Qt
-from qtpy.QtWidgets import (
- QApplication,
- QGridLayout,
- QLineEdit,
- QMainWindow,
- QTabWidget,
- QTextEdit,
- QWidget,
-)
-
-from qwt import QwtPlot, QwtPlotCurve
-from qwt.tests import utils
-
-COLOR_INDEX = None
-
-
-def get_curve_color():
- global COLOR_INDEX
- colors = (Qt.blue, Qt.red, Qt.green, Qt.yellow, Qt.magenta, Qt.cyan)
- if COLOR_INDEX is None:
- COLOR_INDEX = 0
- else:
- COLOR_INDEX = (COLOR_INDEX + 1) % len(colors)
- return colors[COLOR_INDEX]
-
-
-PLOT_ID = 0
-
-
-class BMPlot(QwtPlot):
- def __init__(self, title, xdata, ydata, style, symbol=None, *args):
- super(BMPlot, self).__init__(*args)
- global PLOT_ID
- self.setMinimumSize(200, 150)
- PLOT_ID += 1
- self.setTitle("%s (#%d)" % (title, PLOT_ID))
- self.setAxisTitle(QwtPlot.xBottom, "x")
- self.setAxisTitle(QwtPlot.yLeft, "y")
- self.curve_nb = 0
- for idx in range(1, 11):
- self.curve_nb += 1
- QwtPlotCurve.make(
- xdata,
- ydata * idx,
- style=style,
- symbol=symbol,
- linecolor=get_curve_color(),
- antialiased=True,
- plot=self,
- )
- self.replot()
-
-
-class BMWidget(QWidget):
- def __init__(self, nbcol, points, *args, **kwargs):
- super(BMWidget, self).__init__()
- self.plot_nb = 0
- self.curve_nb = 0
- self.setup(nbcol, points, *args, **kwargs)
-
- def params(self, *args, **kwargs):
- if kwargs.get("only_lines", False):
- return (("Lines", None),)
- else:
- return (
- ("Lines", None),
- ("Dots", None),
- )
-
- def setup(self, nbcol, points, *args, **kwargs):
- x = np.linspace(0.001, 20.0, int(points))
- y = (np.sin(x) / x) * np.cos(20 * x)
- layout = QGridLayout()
- col, row = 0, 0
- for style, symbol in self.params(*args, **kwargs):
- plot = BMPlot(style, x, y, getattr(QwtPlotCurve, style), symbol=symbol)
- layout.addWidget(plot, row, col)
- self.plot_nb += 1
- self.curve_nb += plot.curve_nb
- col += 1
- if col >= nbcol:
- row += 1
- col = 0
- self.text = QLineEdit()
- self.text.setReadOnly(True)
- self.text.setAlignment(Qt.AlignCenter)
- self.text.setText("Rendering plot...")
- layout.addWidget(self.text, row + 1, 0, 1, nbcol)
- self.setLayout(layout)
-
-
-class BMText(QTextEdit):
- def __init__(self, parent=None, title=None):
- super(BMText, self).__init__(parent)
- self.setReadOnly(True)
- library = "PythonQwt"
- wintitle = self.parent().windowTitle()
- if not wintitle:
- wintitle = "Benchmark"
- if title is None:
- title = "%s example" % wintitle
- self.parent().setWindowTitle("%s [%s]" % (wintitle, library))
- self.setText(
- """\
-%s:Developped by Pierre Raybaut
-
Copyright © 2020 Pierre Raybaut
-
%s""" - % (self.windowTitle(), get_lib_versions()), - ) - - -class TestOptions(QW.QGroupBox): - """Test options groupbox""" - - def __init__(self, parent=None): - super(TestOptions, self).__init__("Test options", parent) - self.setLayout(QW.QFormLayout()) - self.hide() - - def add_checkbox(self, title, label, slot): - """Add new checkbox to option panel""" - widget = QW.QCheckBox(label, self) - widget.stateChanged.connect(slot) - self.layout().addRow(title, widget) - self.show() - return widget - - -class TestCentralWidget(QW.QWidget): - """Test central widget""" - - def __init__(self, widget_name, parent=None): - super(TestCentralWidget, self).__init__(parent) - self.widget_name = widget_name - self.plots = None - self.setLayout(QW.QVBoxLayout()) - self.options = TestOptions(self) - self.add_widget(self.options) - - def get_widget_of_interest(self): - """Return widget of interest""" - if self.plots is not None and len(self.plots) == 1: - return self.plots[0] - return self.parent() - - def add_widget(self, widget): - """Add new sub-widget""" - self.layout().addWidget(widget) - if isinstance(widget, QwtPlot): - self.plots = [widget] - else: - self.plots = widget.findChildren(QwtPlot) - for index, plot in enumerate(self.plots): - plot_name = plot.objectName() - if not plot_name: - plot_name = "Plot #%d" % (index + 1) - widget = self.options.add_checkbox( - plot_name, "Enable new flat style option", plot.setFlatStyle - ) - widget.setChecked(plot.flatStyle()) - - -def take_screenshot(widget): - """Take screenshot and save it to the data folder""" - bname = (widget.objectName().lower() + ".png").replace("window", "") - bname = bname.replace("plot", "").replace("widget", "") - qth.take_screenshot(widget, osp.join(TEST_PATH, "data", bname), quit=True) - - -def close_widgets_and_quit() -> None: - """Close Qt top level widgets and quit Qt event loop""" - QW.QApplication.processEvents() - for widget in QW.QApplication.instance().topLevelWidgets(): - assert widget.close() - QC.QTimer.singleShot(0, QW.QApplication.instance().quit) - - -def test_widget(widget_class, size=None, title=None, options=True): - """Test widget""" - widget_name = widget_class.__name__ - app = QW.QApplication.instance() - if app is None: - app = QW.QApplication([]) - test_env = TestEnvironment() - if inspect.signature(widget_class).parameters.get("unattended") is None: - widget = widget_class() - else: - widget = widget_class(unattended=test_env.unattended) - window = widget - if options: - if isinstance(widget, QW.QMainWindow): - widget = window.centralWidget() - widget.setParent(None) - else: - window = QW.QMainWindow() - central_widget = TestCentralWidget(widget_name, parent=window) - central_widget.add_widget(widget) - window.setCentralWidget(central_widget) - widget_of_interest = central_widget.get_widget_of_interest() - else: - widget_of_interest = window - widget_of_interest.setObjectName(widget_name) - if title is None: - title = 'Test "%s" - PythonQwt %s' % (widget_name, qwt.__version__) - window.setWindowTitle(title) - if size is not None: - width, height = size - window.resize(width, height) - - window.show() - if test_env.screenshots: - QC.QTimer.singleShot(1000, lambda: take_screenshot(widget_of_interest)) - elif test_env.unattended: - QC.QTimer.singleShot(0, close_widgets_and_quit) - if QT_API == "pyside6": - app.exec() - else: - app.exec_() - return app diff --git a/qwt/text.py b/qwt/text.py deleted file mode 100644 index 2fb0f9a..0000000 --- a/qwt/text.py +++ /dev/null @@ -1,1524 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Licensed under the terms of the Qwt License -# Copyright (c) 2002 Uwe Rathmann, for the original C++ code -# Copyright (c) 2015 Pierre Raybaut, for the Python translation/optimization -# (see LICENSE file for more details) - -""" -Text widgets ------------- - -QwtText -~~~~~~~ - -.. autoclass:: QwtText - :members: - -QwtTextLabel -~~~~~~~~~~~~ - -.. autoclass:: QwtTextLabel - :members: - -Text engines ------------- - -QwtTextEngine -~~~~~~~~~~~~~ - -.. autoclass:: QwtTextEngine - :members: - -QwtPlainTextEngine -~~~~~~~~~~~~~~~~~~ - -.. autoclass:: QwtPlainTextEngine - :members: - -QwtRichTextEngine -~~~~~~~~~~~~~~~~~ - -.. autoclass:: QwtRichTextEngine - :members: -""" - -import math -import os -import struct - -from qtpy.QtCore import QObject, QRectF, QSize, QSizeF, Qt -from qtpy.QtGui import ( - QAbstractTextDocumentLayout, - QColor, - QFont, - QFontInfo, - QFontMetrics, - QFontMetricsF, - QPainter, - QPalette, - QPixmap, - QTextDocument, - QTextOption, - QTransform, -) -from qtpy.QtWidgets import QApplication, QFrame, QSizePolicy, QWidget - -from qwt.painter import QwtPainter -from qwt.qthelpers import qcolor_from_str - -QWIDGETSIZE_MAX = (1 << 24) - 1 - -QT_API = os.environ["QT_API"] - - -# Cache Qt alignment flags as plain ints once at import time. On PyQt6 these -# are ``Qt.AlignmentFlag`` enum members and every bitwise test goes through -# ``enum.__and__`` (~6 us each). The test code below combines them in hot -# paths called per-tick / per-label / per-paint event. -def _flag_int(flag): - """Return the integer value of a Qt enum/flag (PyQt5 and PyQt6).""" - try: - return flag.value - except AttributeError: - return int(flag) - - -_ALIGN_LEFT = _flag_int(Qt.AlignLeft) -_ALIGN_RIGHT = _flag_int(Qt.AlignRight) -_ALIGN_TOP = _flag_int(Qt.AlignTop) -_ALIGN_BOTTOM = _flag_int(Qt.AlignBottom) -_ALIGN_HCENTER = _flag_int(Qt.AlignHCenter) -_ALIGN_JUSTIFY = _flag_int(Qt.AlignJustify) -_ALIGN_CENTER = _flag_int(Qt.AlignCenter) - - -def taggedRichText(text, flags): - richText = text - if flags & _ALIGN_JUSTIFY: - richText = '