# -*- 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
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
import numpy as np
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.array(xdata, dtype=np.float64, copy=False)
memory[1 : (size - 1) * 2 + 2 : 2] = np.array(ydata, dtype=np.float64, copy=False)
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()
[docs]
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()
[docs]
@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
[docs]
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)
[docs]
def rtti(self):
""":return: `QwtPlotItem.Rtti_PlotCurve`"""
return QwtPlotItem.Rtti_PlotCurve
[docs]
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()
[docs]
def testLegendAttribute(self, attribute):
"""
:param int attribute: Legend attribute
:return: True, when attribute is enabled
.. seealso::
:py:meth:`setLegendAttribute()`
"""
return self.__data.legendAttributes & attribute
[docs]
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()
[docs]
def style(self):
"""
:return: Style of the curve
.. seealso::
:py:meth:`setStyle()`
"""
return self.__data.style
[docs]
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()
[docs]
def symbol(self):
"""
:return: Current symbol or None, when no symbol has been assigned
.. seealso::
:py:meth:`setSymbol()`
"""
return self.__data.symbol
[docs]
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()
[docs]
def pen(self):
"""
:return: Pen used to draw the lines
.. seealso::
:py:meth:`setPen()`, :py:meth:`brush()`
"""
return self.__data.pen
[docs]
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()
[docs]
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
[docs]
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)
[docs]
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()
[docs]
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)
[docs]
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)
[docs]
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()
[docs]
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)
[docs]
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)
[docs]
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()
[docs]
def testCurveAttribute(self, attribute):
"""
:return: True, if attribute is enabled
.. seealso::
:py:meth:`setCurveAttribute()`
"""
return self.__data.attributes & attribute
[docs]
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()
[docs]
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()))
[docs]
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)
[docs]
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()
[docs]
def baseline(self):
"""
:return: Value of the baseline
.. seealso::
:py:meth:`setBaseline()`
"""
return self.__data.baseline
[docs]
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
[docs]
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
[docs]
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))
)
[docs]
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))
)