Traditional log10 mode for PlotDataItem (by adding "mapped data" stage) (#1992)

* make PlotDataItem aware of mapped data

* inf suppression, metadata storage and refactor of data structures

* cleanup, test, and documentation pass

* re-added prepareForPaint, added PlotDataset to sphinx index

* strip more print statements

* indicate (internal) PlotDataset documentation as orphaned to avoid sphinx error

* Do not export PlotDataset

* replacement example

* example comments
This commit is contained in:
Nils Nemitz 2021-10-07 14:40:38 +09:00 committed by GitHub
parent bc542ae1c4
commit 0cc3580687
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 504 additions and 427 deletions

View File

@ -1,38 +1,44 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Test programmatically setting log transformation modes. Demonstrate programmatic setting of log transformation modes.
""" """
import initExample ## Add path to library (just for examples; you do not need this) import initExample ## Add path to library (just for examples; you do not need this)
import numpy as np import numpy as np
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg import pyqtgraph as pg
app = pg.mkQApp("Log Axis Example") app = pg.mkQApp("Log Axis Example")
w = pg.GraphicsLayoutWidget(show=True) w = pg.GraphicsLayoutWidget(show=True)
w.setWindowTitle('pyqtgraph example: logAxis') w.resize(800,800)
p1 = w.addPlot(0,0, title="X Semilog") w.setWindowTitle('pyqtgraph example: Log Axis, or How to Recognise Different Types of Curves from Quite a Long Way Away')
p0 = w.addPlot(0,0, title="Linear")
p1 = w.addPlot(0,1, title="X Semilog")
p2 = w.addPlot(1,0, title="Y Semilog") p2 = w.addPlot(1,0, title="Y Semilog")
p3 = w.addPlot(2,0, title="XY Log") p3 = w.addPlot(1,1, title="XY Log")
p1.showGrid(True, True) # configure logarithmic axis scaling:
p2.showGrid(True, True)
p3.showGrid(True, True)
p1.setLogMode(True, False) p1.setLogMode(True, False)
p2.setLogMode(False, True) p2.setLogMode(False, True)
p3.setLogMode(True, True) p3.setLogMode(True, True)
# 1000 points from 0.1 to 10, chosen to give a compatible range of values across curves:
x = np.logspace(-1, 1, 1000)
plotdata = ( # legend entry, color, and plotted equation:
('1 / 3x' , '#ff9d47', 1./(3*x) ),
('sqrt x' , '#b3cf00', 1/np.sqrt(x) ),
('exp. decay', '#00a0b5', 5 * np.exp(-x/1) ),
('-log x' , '#a54dff', - np.log10(x) )
)
p0.addLegend(offset=(-20,20)) # include legend only in top left plot
for p in (p0, p1, p2, p3): # draw identical numerical data in all four plots
p.showGrid(True, True) # turn on grid for all four plots
p.showAxes(True, size=(40,None)) # show a full frame, and reserve identical room for y labels
for name, color, y in plotdata: # draw all four curves as defined in plotdata
pen = pg.mkPen(color, width=2)
p.plot( x,y, pen=pen, name=name )
w.show() w.show()
y = np.random.normal(size=1000)
x = np.linspace(0, 1, 1000)
p1.plot(x, y)
p2.plot(x, y)
p3.plot(x, y)
#p.getAxis('bottom').setLogMode(True)
if __name__ == '__main__': if __name__ == '__main__':
pg.exec() pg.exec()

View File

@ -1123,7 +1123,10 @@ def transformCoordinates(tr, coords, transpose=False):
m = m[:, :-1] m = m[:, :-1]
## map coordinates and return ## map coordinates and return
mapped = (m*coords).sum(axis=1) ## apply scale/rotate # nan or inf points will not plot, but should not generate warnings
with warnings.catch_warnings():
warnings.simplefilter("ignore", RuntimeWarning)
mapped = (m*coords).sum(axis=1) ## apply scale/rotate
mapped += translate mapped += translate
if transpose: if transpose:
@ -2042,7 +2045,7 @@ def arrayToQPath(x, y, connect='all', finiteCheck=True):
only values with 1 will connect to the previous point. Def only values with 1 will connect to the previous point. Def
finiteCheck : bool, default Ture finiteCheck : bool, default Ture
When false, the check for finite values will be skipped, which can When false, the check for finite values will be skipped, which can
improve performance. If finite values are present in `x` or `y`, improve performance. If nonfinite values are present in `x` or `y`,
an empty QPainterPath will be generated. an empty QPainterPath will be generated.
Returns Returns

File diff suppressed because it is too large Load Diff

View File

@ -1468,6 +1468,10 @@ class ViewBox(GraphicsWidget):
bounds = QtCore.QRectF(range[0][0], range[1][0], range[0][1]-range[0][0], range[1][1]-range[1][0]) bounds = QtCore.QRectF(range[0][0], range[1][0], range[0][1]-range[0][0], range[1][1]-range[1][0])
return bounds return bounds
def update(self, *args, **kwargs):
self.prepareForPaint()
GraphicsWidget.update(self, *args, **kwargs)
def updateViewRange(self, forceX=False, forceY=False): def updateViewRange(self, forceX=False, forceY=False):
## Update viewRange to match targetRange as closely as possible, given ## Update viewRange to match targetRange as closely as possible, given

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import numpy as np import numpy as np
import warnings
import pyqtgraph as pg import pyqtgraph as pg
from pyqtgraph.Qt import QtGui from pyqtgraph.Qt import QtGui
@ -71,6 +72,31 @@ def test_setData():
pdi.setData([],[]) pdi.setData([],[])
assert pdi.xData is None assert pdi.xData is None
assert pdi.yData is None assert pdi.yData is None
def test_nonfinite():
def _assert_equal_arrays(a1, a2):
assert a1.shape == a2.shape
for ( xtest, xgood ) in zip( a1, a2 ):
assert( (xtest == xgood) or (np.isnan(xtest) and np.isnan(xgood) ) )
x = np.array([-np.inf, 0.0, 1.0, 2.0 , np.nan, 4.0 , np.inf])
y = np.array([ 1.0, 0.0,-1.0, np.inf, 2.0 , np.nan, 0.0 ])
pdi = pg.PlotDataItem(x, y)
dataset = pdi.getDisplayDataset()
_assert_equal_arrays( dataset.x, x )
_assert_equal_arrays( dataset.y, y )
with warnings.catch_warnings():
warnings.simplefilter("ignore")
x_log = np.log10(x)
y_log = np.log10(y)
x_log[ ~np.isfinite(x_log) ] = np.nan
y_log[ ~np.isfinite(y_log) ] = np.nan
pdi.setLogMode(True, True)
dataset = pdi.getDisplayDataset()
_assert_equal_arrays( dataset.x, x_log )
_assert_equal_arrays( dataset.y, y_log )
def test_opts(): def test_opts():
# test that curve and scatter plot properties get updated from PlotDataItem methods # test that curve and scatter plot properties get updated from PlotDataItem methods