Add "left" and "right" step Modes (#1360)
* Add "lstep" and "rstep" step Modes stepMode is currently either True or False. If it is True, it requires the user to make len(x) = len(y)+1. This is inconvenient because it makes it difficult to change the stepMode on a given curve (just as one would change, e.g., its color). This commit extends the current situation by introducing two more step modes: "lstep" and "rstep", which do not require passing an extra x value. In turn, this modes associate each y value to either the left or the right boundary of the step. For example, the "rstep" mode is handy when plotting "life" digital signals in which x,y data pairs are appended as they are read. This commit does not modify the behaviour in case of stepMode=True * Replace step mode names: lstep,rstep -> left,right * Improve docs for stepMode Reword docstring and add it to PlotDataItem class too * Document left and right stepModes as added in v 0.12.0 TODO: confirm the exact version number to use here * Add comments stress the need for "is True" Some conditional statements in the code regarding stepMode are done with "is True". This is actually required since other possible values such as "left" also evaluate as true but should not be caught. * Deprecate boolean API for stepMode Introduce stepMode="mid" as a replacement of stepMode=True, but keeping full backwards compatibility with the old API. Adapt docs, examples and tests accordingly. * Raise ValueError on unsupported stepMode values * Rename "mid" step mode to "center" * Remove "added in 0.12.0" note See https://github.com/pyqtgraph/pyqtgraph/pull/1360#discussion_r502746919 * Add deprecation warning when stepMode=True Issue a DeprecationWarning if stepMode=True is being passed to the constructor or setData() of PlotDataItem or PlotCurveItem. Note: warnings module is imported locally so that it is esier to remove once this check is no longer needed. * Fix wrong syntax in last commit Fix usage of "default" kwarg in dict.get()
This commit is contained in:
parent
325a15a6ef
commit
23a46b5fb9
@ -20,9 +20,9 @@ vals = np.hstack([np.random.normal(size=500), np.random.normal(size=260, loc=4)]
|
|||||||
## compute standard histogram
|
## compute standard histogram
|
||||||
y,x = np.histogram(vals, bins=np.linspace(-3, 8, 40))
|
y,x = np.histogram(vals, bins=np.linspace(-3, 8, 40))
|
||||||
|
|
||||||
## Using stepMode=True causes the plot to draw two lines for each sample.
|
## Using stepMode="center" causes the plot to draw two lines for each sample.
|
||||||
## notice that len(x) == len(y)+1
|
## notice that len(x) == len(y)+1
|
||||||
plt1.plot(x, y, stepMode=True, fillLevel=0, fillOutline=True, brush=(0,0,255,150))
|
plt1.plot(x, y, stepMode="center", fillLevel=0, fillOutline=True, brush=(0,0,255,150))
|
||||||
|
|
||||||
## Now draw all points as a nicely-spaced scatter plot
|
## Now draw all points as a nicely-spaced scatter plot
|
||||||
y = pg.pseudoScatter(vals, spacing=0.15)
|
y = pg.pseudoScatter(vals, spacing=0.15)
|
||||||
|
@ -28,7 +28,7 @@ def test_CSVExporter():
|
|||||||
|
|
||||||
y3 = [1,5,2,3,4,6,1,2,4,2,3,5,3]
|
y3 = [1,5,2,3,4,6,1,2,4,2,3,5,3]
|
||||||
x3 = pg.np.linspace(0, 1.0, len(y3)+1)
|
x3 = pg.np.linspace(0, 1.0, len(y3)+1)
|
||||||
plt.plot(x=x3, y=y3, stepMode=True)
|
plt.plot(x=x3, y=y3, stepMode="center")
|
||||||
|
|
||||||
ex = pg.exporters.CSVExporter(plt.plotItem)
|
ex = pg.exporters.CSVExporter(plt.plotItem)
|
||||||
ex.export(fileName=tempfilename)
|
ex.export(fileName=tempfilename)
|
||||||
|
@ -62,7 +62,7 @@ class PlotCurveItem(GraphicsObject):
|
|||||||
'fillLevel': None,
|
'fillLevel': None,
|
||||||
'fillOutline': False,
|
'fillOutline': False,
|
||||||
'brush': None,
|
'brush': None,
|
||||||
'stepMode': False,
|
'stepMode': None,
|
||||||
'name': None,
|
'name': None,
|
||||||
'antialias': getConfigOption('antialias'),
|
'antialias': getConfigOption('antialias'),
|
||||||
'connect': 'all',
|
'connect': 'all',
|
||||||
@ -315,9 +315,17 @@ class PlotCurveItem(GraphicsObject):
|
|||||||
by :func:`mkBrush <pyqtgraph.mkBrush>` is allowed.
|
by :func:`mkBrush <pyqtgraph.mkBrush>` is allowed.
|
||||||
antialias (bool) Whether to use antialiasing when drawing. This
|
antialias (bool) Whether to use antialiasing when drawing. This
|
||||||
is disabled by default because it decreases performance.
|
is disabled by default because it decreases performance.
|
||||||
stepMode If True, two orthogonal lines are drawn for each sample
|
stepMode (str or None) If "center", a step is drawn using the x
|
||||||
as steps. This is commonly used when drawing histograms.
|
values as boundaries and the given y values are
|
||||||
Note that in this case, len(x) == len(y) + 1
|
associated to the mid-points between the boundaries of
|
||||||
|
each step. This is commonly used when drawing
|
||||||
|
histograms. Note that in this case, len(x) == len(y) + 1
|
||||||
|
If "left" or "right", the step is drawn assuming that
|
||||||
|
the y value is associated to the left or right boundary,
|
||||||
|
respectively. In this case len(x) == len(y)
|
||||||
|
If not passed or an empty string or None is passed, the
|
||||||
|
step mode is not enabled.
|
||||||
|
Passing True is a deprecated equivalent to "center".
|
||||||
connect Argument specifying how vertexes should be connected
|
connect Argument specifying how vertexes should be connected
|
||||||
by line segments. Default is "all", indicating full
|
by line segments. Default is "all", indicating full
|
||||||
connection. "pairs" causes only even-numbered segments
|
connection. "pairs" causes only even-numbered segments
|
||||||
@ -379,7 +387,10 @@ class PlotCurveItem(GraphicsObject):
|
|||||||
if 'stepMode' in kargs:
|
if 'stepMode' in kargs:
|
||||||
self.opts['stepMode'] = kargs['stepMode']
|
self.opts['stepMode'] = kargs['stepMode']
|
||||||
|
|
||||||
if self.opts['stepMode'] is True:
|
if self.opts['stepMode'] in ("center", True): ## check against True for backwards compatibility
|
||||||
|
if self.opts['stepMode'] is True:
|
||||||
|
import warnings
|
||||||
|
warnings.warn('stepMode=True is deprecated, use stepMode="center" instead', DeprecationWarning, stacklevel=3)
|
||||||
if len(self.xData) != len(self.yData)+1: ## allow difference of 1 for step mode plots
|
if len(self.xData) != len(self.yData)+1: ## allow difference of 1 for step mode plots
|
||||||
raise Exception("len(X) must be len(Y)+1 since stepMode=True (got %s and %s)" % (self.xData.shape, self.yData.shape))
|
raise Exception("len(X) must be len(Y)+1 since stepMode=True (got %s and %s)" % (self.xData.shape, self.yData.shape))
|
||||||
else:
|
else:
|
||||||
@ -413,10 +424,22 @@ class PlotCurveItem(GraphicsObject):
|
|||||||
profiler('emit')
|
profiler('emit')
|
||||||
|
|
||||||
def generatePath(self, x, y):
|
def generatePath(self, x, y):
|
||||||
if self.opts['stepMode']:
|
stepMode = self.opts['stepMode']
|
||||||
|
if stepMode:
|
||||||
## each value in the x/y arrays generates 2 points.
|
## each value in the x/y arrays generates 2 points.
|
||||||
x2 = np.empty((len(x),2), dtype=x.dtype)
|
if stepMode == "right":
|
||||||
x2[:] = x[:,np.newaxis]
|
x2 = np.empty((len(x) + 1, 2), dtype=x.dtype)
|
||||||
|
x2[:-1] = x[:, np.newaxis]
|
||||||
|
x2[-1] = x2[-2]
|
||||||
|
elif stepMode == "left":
|
||||||
|
x2 = np.empty((len(x) + 1, 2), dtype=x.dtype)
|
||||||
|
x2[1:] = x[:, np.newaxis]
|
||||||
|
x2[0] = x2[1]
|
||||||
|
elif stepMode in ("center", True): ## support True for back-compat
|
||||||
|
x2 = np.empty((len(x),2), dtype=x.dtype)
|
||||||
|
x2[:] = x[:, np.newaxis]
|
||||||
|
else:
|
||||||
|
raise ValueError("Unsupported stepMode %s" % stepMode)
|
||||||
if self.opts['fillLevel'] is None:
|
if self.opts['fillLevel'] is None:
|
||||||
x = x2.reshape(x2.size)[1:-1]
|
x = x2.reshape(x2.size)[1:-1]
|
||||||
y2 = np.empty((len(y),2), dtype=y.dtype)
|
y2 = np.empty((len(y),2), dtype=y.dtype)
|
||||||
|
@ -76,9 +76,17 @@ class PlotDataItem(GraphicsObject):
|
|||||||
fillOutline (bool) If True, an outline surrounding the *fillLevel* area is drawn.
|
fillOutline (bool) If True, an outline surrounding the *fillLevel* area is drawn.
|
||||||
fillBrush Fill to use when fillLevel is specified.
|
fillBrush Fill to use when fillLevel is specified.
|
||||||
May be any single argument accepted by :func:`mkBrush() <pyqtgraph.mkBrush>`
|
May be any single argument accepted by :func:`mkBrush() <pyqtgraph.mkBrush>`
|
||||||
stepMode If True, two orthogonal lines are drawn for each sample
|
stepMode (str or None) If "center", a step is drawn using the x
|
||||||
as steps. This is commonly used when drawing histograms.
|
values as boundaries and the given y values are
|
||||||
Note that in this case, ``len(x) == len(y) + 1``
|
associated to the mid-points between the boundaries of
|
||||||
|
each step. This is commonly used when drawing
|
||||||
|
histograms. Note that in this case, len(x) == len(y) + 1
|
||||||
|
If "left" or "right", the step is drawn assuming that
|
||||||
|
the y value is associated to the left or right boundary,
|
||||||
|
respectively. In this case len(x) == len(y)
|
||||||
|
If not passed or an empty string or None is passed, the
|
||||||
|
step mode is not enabled.
|
||||||
|
Passing True is a deprecated equivalent to "center".
|
||||||
(added in version 0.9.9)
|
(added in version 0.9.9)
|
||||||
============ ==============================================================================
|
============ ==============================================================================
|
||||||
|
|
||||||
@ -376,6 +384,12 @@ class PlotDataItem(GraphicsObject):
|
|||||||
See :func:`__init__() <pyqtgraph.PlotDataItem.__init__>` for details; it accepts the same arguments.
|
See :func:`__init__() <pyqtgraph.PlotDataItem.__init__>` for details; it accepts the same arguments.
|
||||||
"""
|
"""
|
||||||
#self.clear()
|
#self.clear()
|
||||||
|
if kargs.get("stepMode", None) is True:
|
||||||
|
import warnings
|
||||||
|
warnings.warn(
|
||||||
|
'stepMode=True is deprecated, use stepMode="center" instead',
|
||||||
|
DeprecationWarning, stacklevel=3
|
||||||
|
)
|
||||||
profiler = debug.Profiler()
|
profiler = debug.Profiler()
|
||||||
y = None
|
y = None
|
||||||
x = None
|
x = None
|
||||||
@ -520,8 +534,8 @@ class PlotDataItem(GraphicsObject):
|
|||||||
self.curve.hide()
|
self.curve.hide()
|
||||||
|
|
||||||
if scatterArgs['symbol'] is not None:
|
if scatterArgs['symbol'] is not None:
|
||||||
|
## check against `True` too for backwards compatibility
|
||||||
if self.opts.get('stepMode', False) is True:
|
if self.opts.get('stepMode', False) in ("center", True):
|
||||||
x = 0.5 * (x[:-1] + x[1:])
|
x = 0.5 * (x[:-1] + x[1:])
|
||||||
self.scatter.setData(x=x, y=y, **scatterArgs)
|
self.scatter.setData(x=x, y=y, **scatterArgs)
|
||||||
self.scatter.show()
|
self.scatter.show()
|
||||||
|
@ -61,7 +61,7 @@ def test_clear():
|
|||||||
|
|
||||||
def test_clear_in_step_mode():
|
def test_clear_in_step_mode():
|
||||||
w = pg.PlotWidget()
|
w = pg.PlotWidget()
|
||||||
c = pg.PlotDataItem([1,4,2,3], [5,7,6], stepMode=True)
|
c = pg.PlotDataItem([1,4,2,3], [5,7,6], stepMode="center")
|
||||||
w.addItem(c)
|
w.addItem(c)
|
||||||
c.clear()
|
c.clear()
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user