Merge branch 'develop' into pyside2
This commit is contained in:
commit
66dcfc7c67
@ -7,6 +7,9 @@ pyqtgraph-0.11.0 (in development)
|
|||||||
To mimic the old behavior, use ArrowItem.rotate() instead of the `angle` argument.
|
To mimic the old behavior, use ArrowItem.rotate() instead of the `angle` argument.
|
||||||
- Deprecated graphicsWindow classes; these have been unnecessary for many years because
|
- Deprecated graphicsWindow classes; these have been unnecessary for many years because
|
||||||
widgets can be placed into a new window just by calling show().
|
widgets can be placed into a new window just by calling show().
|
||||||
|
- Integer values in ParameterTree are now formatted as integer (%d) by default, rather than
|
||||||
|
scientific notation (%g). This can be overridden by providing `format={value:g}` when
|
||||||
|
creating the parameter.
|
||||||
|
|
||||||
|
|
||||||
pyqtgraph-0.10.0
|
pyqtgraph-0.10.0
|
||||||
|
60
README.md
60
README.md
@ -6,46 +6,21 @@ PyQtGraph
|
|||||||
|
|
||||||
A pure-Python graphics library for PyQt/PySide
|
A pure-Python graphics library for PyQt/PySide
|
||||||
|
|
||||||
Copyright 2012 Luke Campagnola, University of North Carolina at Chapel Hill
|
Copyright 2017 Luke Campagnola, University of North Carolina at Chapel Hill
|
||||||
|
|
||||||
<http://www.pyqtgraph.org>
|
<http://www.pyqtgraph.org>
|
||||||
|
|
||||||
Maintainer
|
PyQtGraph is intended for use in mathematics / scientific / engineering applications.
|
||||||
----------
|
Despite being written entirely in python, the library is fast due to its
|
||||||
|
heavy leverage of numpy for number crunching, Qt's GraphicsView framework for
|
||||||
|
2D display, and OpenGL for 3D display.
|
||||||
|
|
||||||
* Luke Campagnola <luke.campagnola@gmail.com>
|
|
||||||
|
|
||||||
Contributors
|
|
||||||
------------
|
|
||||||
|
|
||||||
* Megan Kratz
|
|
||||||
* Paul Manis
|
|
||||||
* Ingo Breßler
|
|
||||||
* Christian Gavin
|
|
||||||
* Michael Cristopher Hogg
|
|
||||||
* Ulrich Leutner
|
|
||||||
* Felix Schill
|
|
||||||
* Guillaume Poulin
|
|
||||||
* Antony Lee
|
|
||||||
* Mattias Põldaru
|
|
||||||
* Thomas S.
|
|
||||||
* Fabio Zadrozny
|
|
||||||
* Mikhail Terekhov
|
|
||||||
* Pietro Zambelli
|
|
||||||
* Stefan Holzmann
|
|
||||||
* Nicholas TJ
|
|
||||||
* John David Reaver
|
|
||||||
* David Kaplan
|
|
||||||
* Martin Fitzpatrick
|
|
||||||
* Daniel Lidstrom
|
|
||||||
* Eric Dill
|
|
||||||
* Vincent LeSaux
|
|
||||||
|
|
||||||
Requirements
|
Requirements
|
||||||
------------
|
------------
|
||||||
|
|
||||||
* PyQt 4.7+, PySide, or PyQt5
|
* PyQt 4.7+, PySide, or PyQt5
|
||||||
* python 2.6, 2.7, or 3.x
|
* python 2.7, or 3.x
|
||||||
* NumPy
|
* NumPy
|
||||||
* For 3D graphics: pyopengl and qt-opengl
|
* For 3D graphics: pyopengl and qt-opengl
|
||||||
* Known to run on Windows, Linux, and Mac.
|
* Known to run on Windows, Linux, and Mac.
|
||||||
@ -53,33 +28,24 @@ Requirements
|
|||||||
Support
|
Support
|
||||||
-------
|
-------
|
||||||
|
|
||||||
Post at the [mailing list / forum](https://groups.google.com/forum/?fromgroups#!forum/pyqtgraph)
|
* Report issues on the [GitHub issue tracker](https://github.com/pyqtgraph/pyqtgraph/issues)
|
||||||
|
* Post questions to the [mailing list / forum](https://groups.google.com/forum/?fromgroups#!forum/pyqtgraph) or [StackOverflow](https://stackoverflow.com/questions/tagged/pyqtgraph)
|
||||||
|
|
||||||
Installation Methods
|
Installation Methods
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
* From pypi:
|
||||||
|
`pip install pyqtgraph`
|
||||||
* To use with a specific project, simply copy the pyqtgraph subdirectory
|
* To use with a specific project, simply copy the pyqtgraph subdirectory
|
||||||
anywhere that is importable from your project. PyQtGraph may also be
|
anywhere that is importable from your project.
|
||||||
used as a git subtree by cloning the git-core repository from github.
|
|
||||||
* To install system-wide from source distribution:
|
* To install system-wide from source distribution:
|
||||||
`$ python setup.py install`
|
`$ python setup.py install`
|
||||||
* For installation packages, see the website (pyqtgraph.org)
|
* For installation packages, see the website (pyqtgraph.org)
|
||||||
* On debian-like systems, pyqtgraph requires the following packages:
|
|
||||||
python-numpy, python-qt4 | python-pyside
|
|
||||||
For 3D support: python-opengl, python-qt4-gl | python-pyside.qtopengl
|
|
||||||
|
|
||||||
Documentation
|
Documentation
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
There are many examples; run `python -m pyqtgraph.examples` for a menu.
|
The easiest way to learn pyqtgraph is to browse through the examples; run `python -m pyqtgraph.examples` for a menu.
|
||||||
|
|
||||||
Some (incomplete) documentation exists at this time.
|
The official documentation lives at http://pyqtgraph.org/documentation
|
||||||
* Easiest place to get documentation is at <http://www.pyqtgraph.org/documentation>
|
|
||||||
* If you acquired this code as a .tar.gz file from the website, then you can also look in
|
|
||||||
doc/html.
|
|
||||||
* If you acquired this code via GitHub, then you can build the documentation using sphinx.
|
|
||||||
From the documentation directory, run:
|
|
||||||
`$ make html`
|
|
||||||
|
|
||||||
Please feel free to pester Luke or post to the forum if you need a specific
|
|
||||||
section of documentation to be expanded.
|
|
||||||
|
@ -12,7 +12,7 @@ import numpy as np
|
|||||||
# Enable antialiasing for prettier plots
|
# Enable antialiasing for prettier plots
|
||||||
pg.setConfigOptions(antialias=True)
|
pg.setConfigOptions(antialias=True)
|
||||||
|
|
||||||
w = pg.GraphicsWindow()
|
w = pg.GraphicsLayoutWidget(show=True)
|
||||||
w.setWindowTitle('pyqtgraph example: CustomGraphItem')
|
w.setWindowTitle('pyqtgraph example: CustomGraphItem')
|
||||||
v = w.addViewBox()
|
v = w.addViewBox()
|
||||||
v.setAspectLocked()
|
v.setAspectLocked()
|
||||||
|
@ -11,15 +11,29 @@ from pyqtgraph.Qt import QtCore, QtGui
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
# for generating a traceback object to display
|
||||||
|
def some_func1():
|
||||||
|
return some_func2()
|
||||||
|
def some_func2():
|
||||||
|
try:
|
||||||
|
raise Exception()
|
||||||
|
except:
|
||||||
|
import sys
|
||||||
|
return sys.exc_info()[2]
|
||||||
|
|
||||||
|
|
||||||
app = QtGui.QApplication([])
|
app = QtGui.QApplication([])
|
||||||
d = {
|
d = {
|
||||||
'list1': [1,2,3,4,5,6, {'nested1': 'aaaaa', 'nested2': 'bbbbb'}, "seven"],
|
'a list': [1,2,3,4,5,6, {'nested1': 'aaaaa', 'nested2': 'bbbbb'}, "seven"],
|
||||||
'dict1': {
|
'a dict': {
|
||||||
'x': 1,
|
'x': 1,
|
||||||
'y': 2,
|
'y': 2,
|
||||||
'z': 'three'
|
'z': 'three'
|
||||||
},
|
},
|
||||||
'array1 (20x20)': np.ones((10,10))
|
'an array': np.random.randint(10, size=(40,10)),
|
||||||
|
'a traceback': some_func1(),
|
||||||
|
'a function': some_func1,
|
||||||
|
'a class': pg.DataTreeWidget,
|
||||||
}
|
}
|
||||||
|
|
||||||
tree = pg.DataTreeWidget(data=d)
|
tree = pg.DataTreeWidget(data=d)
|
||||||
|
52
examples/DiffTreeWidget.py
Normal file
52
examples/DiffTreeWidget.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Simple use of DiffTreeWidget to display differences between structures of
|
||||||
|
nested dicts, lists, and arrays.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import initExample ## Add path to library (just for examples; you do not need this)
|
||||||
|
|
||||||
|
import pyqtgraph as pg
|
||||||
|
from pyqtgraph.Qt import QtCore, QtGui
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
app = QtGui.QApplication([])
|
||||||
|
A = {
|
||||||
|
'a list': [1,2,2,4,5,6, {'nested1': 'aaaa', 'nested2': 'bbbbb'}, "seven"],
|
||||||
|
'a dict': {
|
||||||
|
'x': 1,
|
||||||
|
'y': 2,
|
||||||
|
'z': 'three'
|
||||||
|
},
|
||||||
|
'an array': np.random.randint(10, size=(40,10)),
|
||||||
|
#'a traceback': some_func1(),
|
||||||
|
#'a function': some_func1,
|
||||||
|
#'a class': pg.DataTreeWidget,
|
||||||
|
}
|
||||||
|
|
||||||
|
B = {
|
||||||
|
'a list': [1,2,3,4,5,5, {'nested1': 'aaaaa', 'nested2': 'bbbbb'}, "seven"],
|
||||||
|
'a dict': {
|
||||||
|
'x': 2,
|
||||||
|
'y': 2,
|
||||||
|
'z': 'three',
|
||||||
|
'w': 5
|
||||||
|
},
|
||||||
|
'another dict': {1:2, 2:3, 3:4},
|
||||||
|
'an array': np.random.randint(10, size=(40,10)),
|
||||||
|
}
|
||||||
|
|
||||||
|
tree = pg.DiffTreeWidget()
|
||||||
|
tree.setData(A, B)
|
||||||
|
tree.show()
|
||||||
|
tree.setWindowTitle('pyqtgraph example: DiffTreeWidget')
|
||||||
|
tree.resize(1000, 800)
|
||||||
|
|
||||||
|
|
||||||
|
## Start Qt event loop unless running in interactive mode or using pyside.
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import sys
|
||||||
|
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
|
||||||
|
QtGui.QApplication.instance().exec_()
|
@ -26,9 +26,9 @@ data += pg.gaussianFilter(np.random.normal(size=shape), (15,15,15))*15
|
|||||||
|
|
||||||
## slice out three planes, convert to RGBA for OpenGL texture
|
## slice out three planes, convert to RGBA for OpenGL texture
|
||||||
levels = (-0.08, 0.08)
|
levels = (-0.08, 0.08)
|
||||||
tex1 = pg.makeRGBA(data[shape[0]/2], levels=levels)[0] # yz plane
|
tex1 = pg.makeRGBA(data[shape[0]//2], levels=levels)[0] # yz plane
|
||||||
tex2 = pg.makeRGBA(data[:,shape[1]/2], levels=levels)[0] # xz plane
|
tex2 = pg.makeRGBA(data[:,shape[1]//2], levels=levels)[0] # xz plane
|
||||||
tex3 = pg.makeRGBA(data[:,:,shape[2]/2], levels=levels)[0] # xy plane
|
tex3 = pg.makeRGBA(data[:,:,shape[2]//2], levels=levels)[0] # xy plane
|
||||||
#tex1[:,:,3] = 128
|
#tex1[:,:,3] = 128
|
||||||
#tex2[:,:,3] = 128
|
#tex2[:,:,3] = 128
|
||||||
#tex3[:,:,3] = 128
|
#tex3[:,:,3] = 128
|
||||||
|
@ -13,7 +13,7 @@ import numpy as np
|
|||||||
# Enable antialiasing for prettier plots
|
# Enable antialiasing for prettier plots
|
||||||
pg.setConfigOptions(antialias=True)
|
pg.setConfigOptions(antialias=True)
|
||||||
|
|
||||||
w = pg.GraphicsWindow()
|
w = pg.GraphicsLayoutWidget(show=True)
|
||||||
w.setWindowTitle('pyqtgraph example: GraphItem')
|
w.setWindowTitle('pyqtgraph example: GraphItem')
|
||||||
v = w.addViewBox()
|
v = w.addViewBox()
|
||||||
v.setAspectLocked()
|
v.setAspectLocked()
|
||||||
|
@ -10,7 +10,7 @@ import pyqtgraph as pg
|
|||||||
|
|
||||||
|
|
||||||
app = QtGui.QApplication([])
|
app = QtGui.QApplication([])
|
||||||
win = pg.GraphicsWindow(title="Plotting items examples")
|
win = pg.GraphicsLayoutWidget(show=True, title="Plotting items examples")
|
||||||
win.resize(1000,600)
|
win.resize(1000,600)
|
||||||
|
|
||||||
# Enable antialiasing for prettier plots
|
# Enable antialiasing for prettier plots
|
||||||
|
@ -12,7 +12,7 @@ import pyqtgraph as pg
|
|||||||
|
|
||||||
app = QtGui.QApplication([])
|
app = QtGui.QApplication([])
|
||||||
|
|
||||||
win = pg.GraphicsWindow(title="Basic plotting examples")
|
win = pg.GraphicsLayoutWidget(show=True, title="Basic plotting examples")
|
||||||
win.resize(1000,600)
|
win.resize(1000,600)
|
||||||
win.setWindowTitle('pyqtgraph example: LogPlotTest')
|
win.setWindowTitle('pyqtgraph example: LogPlotTest')
|
||||||
|
|
||||||
|
@ -12,32 +12,27 @@ from pyqtgraph.Qt import QtGui, QtCore
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import pyqtgraph as pg
|
import pyqtgraph as pg
|
||||||
from pyqtgraph.ptime import time
|
from pyqtgraph.ptime import time
|
||||||
#QtGui.QApplication.setGraphicsSystem('raster')
|
|
||||||
app = QtGui.QApplication([])
|
app = QtGui.QApplication([])
|
||||||
#mw = QtGui.QMainWindow()
|
|
||||||
#mw.resize(800,800)
|
|
||||||
|
|
||||||
p = pg.plot()
|
plot = pg.plot()
|
||||||
p.setWindowTitle('pyqtgraph example: MultiPlotSpeedTest')
|
plot.setWindowTitle('pyqtgraph example: MultiPlotSpeedTest')
|
||||||
#p.setRange(QtCore.QRectF(0, -10, 5000, 20))
|
plot.setLabel('bottom', 'Index', units='B')
|
||||||
p.setLabel('bottom', 'Index', units='B')
|
|
||||||
|
|
||||||
nPlots = 100
|
nPlots = 100
|
||||||
nSamples = 500
|
nSamples = 500
|
||||||
#curves = [p.plot(pen=(i,nPlots*1.3)) for i in range(nPlots)]
|
|
||||||
curves = []
|
curves = []
|
||||||
for i in range(nPlots):
|
for idx in range(nPlots):
|
||||||
c = pg.PlotCurveItem(pen=(i,nPlots*1.3))
|
curve = pg.PlotCurveItem(pen=(idx,nPlots*1.3))
|
||||||
p.addItem(c)
|
plot.addItem(curve)
|
||||||
c.setPos(0,i*6)
|
curve.setPos(0,idx*6)
|
||||||
curves.append(c)
|
curves.append(curve)
|
||||||
|
|
||||||
p.setYRange(0, nPlots*6)
|
plot.setYRange(0, nPlots*6)
|
||||||
p.setXRange(0, nSamples)
|
plot.setXRange(0, nSamples)
|
||||||
p.resize(600,900)
|
plot.resize(600,900)
|
||||||
|
|
||||||
rgn = pg.LinearRegionItem([nSamples/5.,nSamples/3.])
|
rgn = pg.LinearRegionItem([nSamples/5.,nSamples/3.])
|
||||||
p.addItem(rgn)
|
plot.addItem(rgn)
|
||||||
|
|
||||||
|
|
||||||
data = np.random.normal(size=(nPlots*23,nSamples))
|
data = np.random.normal(size=(nPlots*23,nSamples))
|
||||||
@ -46,13 +41,12 @@ lastTime = time()
|
|||||||
fps = None
|
fps = None
|
||||||
count = 0
|
count = 0
|
||||||
def update():
|
def update():
|
||||||
global curve, data, ptr, p, lastTime, fps, nPlots, count
|
global curve, data, ptr, plot, lastTime, fps, nPlots, count
|
||||||
count += 1
|
count += 1
|
||||||
#print "---------", count
|
|
||||||
for i in range(nPlots):
|
for i in range(nPlots):
|
||||||
curves[i].setData(data[(ptr+i)%data.shape[0]])
|
curves[i].setData(data[(ptr+i)%data.shape[0]])
|
||||||
|
|
||||||
#print " setData done."
|
|
||||||
ptr += nPlots
|
ptr += nPlots
|
||||||
now = time()
|
now = time()
|
||||||
dt = now - lastTime
|
dt = now - lastTime
|
||||||
@ -62,14 +56,12 @@ def update():
|
|||||||
else:
|
else:
|
||||||
s = np.clip(dt*3., 0, 1)
|
s = np.clip(dt*3., 0, 1)
|
||||||
fps = fps * (1-s) + (1.0/dt) * s
|
fps = fps * (1-s) + (1.0/dt) * s
|
||||||
p.setTitle('%0.2f fps' % fps)
|
plot.setTitle('%0.2f fps' % fps)
|
||||||
#app.processEvents() ## force complete redraw for every plot
|
#app.processEvents() ## force complete redraw for every plot
|
||||||
timer = QtCore.QTimer()
|
timer = QtCore.QTimer()
|
||||||
timer.timeout.connect(update)
|
timer.timeout.connect(update)
|
||||||
timer.start(0)
|
timer.start(0)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Start Qt event loop unless running in interactive mode.
|
## Start Qt event loop unless running in interactive mode.
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import sys
|
import sys
|
||||||
|
@ -9,7 +9,7 @@ import pyqtgraph as pg
|
|||||||
from pyqtgraph.Qt import QtCore, QtGui
|
from pyqtgraph.Qt import QtCore, QtGui
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
win = pg.GraphicsWindow()
|
win = pg.GraphicsLayoutWidget(show=True)
|
||||||
win.setWindowTitle('pyqtgraph example: PanningPlot')
|
win.setWindowTitle('pyqtgraph example: PanningPlot')
|
||||||
|
|
||||||
plt = win.addPlot()
|
plt = win.addPlot()
|
||||||
|
@ -16,7 +16,7 @@ app = QtGui.QApplication([])
|
|||||||
#mw = QtGui.QMainWindow()
|
#mw = QtGui.QMainWindow()
|
||||||
#mw.resize(800,800)
|
#mw.resize(800,800)
|
||||||
|
|
||||||
win = pg.GraphicsWindow(title="Plot auto-range examples")
|
win = pg.GraphicsLayoutWidget(show=True, title="Plot auto-range examples")
|
||||||
win.resize(800,600)
|
win.resize(800,600)
|
||||||
win.setWindowTitle('pyqtgraph example: PlotAutoRange')
|
win.setWindowTitle('pyqtgraph example: PlotAutoRange')
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ app = QtGui.QApplication([])
|
|||||||
#mw = QtGui.QMainWindow()
|
#mw = QtGui.QMainWindow()
|
||||||
#mw.resize(800,800)
|
#mw.resize(800,800)
|
||||||
|
|
||||||
win = pg.GraphicsWindow(title="Basic plotting examples")
|
win = pg.GraphicsLayoutWidget(show=True, title="Basic plotting examples")
|
||||||
win.resize(1000,600)
|
win.resize(1000,600)
|
||||||
win.setWindowTitle('pyqtgraph example: Plotting')
|
win.setWindowTitle('pyqtgraph example: Plotting')
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ arr[8:13, 44:46] = 10
|
|||||||
|
|
||||||
## create GUI
|
## create GUI
|
||||||
app = QtGui.QApplication([])
|
app = QtGui.QApplication([])
|
||||||
w = pg.GraphicsWindow(size=(1000,800), border=True)
|
w = pg.GraphicsLayoutWidget(show=True, size=(1000,800), border=True)
|
||||||
w.setWindowTitle('pyqtgraph example: ROI Examples')
|
w.setWindowTitle('pyqtgraph example: ROI Examples')
|
||||||
|
|
||||||
text = """Data Selection From Image.<br>\n
|
text = """Data Selection From Image.<br>\n
|
||||||
|
@ -13,7 +13,7 @@ pg.setConfigOptions(imageAxisOrder='row-major')
|
|||||||
## create GUI
|
## create GUI
|
||||||
app = QtGui.QApplication([])
|
app = QtGui.QApplication([])
|
||||||
|
|
||||||
w = pg.GraphicsWindow(size=(800,800), border=True)
|
w = pg.GraphicsLayoutWidget(show=True, size=(800,800), border=True)
|
||||||
v = w.addViewBox(colspan=2)
|
v = w.addViewBox(colspan=2)
|
||||||
v.invertY(True) ## Images usually have their Y-axis pointing downward
|
v.invertY(True) ## Images usually have their Y-axis pointing downward
|
||||||
v.setAspectLocked(True)
|
v.setAspectLocked(True)
|
||||||
|
@ -9,7 +9,7 @@ from pyqtgraph.Qt import QtCore, QtGui
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
pg.mkQApp()
|
pg.mkQApp()
|
||||||
win = pg.GraphicsWindow()
|
win = pg.GraphicsLayoutWidget(show=True)
|
||||||
win.setWindowTitle('pyqtgraph example: ScaleBar')
|
win.setWindowTitle('pyqtgraph example: ScaleBar')
|
||||||
|
|
||||||
vb = win.addViewBox()
|
vb = win.addViewBox()
|
||||||
|
@ -28,7 +28,7 @@ pg.mkQApp()
|
|||||||
# Make up some tabular data with structure
|
# Make up some tabular data with structure
|
||||||
data = np.empty(1000, dtype=[('x_pos', float), ('y_pos', float),
|
data = np.empty(1000, dtype=[('x_pos', float), ('y_pos', float),
|
||||||
('count', int), ('amplitude', float),
|
('count', int), ('amplitude', float),
|
||||||
('decay', float), ('type', 'S10')])
|
('decay', float), ('type', 'U10')])
|
||||||
strings = ['Type-A', 'Type-B', 'Type-C', 'Type-D', 'Type-E']
|
strings = ['Type-A', 'Type-B', 'Type-C', 'Type-D', 'Type-E']
|
||||||
typeInds = np.random.randint(5, size=1000)
|
typeInds = np.random.randint(5, size=1000)
|
||||||
data['type'] = np.array(strings)[typeInds]
|
data['type'] = np.array(strings)[typeInds]
|
||||||
|
@ -11,7 +11,7 @@ from pyqtgraph.Qt import QtGui, QtCore
|
|||||||
import pyqtgraph as pg
|
import pyqtgraph as pg
|
||||||
|
|
||||||
app = QtGui.QApplication([])
|
app = QtGui.QApplication([])
|
||||||
win = pg.GraphicsWindow(title="Scatter Plot Symbols")
|
win = pg.GraphicsLayoutWidget(show=True, title="Scatter Plot Symbols")
|
||||||
win.resize(1000,600)
|
win.resize(1000,600)
|
||||||
|
|
||||||
pg.setConfigOptions(antialias=True)
|
pg.setConfigOptions(antialias=True)
|
||||||
|
@ -16,7 +16,7 @@ x = np.arange(1000, dtype=float)
|
|||||||
y = np.random.normal(size=1000)
|
y = np.random.normal(size=1000)
|
||||||
y += 5 * np.sin(x/100)
|
y += 5 * np.sin(x/100)
|
||||||
|
|
||||||
win = pg.GraphicsWindow()
|
win = pg.GraphicsLayoutWidget(show=True)
|
||||||
win.setWindowTitle('pyqtgraph example: ____')
|
win.setWindowTitle('pyqtgraph example: ____')
|
||||||
win.resize(1000, 800)
|
win.resize(1000, 800)
|
||||||
win.ci.setBorder((50, 50, 100))
|
win.ci.setBorder((50, 50, 100))
|
||||||
|
@ -28,6 +28,7 @@ class ExampleLoader(QtGui.QMainWindow):
|
|||||||
self.cw = QtGui.QWidget()
|
self.cw = QtGui.QWidget()
|
||||||
self.setCentralWidget(self.cw)
|
self.setCentralWidget(self.cw)
|
||||||
self.ui.setupUi(self.cw)
|
self.ui.setupUi(self.cw)
|
||||||
|
self.setWindowTitle("PyQtGraph Examples")
|
||||||
|
|
||||||
self.codeBtn = QtGui.QPushButton('Run Edited Code')
|
self.codeBtn = QtGui.QPushButton('Run Edited Code')
|
||||||
self.codeLayout = QtGui.QGridLayout()
|
self.codeLayout = QtGui.QGridLayout()
|
||||||
|
@ -14,7 +14,7 @@ import pyqtgraph as pg
|
|||||||
from pyqtgraph.Qt import QtCore, QtGui
|
from pyqtgraph.Qt import QtCore, QtGui
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
win = pg.GraphicsWindow()
|
win = pg.GraphicsLayoutWidget(show=True)
|
||||||
win.setWindowTitle('pyqtgraph example: context menu')
|
win.setWindowTitle('pyqtgraph example: context menu')
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ from pyqtgraph.Point import Point
|
|||||||
|
|
||||||
#generate layout
|
#generate layout
|
||||||
app = QtGui.QApplication([])
|
app = QtGui.QApplication([])
|
||||||
win = pg.GraphicsWindow()
|
win = pg.GraphicsLayoutWidget(show=True)
|
||||||
win.setWindowTitle('pyqtgraph example: crosshair')
|
win.setWindowTitle('pyqtgraph example: crosshair')
|
||||||
label = pg.LabelItem(justify='right')
|
label = pg.LabelItem(justify='right')
|
||||||
win.addItem(label)
|
win.addItem(label)
|
||||||
|
@ -4,6 +4,7 @@ Displays an interactive Koch fractal
|
|||||||
"""
|
"""
|
||||||
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)
|
||||||
|
|
||||||
|
from functools import reduce
|
||||||
import pyqtgraph as pg
|
import pyqtgraph as pg
|
||||||
from pyqtgraph.Qt import QtCore, QtGui
|
from pyqtgraph.Qt import QtCore, QtGui
|
||||||
import numpy as np
|
import numpy as np
|
||||||
@ -112,11 +113,3 @@ if __name__ == '__main__':
|
|||||||
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
|
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
|
||||||
QtGui.QApplication.instance().exec_()
|
QtGui.QApplication.instance().exec_()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -8,7 +8,7 @@ import pyqtgraph as pg
|
|||||||
from pyqtgraph.Qt import QtCore, QtGui
|
from pyqtgraph.Qt import QtCore, QtGui
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
win = pg.GraphicsWindow()
|
win = pg.GraphicsLayoutWidget(show=True)
|
||||||
win.resize(800,350)
|
win.resize(800,350)
|
||||||
win.setWindowTitle('pyqtgraph example: Histogram')
|
win.setWindowTitle('pyqtgraph example: Histogram')
|
||||||
plt1 = win.addPlot()
|
plt1 = win.addPlot()
|
||||||
|
@ -17,10 +17,10 @@ app = QtGui.QApplication([])
|
|||||||
frames = 200
|
frames = 200
|
||||||
data = np.random.normal(size=(frames,30,30), loc=0, scale=100)
|
data = np.random.normal(size=(frames,30,30), loc=0, scale=100)
|
||||||
data = np.concatenate([data, data], axis=0)
|
data = np.concatenate([data, data], axis=0)
|
||||||
data = pg.gaussianFilter(data, (10, 10, 10))[frames/2:frames + frames/2]
|
data = pg.gaussianFilter(data, (10, 10, 10))[frames//2:frames + frames//2]
|
||||||
data[:, 15:16, 15:17] += 1
|
data[:, 15:16, 15:17] += 1
|
||||||
|
|
||||||
win = pg.GraphicsWindow()
|
win = pg.GraphicsLayoutWidget(show=True)
|
||||||
win.setWindowTitle('pyqtgraph example: Isocurve')
|
win.setWindowTitle('pyqtgraph example: Isocurve')
|
||||||
vb = win.addViewBox()
|
vb = win.addViewBox()
|
||||||
img = pg.ImageItem(data[0])
|
img = pg.ImageItem(data[0])
|
||||||
|
@ -20,7 +20,7 @@ app = QtGui.QApplication([])
|
|||||||
x = np.linspace(-50, 50, 1000)
|
x = np.linspace(-50, 50, 1000)
|
||||||
y = np.sin(x) / x
|
y = np.sin(x) / x
|
||||||
|
|
||||||
win = pg.GraphicsWindow(title="pyqtgraph example: Linked Views")
|
win = pg.GraphicsLayoutWidget(show=True, title="pyqtgraph example: Linked Views")
|
||||||
win.resize(800,600)
|
win.resize(800,600)
|
||||||
|
|
||||||
win.addLabel("Linked Views", colspan=2)
|
win.addLabel("Linked Views", colspan=2)
|
||||||
|
@ -11,7 +11,7 @@ import pyqtgraph as pg
|
|||||||
|
|
||||||
app = QtGui.QApplication([])
|
app = QtGui.QApplication([])
|
||||||
|
|
||||||
w = pg.GraphicsWindow()
|
w = pg.GraphicsLayoutWidget(show=True)
|
||||||
w.setWindowTitle('pyqtgraph example: logAxis')
|
w.setWindowTitle('pyqtgraph example: logAxis')
|
||||||
p1 = w.addPlot(0,0, title="X Semilog")
|
p1 = w.addPlot(0,0, title="X Semilog")
|
||||||
p2 = w.addPlot(1,0, title="Y Semilog")
|
p2 = w.addPlot(1,0, title="Y Semilog")
|
||||||
|
@ -17,7 +17,7 @@ from pyqtgraph import Point
|
|||||||
|
|
||||||
app = pg.QtGui.QApplication([])
|
app = pg.QtGui.QApplication([])
|
||||||
|
|
||||||
w = pg.GraphicsWindow(border=0.5)
|
w = pg.GraphicsLayoutWidget(show=True, border=0.5)
|
||||||
w.resize(1000, 900)
|
w.resize(1000, 900)
|
||||||
w.show()
|
w.show()
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import pyqtgraph as pg
|
|||||||
from pyqtgraph.Qt import QtCore, QtGui
|
from pyqtgraph.Qt import QtCore, QtGui
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
win = pg.GraphicsWindow()
|
win = pg.GraphicsLayoutWidget(show=True)
|
||||||
win.setWindowTitle('pyqtgraph example: Scrolling Plots')
|
win.setWindowTitle('pyqtgraph example: Scrolling Plots')
|
||||||
|
|
||||||
|
|
||||||
|
@ -37,6 +37,18 @@ class GraphicsScene(QtGui.QGraphicsScene):
|
|||||||
* Eats mouseMove events that occur too soon after a mouse press.
|
* Eats mouseMove events that occur too soon after a mouse press.
|
||||||
* Reimplements items() and itemAt() to circumvent PyQt bug
|
* Reimplements items() and itemAt() to circumvent PyQt bug
|
||||||
|
|
||||||
|
====================== ==================================================================
|
||||||
|
**Signals**
|
||||||
|
sigMouseClicked(event) Emitted when the mouse is clicked over the scene. Use ev.pos() to
|
||||||
|
get the click position relative to the item that was clicked on,
|
||||||
|
or ev.scenePos() to get the click position in scene coordinates.
|
||||||
|
See :class:`pyqtgraph.GraphicsScene.MouseClickEvent`.
|
||||||
|
sigMouseMoved(pos) Emitted when the mouse cursor moves over the scene. The position
|
||||||
|
is given in scene coordinates.
|
||||||
|
sigMouseHover(items) Emitted when the mouse is moved over the scene. Items is a list
|
||||||
|
of items under the cursor.
|
||||||
|
====================== ==================================================================
|
||||||
|
|
||||||
Mouse interaction is as follows:
|
Mouse interaction is as follows:
|
||||||
|
|
||||||
1) Every time the mouse moves, the scene delivers both the standard hoverEnter/Move/LeaveEvents
|
1) Every time the mouse moves, the scene delivers both the standard hoverEnter/Move/LeaveEvents
|
||||||
|
@ -105,7 +105,13 @@ class Point(QtCore.QPointF):
|
|||||||
|
|
||||||
def length(self):
|
def length(self):
|
||||||
"""Returns the vector length of this Point."""
|
"""Returns the vector length of this Point."""
|
||||||
|
try:
|
||||||
return (self[0]**2 + self[1]**2) ** 0.5
|
return (self[0]**2 + self[1]**2) ** 0.5
|
||||||
|
except OverflowError:
|
||||||
|
try:
|
||||||
|
return self[1] / np.sin(np.arctan2(self[1], self[0]))
|
||||||
|
except OverflowError:
|
||||||
|
return np.inf
|
||||||
|
|
||||||
def norm(self):
|
def norm(self):
|
||||||
"""Returns a vector in the same direction with unit length."""
|
"""Returns a vector in the same direction with unit length."""
|
||||||
|
@ -319,3 +319,12 @@ m = re.match(r'(\d+)\.(\d+).*', QtVersion)
|
|||||||
if m is not None and list(map(int, m.groups())) < versionReq:
|
if m is not None and list(map(int, m.groups())) < versionReq:
|
||||||
print(list(map(int, m.groups())))
|
print(list(map(int, m.groups())))
|
||||||
raise Exception('pyqtgraph requires Qt version >= %d.%d (your version is %s)' % (versionReq[0], versionReq[1], QtVersion))
|
raise Exception('pyqtgraph requires Qt version >= %d.%d (your version is %s)' % (versionReq[0], versionReq[1], QtVersion))
|
||||||
|
|
||||||
|
|
||||||
|
QAPP = None
|
||||||
|
def mkQApp():
|
||||||
|
global QAPP
|
||||||
|
QAPP = QtGui.QApplication.instance()
|
||||||
|
if QAPP is None:
|
||||||
|
QAPP = QtGui.QApplication([])
|
||||||
|
return QAPP
|
||||||
|
@ -113,7 +113,7 @@ class SRTTransform3D(Transform3D):
|
|||||||
|
|
||||||
def setFromMatrix(self, m):
|
def setFromMatrix(self, m):
|
||||||
"""
|
"""
|
||||||
Set this transform mased on the elements of *m*
|
Set this transform based on the elements of *m*
|
||||||
The input matrix must be affine AND have no shear,
|
The input matrix must be affine AND have no shear,
|
||||||
otherwise the conversion will most likely fail.
|
otherwise the conversion will most likely fail.
|
||||||
"""
|
"""
|
||||||
|
@ -10,7 +10,7 @@ __version__ = '0.10.0'
|
|||||||
|
|
||||||
## 'Qt' is a local module; it is intended mainly to cover up the differences
|
## 'Qt' is a local module; it is intended mainly to cover up the differences
|
||||||
## between PyQt4 and PySide.
|
## between PyQt4 and PySide.
|
||||||
from .Qt import QtGui
|
from .Qt import QtGui, mkQApp
|
||||||
|
|
||||||
## not really safe--If we accidentally create another QApplication, the process hangs (and it is very difficult to trace the cause)
|
## not really safe--If we accidentally create another QApplication, the process hangs (and it is very difficult to trace the cause)
|
||||||
#if QtGui.QApplication.instance() is None:
|
#if QtGui.QApplication.instance() is None:
|
||||||
@ -258,6 +258,7 @@ from .widgets.VerticalLabel import *
|
|||||||
from .widgets.FeedbackButton import *
|
from .widgets.FeedbackButton import *
|
||||||
from .widgets.ColorButton import *
|
from .widgets.ColorButton import *
|
||||||
from .widgets.DataTreeWidget import *
|
from .widgets.DataTreeWidget import *
|
||||||
|
from .widgets.DiffTreeWidget import *
|
||||||
from .widgets.GraphicsView import *
|
from .widgets.GraphicsView import *
|
||||||
from .widgets.LayoutWidget import *
|
from .widgets.LayoutWidget import *
|
||||||
from .widgets.TableWidget import *
|
from .widgets.TableWidget import *
|
||||||
@ -466,14 +467,3 @@ def stack(*args, **kwds):
|
|||||||
except NameError:
|
except NameError:
|
||||||
consoles = [c]
|
consoles = [c]
|
||||||
return c
|
return c
|
||||||
|
|
||||||
|
|
||||||
def mkQApp():
|
|
||||||
global QAPP
|
|
||||||
inst = QtGui.QApplication.instance()
|
|
||||||
if inst is None:
|
|
||||||
QAPP = QtGui.QApplication([])
|
|
||||||
else:
|
|
||||||
QAPP = inst
|
|
||||||
return QAPP
|
|
||||||
|
|
||||||
|
@ -361,7 +361,6 @@ class ConsoleWidget(QtGui.QWidget):
|
|||||||
for index, line in enumerate(traceback.extract_stack(frame)):
|
for index, line in enumerate(traceback.extract_stack(frame)):
|
||||||
# extract_stack return value changed in python 3.5
|
# extract_stack return value changed in python 3.5
|
||||||
if 'FrameSummary' in str(type(line)):
|
if 'FrameSummary' in str(type(line)):
|
||||||
print(dir(line))
|
|
||||||
line = (line.filename, line.lineno, line.name, line._line)
|
line = (line.filename, line.lineno, line.name, line._line)
|
||||||
|
|
||||||
self.ui.exceptionStackList.addItem('File "%s", line %s, in %s()\n %s' % line)
|
self.ui.exceptionStackList.addItem('File "%s", line %s, in %s()\n %s' % line)
|
||||||
@ -382,7 +381,6 @@ class ConsoleWidget(QtGui.QWidget):
|
|||||||
for index, line in enumerate(traceback.extract_tb(tb)):
|
for index, line in enumerate(traceback.extract_tb(tb)):
|
||||||
# extract_stack return value changed in python 3.5
|
# extract_stack return value changed in python 3.5
|
||||||
if 'FrameSummary' in str(type(line)):
|
if 'FrameSummary' in str(type(line)):
|
||||||
print(dir(line))
|
|
||||||
line = (line.filename, line.lineno, line.name, line._line)
|
line = (line.filename, line.lineno, line.name, line._line)
|
||||||
|
|
||||||
self.ui.exceptionStackList.addItem('File "%s", line %s, in %s()\n %s' % line)
|
self.ui.exceptionStackList.addItem('File "%s", line %s, in %s()\n %s' % line)
|
||||||
|
@ -510,7 +510,7 @@ class Profiler(object):
|
|||||||
try:
|
try:
|
||||||
caller_object_type = type(caller_frame.f_locals["self"])
|
caller_object_type = type(caller_frame.f_locals["self"])
|
||||||
except KeyError: # we are in a regular function
|
except KeyError: # we are in a regular function
|
||||||
qualifier = caller_frame.f_globals["__name__"].split(".", 1)[1]
|
qualifier = caller_frame.f_globals["__name__"].split(".", 1)[-1]
|
||||||
else: # we are in a method
|
else: # we are in a method
|
||||||
qualifier = caller_object_type.__name__
|
qualifier = caller_object_type.__name__
|
||||||
func_qualname = qualifier + "." + caller_frame.f_code.co_name
|
func_qualname = qualifier + "." + caller_frame.f_code.co_name
|
||||||
|
@ -23,7 +23,8 @@ class SVGExporter(Exporter):
|
|||||||
#{'name': 'height', 'type': 'float', 'value': tr.height(), 'limits': (0, None)},
|
#{'name': 'height', 'type': 'float', 'value': tr.height(), 'limits': (0, None)},
|
||||||
#{'name': 'viewbox clipping', 'type': 'bool', 'value': True},
|
#{'name': 'viewbox clipping', 'type': 'bool', 'value': True},
|
||||||
#{'name': 'normalize coordinates', 'type': 'bool', 'value': True},
|
#{'name': 'normalize coordinates', 'type': 'bool', 'value': True},
|
||||||
#{'name': 'normalize line width', 'type': 'bool', 'value': True},
|
{'name': 'scaling stroke', 'type': 'bool', 'value': False, 'tip': "If False, strokes are non-scaling, "
|
||||||
|
"which means that they appear the same width on screen regardless of how they are scaled or how the view is zoomed."},
|
||||||
])
|
])
|
||||||
#self.params.param('width').sigValueChanged.connect(self.widthChanged)
|
#self.params.param('width').sigValueChanged.connect(self.widthChanged)
|
||||||
#self.params.param('height').sigValueChanged.connect(self.heightChanged)
|
#self.params.param('height').sigValueChanged.connect(self.heightChanged)
|
||||||
@ -49,7 +50,8 @@ class SVGExporter(Exporter):
|
|||||||
## Qt's SVG generator is not complete. (notably, it lacks clipping)
|
## Qt's SVG generator is not complete. (notably, it lacks clipping)
|
||||||
## Instead, we will use Qt to generate SVG for each item independently,
|
## Instead, we will use Qt to generate SVG for each item independently,
|
||||||
## then manually reconstruct the entire document.
|
## then manually reconstruct the entire document.
|
||||||
xml = generateSvg(self.item)
|
options = {ch.name():ch.value() for ch in self.params.children()}
|
||||||
|
xml = generateSvg(self.item, options)
|
||||||
|
|
||||||
if toBytes:
|
if toBytes:
|
||||||
return xml.encode('UTF-8')
|
return xml.encode('UTF-8')
|
||||||
@ -69,10 +71,10 @@ xmlHeader = """\
|
|||||||
<desc>Generated with Qt and pyqtgraph</desc>
|
<desc>Generated with Qt and pyqtgraph</desc>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def generateSvg(item):
|
def generateSvg(item, options={}):
|
||||||
global xmlHeader
|
global xmlHeader
|
||||||
try:
|
try:
|
||||||
node, defs = _generateItemSvg(item)
|
node, defs = _generateItemSvg(item, options=options)
|
||||||
finally:
|
finally:
|
||||||
## reset export mode for all items in the tree
|
## reset export mode for all items in the tree
|
||||||
if isinstance(item, QtGui.QGraphicsScene):
|
if isinstance(item, QtGui.QGraphicsScene):
|
||||||
@ -94,7 +96,7 @@ def generateSvg(item):
|
|||||||
return xmlHeader + defsXml + node.toprettyxml(indent=' ') + "\n</svg>\n"
|
return xmlHeader + defsXml + node.toprettyxml(indent=' ') + "\n</svg>\n"
|
||||||
|
|
||||||
|
|
||||||
def _generateItemSvg(item, nodes=None, root=None):
|
def _generateItemSvg(item, nodes=None, root=None, options={}):
|
||||||
## This function is intended to work around some issues with Qt's SVG generator
|
## This function is intended to work around some issues with Qt's SVG generator
|
||||||
## and SVG in general.
|
## and SVG in general.
|
||||||
## 1) Qt SVG does not implement clipping paths. This is absurd.
|
## 1) Qt SVG does not implement clipping paths. This is absurd.
|
||||||
@ -169,7 +171,7 @@ def _generateItemSvg(item, nodes=None, root=None):
|
|||||||
buf = QtCore.QBuffer(arr)
|
buf = QtCore.QBuffer(arr)
|
||||||
svg = QtSvg.QSvgGenerator()
|
svg = QtSvg.QSvgGenerator()
|
||||||
svg.setOutputDevice(buf)
|
svg.setOutputDevice(buf)
|
||||||
dpi = QtGui.QDesktopWidget().physicalDpiX()
|
dpi = QtGui.QDesktopWidget().logicalDpiX()
|
||||||
svg.setResolution(dpi)
|
svg.setResolution(dpi)
|
||||||
|
|
||||||
p = QtGui.QPainter()
|
p = QtGui.QPainter()
|
||||||
@ -209,18 +211,8 @@ def _generateItemSvg(item, nodes=None, root=None):
|
|||||||
|
|
||||||
## Get rid of group transformation matrices by applying
|
## Get rid of group transformation matrices by applying
|
||||||
## transformation to inner coordinates
|
## transformation to inner coordinates
|
||||||
correctCoordinates(g1, defs, item)
|
correctCoordinates(g1, defs, item, options)
|
||||||
profiler('correct')
|
profiler('correct')
|
||||||
## make sure g1 has the transformation matrix
|
|
||||||
#m = (tr.m11(), tr.m12(), tr.m21(), tr.m22(), tr.m31(), tr.m32())
|
|
||||||
#g1.setAttribute('transform', "matrix(%f,%f,%f,%f,%f,%f)" % m)
|
|
||||||
|
|
||||||
#print "=================",item,"====================="
|
|
||||||
#print g1.toprettyxml(indent=" ", newl='')
|
|
||||||
|
|
||||||
## Inkscape does not support non-scaling-stroke (this is SVG 1.2, inkscape supports 1.1)
|
|
||||||
## So we need to correct anything attempting to use this.
|
|
||||||
#correctStroke(g1, item, root)
|
|
||||||
|
|
||||||
## decide on a name for this item
|
## decide on a name for this item
|
||||||
baseName = item.__class__.__name__
|
baseName = item.__class__.__name__
|
||||||
@ -239,15 +231,10 @@ def _generateItemSvg(item, nodes=None, root=None):
|
|||||||
## See if this item clips its children
|
## See if this item clips its children
|
||||||
if int(item.flags() & item.ItemClipsChildrenToShape) > 0:
|
if int(item.flags() & item.ItemClipsChildrenToShape) > 0:
|
||||||
## Generate svg for just the path
|
## Generate svg for just the path
|
||||||
#if isinstance(root, QtGui.QGraphicsScene):
|
|
||||||
#path = QtGui.QGraphicsPathItem(item.mapToScene(item.shape()))
|
|
||||||
#else:
|
|
||||||
#path = QtGui.QGraphicsPathItem(root.mapToParent(item.mapToItem(root, item.shape())))
|
|
||||||
path = QtGui.QGraphicsPathItem(item.mapToScene(item.shape()))
|
path = QtGui.QGraphicsPathItem(item.mapToScene(item.shape()))
|
||||||
item.scene().addItem(path)
|
item.scene().addItem(path)
|
||||||
try:
|
try:
|
||||||
#pathNode = _generateItemSvg(path, root=root).getElementsByTagName('path')[0]
|
pathNode = _generateItemSvg(path, root=root, options=options)[0].getElementsByTagName('path')[0]
|
||||||
pathNode = _generateItemSvg(path, root=root)[0].getElementsByTagName('path')[0]
|
|
||||||
# assume <defs> for this path is empty.. possibly problematic.
|
# assume <defs> for this path is empty.. possibly problematic.
|
||||||
finally:
|
finally:
|
||||||
item.scene().removeItem(path)
|
item.scene().removeItem(path)
|
||||||
@ -267,7 +254,7 @@ def _generateItemSvg(item, nodes=None, root=None):
|
|||||||
## Add all child items as sub-elements.
|
## Add all child items as sub-elements.
|
||||||
childs.sort(key=lambda c: c.zValue())
|
childs.sort(key=lambda c: c.zValue())
|
||||||
for ch in childs:
|
for ch in childs:
|
||||||
csvg = _generateItemSvg(ch, nodes, root)
|
csvg = _generateItemSvg(ch, nodes, root, options=options)
|
||||||
if csvg is None:
|
if csvg is None:
|
||||||
continue
|
continue
|
||||||
cg, cdefs = csvg
|
cg, cdefs = csvg
|
||||||
@ -277,7 +264,8 @@ def _generateItemSvg(item, nodes=None, root=None):
|
|||||||
profiler('children')
|
profiler('children')
|
||||||
return g1, defs
|
return g1, defs
|
||||||
|
|
||||||
def correctCoordinates(node, defs, item):
|
|
||||||
|
def correctCoordinates(node, defs, item, options):
|
||||||
# TODO: correct gradient coordinates inside defs
|
# TODO: correct gradient coordinates inside defs
|
||||||
|
|
||||||
## Remove transformation matrices from <g> tags by applying matrix to coordinates inside.
|
## Remove transformation matrices from <g> tags by applying matrix to coordinates inside.
|
||||||
@ -344,6 +332,10 @@ def correctCoordinates(node, defs, item):
|
|||||||
t = ''
|
t = ''
|
||||||
nc = fn.transformCoordinates(tr, np.array([[float(x),float(y)]]), transpose=True)
|
nc = fn.transformCoordinates(tr, np.array([[float(x),float(y)]]), transpose=True)
|
||||||
newCoords += t+str(nc[0,0])+','+str(nc[0,1])+' '
|
newCoords += t+str(nc[0,0])+','+str(nc[0,1])+' '
|
||||||
|
# If coords start with L instead of M, then the entire path will not be rendered.
|
||||||
|
# (This can happen if the first point had nan values in it--Qt will skip it on export)
|
||||||
|
if newCoords[0] != 'M':
|
||||||
|
newCoords = 'M' + newCoords[1:]
|
||||||
ch.setAttribute('d', newCoords)
|
ch.setAttribute('d', newCoords)
|
||||||
elif ch.tagName == 'text':
|
elif ch.tagName == 'text':
|
||||||
removeTransform = False
|
removeTransform = False
|
||||||
@ -372,12 +364,16 @@ def correctCoordinates(node, defs, item):
|
|||||||
ch.setAttribute('font-family', ', '.join([f if ' ' not in f else '"%s"'%f for f in families]))
|
ch.setAttribute('font-family', ', '.join([f if ' ' not in f else '"%s"'%f for f in families]))
|
||||||
|
|
||||||
## correct line widths if needed
|
## correct line widths if needed
|
||||||
if removeTransform and ch.getAttribute('vector-effect') != 'non-scaling-stroke':
|
if removeTransform and ch.getAttribute('vector-effect') != 'non-scaling-stroke' and grp.getAttribute('stroke-width') != '':
|
||||||
w = float(grp.getAttribute('stroke-width'))
|
w = float(grp.getAttribute('stroke-width'))
|
||||||
s = fn.transformCoordinates(tr, np.array([[w,0], [0,0]]), transpose=True)
|
s = fn.transformCoordinates(tr, np.array([[w,0], [0,0]]), transpose=True)
|
||||||
w = ((s[0]-s[1])**2).sum()**0.5
|
w = ((s[0]-s[1])**2).sum()**0.5
|
||||||
ch.setAttribute('stroke-width', str(w))
|
ch.setAttribute('stroke-width', str(w))
|
||||||
|
|
||||||
|
# Remove non-scaling-stroke if requested
|
||||||
|
if options.get('scaling stroke') is True and ch.getAttribute('vector-effect') == 'non-scaling-stroke':
|
||||||
|
ch.removeAttribute('vector-effect')
|
||||||
|
|
||||||
if removeTransform:
|
if removeTransform:
|
||||||
grp.removeAttribute('transform')
|
grp.removeAttribute('transform')
|
||||||
|
|
||||||
|
@ -14,7 +14,8 @@ import sys, struct
|
|||||||
from .python2_3 import asUnicode, basestring
|
from .python2_3 import asUnicode, basestring
|
||||||
from .Qt import QtGui, QtCore, QT_LIB
|
from .Qt import QtGui, QtCore, QT_LIB
|
||||||
from . import getConfigOption, setConfigOptions
|
from . import getConfigOption, setConfigOptions
|
||||||
from . import debug
|
from . import debug, reload
|
||||||
|
from .reload import getPreviousVersion
|
||||||
from .metaarray import MetaArray
|
from .metaarray import MetaArray
|
||||||
|
|
||||||
|
|
||||||
@ -2172,7 +2173,7 @@ def isosurface(data, level):
|
|||||||
## compute lookup table of index: vertexes mapping
|
## compute lookup table of index: vertexes mapping
|
||||||
faceTableI = np.zeros((len(triTable), i*3), dtype=np.ubyte)
|
faceTableI = np.zeros((len(triTable), i*3), dtype=np.ubyte)
|
||||||
faceTableInds = np.argwhere(nTableFaces == i)
|
faceTableInds = np.argwhere(nTableFaces == i)
|
||||||
faceTableI[faceTableInds[:,0]] = np.array([triTable[j] for j in faceTableInds])
|
faceTableI[faceTableInds[:,0]] = np.array([triTable[j[0]] for j in faceTableInds])
|
||||||
faceTableI = faceTableI.reshape((len(triTable), i, 3))
|
faceTableI = faceTableI.reshape((len(triTable), i, 3))
|
||||||
faceShiftTables.append(edgeShifts[faceTableI])
|
faceShiftTables.append(edgeShifts[faceTableI])
|
||||||
|
|
||||||
@ -2428,3 +2429,45 @@ def toposort(deps, nodes=None, seen=None, stack=None, depth=0):
|
|||||||
sorted.extend( toposort(deps, deps[n], seen, stack+[n], depth=depth+1))
|
sorted.extend( toposort(deps, deps[n], seen, stack+[n], depth=depth+1))
|
||||||
sorted.append(n)
|
sorted.append(n)
|
||||||
return sorted
|
return sorted
|
||||||
|
|
||||||
|
|
||||||
|
def disconnect(signal, slot):
|
||||||
|
"""Disconnect a Qt signal from a slot.
|
||||||
|
|
||||||
|
This method augments Qt's Signal.disconnect():
|
||||||
|
|
||||||
|
* Return bool indicating whether disconnection was successful, rather than
|
||||||
|
raising an exception
|
||||||
|
* Attempt to disconnect prior versions of the slot when using pg.reload
|
||||||
|
"""
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
signal.disconnect(slot)
|
||||||
|
return True
|
||||||
|
except (TypeError, RuntimeError):
|
||||||
|
slot = reload.getPreviousVersion(slot)
|
||||||
|
if slot is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class SignalBlock(object):
|
||||||
|
"""Class used to temporarily block a Qt signal connection::
|
||||||
|
|
||||||
|
with SignalBlock(signal, slot):
|
||||||
|
# do something that emits a signal; it will
|
||||||
|
# not be delivered to slot
|
||||||
|
"""
|
||||||
|
def __init__(self, signal, slot):
|
||||||
|
self.signal = signal
|
||||||
|
self.slot = slot
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self.reconnect = disconnect(self.signal, self.slot)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, *args):
|
||||||
|
if self.reconnect:
|
||||||
|
self.signal.connect(self.slot)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -379,6 +379,10 @@ class ImageItem(GraphicsObject):
|
|||||||
image = fn.downsample(self.image, xds, axis=axes[0])
|
image = fn.downsample(self.image, xds, axis=axes[0])
|
||||||
image = fn.downsample(image, yds, axis=axes[1])
|
image = fn.downsample(image, yds, axis=axes[1])
|
||||||
self._lastDownsample = (xds, yds)
|
self._lastDownsample = (xds, yds)
|
||||||
|
|
||||||
|
# Check if downsampling reduced the image size to zero due to inf values.
|
||||||
|
if image.size == 0:
|
||||||
|
return
|
||||||
else:
|
else:
|
||||||
image = self.image
|
image = self.image
|
||||||
|
|
||||||
@ -465,11 +469,11 @@ class ImageItem(GraphicsObject):
|
|||||||
|
|
||||||
This method is also used when automatically computing levels.
|
This method is also used when automatically computing levels.
|
||||||
"""
|
"""
|
||||||
if self.image is None:
|
if self.image is None or self.image.size == 0:
|
||||||
return None,None
|
return None,None
|
||||||
if step == 'auto':
|
if step == 'auto':
|
||||||
step = (int(np.ceil(self.image.shape[0] / targetImageSize)),
|
step = (max(1, int(np.ceil(self.image.shape[0] / targetImageSize))),
|
||||||
int(np.ceil(self.image.shape[1] / targetImageSize)))
|
max(1, int(np.ceil(self.image.shape[1] / targetImageSize))))
|
||||||
if np.isscalar(step):
|
if np.isscalar(step):
|
||||||
step = (step, step)
|
step = (step, step)
|
||||||
stepData = self.image[::step[0], ::step[1]]
|
stepData = self.image[::step[0], ::step[1]]
|
||||||
|
@ -110,7 +110,8 @@ class LegendItem(GraphicsWidget, GraphicsWidgetAnchor):
|
|||||||
#print("-------")
|
#print("-------")
|
||||||
for sample, label in self.items:
|
for sample, label in self.items:
|
||||||
height += max(sample.height(), label.height()) + 3
|
height += max(sample.height(), label.height()) + 3
|
||||||
width = max(width, sample.width()+label.width())
|
width = max(width, (sample.sizeHint(QtCore.Qt.MinimumSize, sample.size()).width() +
|
||||||
|
label.sizeHint(QtCore.Qt.MinimumSize, label.size()).width()))
|
||||||
#print(width, height)
|
#print(width, height)
|
||||||
#print width, height
|
#print width, height
|
||||||
self.setGeometry(0, 0, width+25, height)
|
self.setGeometry(0, 0, width+25, height)
|
||||||
|
@ -649,6 +649,9 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
d = d[mask]
|
d = d[mask]
|
||||||
d2 = d2[mask]
|
d2 = d2[mask]
|
||||||
|
|
||||||
|
if d.size == 0:
|
||||||
|
return (None, None)
|
||||||
|
|
||||||
if frac >= 1.0:
|
if frac >= 1.0:
|
||||||
self.bounds[ax] = (np.nanmin(d) - self._maxSpotWidth*0.7072, np.nanmax(d) + self._maxSpotWidth*0.7072)
|
self.bounds[ax] = (np.nanmin(d) - self._maxSpotWidth*0.7072, np.nanmax(d) + self._maxSpotWidth*0.7072)
|
||||||
return self.bounds[ax]
|
return self.bounds[ax]
|
||||||
@ -701,16 +704,12 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
GraphicsObject.setExportMode(self, *args, **kwds)
|
GraphicsObject.setExportMode(self, *args, **kwds)
|
||||||
self.invalidate()
|
self.invalidate()
|
||||||
|
|
||||||
|
|
||||||
def mapPointsToDevice(self, pts):
|
def mapPointsToDevice(self, pts):
|
||||||
# Map point locations to device
|
# Map point locations to device
|
||||||
tr = self.deviceTransform()
|
tr = self.deviceTransform()
|
||||||
if tr is None:
|
if tr is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
#pts = np.empty((2,len(self.data['x'])))
|
|
||||||
#pts[0] = self.data['x']
|
|
||||||
#pts[1] = self.data['y']
|
|
||||||
pts = fn.transformCoordinates(tr, pts)
|
pts = fn.transformCoordinates(tr, pts)
|
||||||
pts -= self.data['width']
|
pts -= self.data['width']
|
||||||
pts = np.clip(pts, -2**30, 2**30) ## prevent Qt segmentation fault.
|
pts = np.clip(pts, -2**30, 2**30) ## prevent Qt segmentation fault.
|
||||||
@ -731,7 +730,6 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
(pts[1] - w < viewBounds.bottom())) ## remove out of view points
|
(pts[1] - w < viewBounds.bottom())) ## remove out of view points
|
||||||
return mask
|
return mask
|
||||||
|
|
||||||
|
|
||||||
@debug.warnOnException ## raising an exception here causes crash
|
@debug.warnOnException ## raising an exception here causes crash
|
||||||
def paint(self, p, *args):
|
def paint(self, p, *args):
|
||||||
cmode = self.opts.get('compositionMode', None)
|
cmode = self.opts.get('compositionMode', None)
|
||||||
@ -758,8 +756,6 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
|
|
||||||
# Cull points that are outside view
|
# Cull points that are outside view
|
||||||
viewMask = self.getViewMask(pts)
|
viewMask = self.getViewMask(pts)
|
||||||
#pts = pts[:,mask]
|
|
||||||
#data = self.data[mask]
|
|
||||||
|
|
||||||
if self.opts['useCache'] and self._exportOpts is False:
|
if self.opts['useCache'] and self._exportOpts is False:
|
||||||
# Draw symbols from pre-rendered atlas
|
# Draw symbols from pre-rendered atlas
|
||||||
@ -804,9 +800,9 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
self.picture.play(p)
|
self.picture.play(p)
|
||||||
|
|
||||||
def points(self):
|
def points(self):
|
||||||
for rec in self.data:
|
for i,rec in enumerate(self.data):
|
||||||
if rec['item'] is None:
|
if rec['item'] is None:
|
||||||
rec['item'] = SpotItem(rec, self)
|
rec['item'] = SpotItem(rec, self, i)
|
||||||
return self.data['item']
|
return self.data['item']
|
||||||
|
|
||||||
def pointsAt(self, pos):
|
def pointsAt(self, pos):
|
||||||
@ -854,18 +850,26 @@ class SpotItem(object):
|
|||||||
by connecting to the ScatterPlotItem's click signals.
|
by connecting to the ScatterPlotItem's click signals.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, data, plot):
|
def __init__(self, data, plot, index):
|
||||||
#GraphicsItem.__init__(self, register=False)
|
|
||||||
self._data = data
|
self._data = data
|
||||||
self._plot = plot
|
self._index = index
|
||||||
#self.setParentItem(plot)
|
# SpotItems are kept in plot.data["items"] numpy object array which
|
||||||
#self.setPos(QtCore.QPointF(data['x'], data['y']))
|
# does not support cyclic garbage collection (numpy issue 6581).
|
||||||
#self.updateItem()
|
# Keeping a strong ref to plot here would leak the cycle
|
||||||
|
self.__plot_ref = weakref.ref(plot)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _plot(self):
|
||||||
|
return self.__plot_ref()
|
||||||
|
|
||||||
def data(self):
|
def data(self):
|
||||||
"""Return the user data associated with this spot."""
|
"""Return the user data associated with this spot."""
|
||||||
return self._data['data']
|
return self._data['data']
|
||||||
|
|
||||||
|
def index(self):
|
||||||
|
"""Return the index of this point as given in the scatter plot data."""
|
||||||
|
return self._index
|
||||||
|
|
||||||
def size(self):
|
def size(self):
|
||||||
"""Return the size of this spot.
|
"""Return the size of this spot.
|
||||||
If the spot has no explicit size set, then return the ScatterPlotItem's default size instead."""
|
If the spot has no explicit size set, then return the ScatterPlotItem's default size instead."""
|
||||||
@ -949,37 +953,3 @@ class SpotItem(object):
|
|||||||
self._data['sourceRect'] = None
|
self._data['sourceRect'] = None
|
||||||
self._plot.updateSpots(self._data.reshape(1))
|
self._plot.updateSpots(self._data.reshape(1))
|
||||||
self._plot.invalidate()
|
self._plot.invalidate()
|
||||||
|
|
||||||
#class PixmapSpotItem(SpotItem, QtGui.QGraphicsPixmapItem):
|
|
||||||
#def __init__(self, data, plot):
|
|
||||||
#QtGui.QGraphicsPixmapItem.__init__(self)
|
|
||||||
#self.setFlags(self.flags() | self.ItemIgnoresTransformations)
|
|
||||||
#SpotItem.__init__(self, data, plot)
|
|
||||||
|
|
||||||
#def setPixmap(self, pixmap):
|
|
||||||
#QtGui.QGraphicsPixmapItem.setPixmap(self, pixmap)
|
|
||||||
#self.setOffset(-pixmap.width()/2.+0.5, -pixmap.height()/2.)
|
|
||||||
|
|
||||||
#def updateItem(self):
|
|
||||||
#symbolOpts = (self._data['pen'], self._data['brush'], self._data['size'], self._data['symbol'])
|
|
||||||
|
|
||||||
### If all symbol options are default, use default pixmap
|
|
||||||
#if symbolOpts == (None, None, -1, ''):
|
|
||||||
#pixmap = self._plot.defaultSpotPixmap()
|
|
||||||
#else:
|
|
||||||
#pixmap = makeSymbolPixmap(size=self.size(), pen=self.pen(), brush=self.brush(), symbol=self.symbol())
|
|
||||||
#self.setPixmap(pixmap)
|
|
||||||
|
|
||||||
|
|
||||||
#class PathSpotItem(SpotItem, QtGui.QGraphicsPathItem):
|
|
||||||
#def __init__(self, data, plot):
|
|
||||||
#QtGui.QGraphicsPathItem.__init__(self)
|
|
||||||
#SpotItem.__init__(self, data, plot)
|
|
||||||
|
|
||||||
#def updateItem(self):
|
|
||||||
#QtGui.QGraphicsPathItem.setPath(self, Symbols[self.symbol()])
|
|
||||||
#QtGui.QGraphicsPathItem.setPen(self, self.pen())
|
|
||||||
#QtGui.QGraphicsPathItem.setBrush(self, self.brush())
|
|
||||||
#size = self.size()
|
|
||||||
#self.resetTransform()
|
|
||||||
#self.scale(size, size)
|
|
||||||
|
@ -6,17 +6,11 @@ it is possible to place any widget into its own window by simply calling its
|
|||||||
show() method.
|
show() method.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .Qt import QtCore, QtGui
|
from .Qt import QtCore, QtGui, mkQApp
|
||||||
from .widgets.PlotWidget import *
|
from .widgets.PlotWidget import *
|
||||||
from .imageview import *
|
from .imageview import *
|
||||||
from .widgets.GraphicsLayoutWidget import GraphicsLayoutWidget
|
from .widgets.GraphicsLayoutWidget import GraphicsLayoutWidget
|
||||||
from .widgets.GraphicsView import GraphicsView
|
from .widgets.GraphicsView import GraphicsView
|
||||||
QAPP = None
|
|
||||||
|
|
||||||
def mkQApp():
|
|
||||||
if QtGui.QApplication.instance() is None:
|
|
||||||
global QAPP
|
|
||||||
QAPP = QtGui.QApplication([])
|
|
||||||
|
|
||||||
|
|
||||||
class GraphicsWindow(GraphicsLayoutWidget):
|
class GraphicsWindow(GraphicsLayoutWidget):
|
||||||
|
@ -637,8 +637,12 @@ class ImageView(QtGui.QWidget):
|
|||||||
|
|
||||||
cax = self.axes['c']
|
cax = self.axes['c']
|
||||||
if cax is None:
|
if cax is None:
|
||||||
|
if data.size == 0:
|
||||||
|
return [(0, 0)]
|
||||||
return [(float(nanmin(data)), float(nanmax(data)))]
|
return [(float(nanmin(data)), float(nanmax(data)))]
|
||||||
else:
|
else:
|
||||||
|
if data.size == 0:
|
||||||
|
return [(0, 0)] * data.shape[-1]
|
||||||
return [(float(nanmin(data.take(i, axis=cax))),
|
return [(float(nanmin(data.take(i, axis=cax))),
|
||||||
float(nanmax(data.take(i, axis=cax)))) for i in range(data.shape[-1])]
|
float(nanmax(data.take(i, axis=cax)))) for i in range(data.shape[-1])]
|
||||||
|
|
||||||
|
@ -195,6 +195,8 @@ class Parallelize(object):
|
|||||||
finally:
|
finally:
|
||||||
if self.showProgress:
|
if self.showProgress:
|
||||||
self.progressDlg.__exit__(None, None, None)
|
self.progressDlg.__exit__(None, None, None)
|
||||||
|
for ch in self.childs:
|
||||||
|
ch.join()
|
||||||
if len(self.exitCodes) < len(self.childs):
|
if len(self.exitCodes) < len(self.childs):
|
||||||
raise Exception("Parallelizer started %d processes but only received exit codes from %d." % (len(self.childs), len(self.exitCodes)))
|
raise Exception("Parallelizer started %d processes but only received exit codes from %d." % (len(self.childs), len(self.exitCodes)))
|
||||||
for code in self.exitCodes:
|
for code in self.exitCodes:
|
||||||
|
@ -165,6 +165,7 @@ class Process(RemoteEventHandler):
|
|||||||
if timeout is not None and time.time() - start > timeout:
|
if timeout is not None and time.time() - start > timeout:
|
||||||
raise Exception('Timed out waiting for remote process to end.')
|
raise Exception('Timed out waiting for remote process to end.')
|
||||||
time.sleep(0.05)
|
time.sleep(0.05)
|
||||||
|
self.conn.close()
|
||||||
self.debugMsg('Child process exited. (%d)' % self.proc.returncode)
|
self.debugMsg('Child process exited. (%d)' % self.proc.returncode)
|
||||||
|
|
||||||
def debugMsg(self, msg, *args):
|
def debugMsg(self, msg, *args):
|
||||||
@ -341,6 +342,7 @@ class ForkedProcess(RemoteEventHandler):
|
|||||||
except OSError: ## probably remote process has already quit
|
except OSError: ## probably remote process has already quit
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
self.conn.close() # don't leak file handles!
|
||||||
self.hasJoined = True
|
self.hasJoined = True
|
||||||
|
|
||||||
def kill(self):
|
def kill(self):
|
||||||
|
@ -16,9 +16,13 @@ class GLViewWidget(QtOpenGL.QGLWidget):
|
|||||||
- Axis/grid display
|
- Axis/grid display
|
||||||
- Export options
|
- Export options
|
||||||
|
|
||||||
|
|
||||||
|
High-DPI displays: Qt5 should automatically detect the correct resolution.
|
||||||
|
For Qt4, specify the ``devicePixelRatio`` argument when initializing the
|
||||||
|
widget (usually this value is 1-2).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None, devicePixelRatio=None):
|
||||||
global ShareWidget
|
global ShareWidget
|
||||||
|
|
||||||
if ShareWidget is None:
|
if ShareWidget is None:
|
||||||
@ -37,6 +41,7 @@ class GLViewWidget(QtOpenGL.QGLWidget):
|
|||||||
'azimuth': 45, ## camera's azimuthal angle in degrees
|
'azimuth': 45, ## camera's azimuthal angle in degrees
|
||||||
## (rotation around z-axis 0 points along x-axis)
|
## (rotation around z-axis 0 points along x-axis)
|
||||||
'viewport': None, ## glViewport params; None == whole widget
|
'viewport': None, ## glViewport params; None == whole widget
|
||||||
|
'devicePixelRatio': devicePixelRatio,
|
||||||
}
|
}
|
||||||
self.setBackgroundColor('k')
|
self.setBackgroundColor('k')
|
||||||
self.items = []
|
self.items = []
|
||||||
@ -79,10 +84,21 @@ class GLViewWidget(QtOpenGL.QGLWidget):
|
|||||||
|
|
||||||
def getViewport(self):
|
def getViewport(self):
|
||||||
vp = self.opts['viewport']
|
vp = self.opts['viewport']
|
||||||
|
dpr = self.devicePixelRatio()
|
||||||
if vp is None:
|
if vp is None:
|
||||||
return (0, 0, self.width(), self.height())
|
return (0, 0, int(self.width() * dpr), int(self.height() * dpr))
|
||||||
else:
|
else:
|
||||||
return vp
|
return tuple([int(x * dpr) for x in vp])
|
||||||
|
|
||||||
|
def devicePixelRatio(self):
|
||||||
|
dpr = self.opts['devicePixelRatio']
|
||||||
|
if dpr is not None:
|
||||||
|
return dpr
|
||||||
|
|
||||||
|
if hasattr(QtOpenGL.QGLWidget, 'devicePixelRatio'):
|
||||||
|
return QtOpenGL.QGLWidget.devicePixelRatio(self)
|
||||||
|
else:
|
||||||
|
return 1.0
|
||||||
|
|
||||||
def resizeGL(self, w, h):
|
def resizeGL(self, w, h):
|
||||||
pass
|
pass
|
||||||
@ -99,7 +115,8 @@ class GLViewWidget(QtOpenGL.QGLWidget):
|
|||||||
def projectionMatrix(self, region=None):
|
def projectionMatrix(self, region=None):
|
||||||
# Xw = (Xnd + 1) * width/2 + X
|
# Xw = (Xnd + 1) * width/2 + X
|
||||||
if region is None:
|
if region is None:
|
||||||
region = (0, 0, self.width(), self.height())
|
dpr = self.devicePixelRatio()
|
||||||
|
region = (0, 0, self.width() * dpr, self.height() * dpr)
|
||||||
|
|
||||||
x0, y0, w, h = self.getViewport()
|
x0, y0, w, h = self.getViewport()
|
||||||
dist = self.opts['distance']
|
dist = self.opts['distance']
|
||||||
|
@ -485,7 +485,7 @@ class MeshData(object):
|
|||||||
if isinstance(radius, int):
|
if isinstance(radius, int):
|
||||||
radius = [radius, radius] # convert to list
|
radius = [radius, radius] # convert to list
|
||||||
## compute vertexes
|
## compute vertexes
|
||||||
th = np.linspace(2 * np.pi, 0, cols).reshape(1, cols)
|
th = np.linspace(2 * np.pi, (2 * np.pi)/cols, cols).reshape(1, cols)
|
||||||
r = np.linspace(radius[0],radius[1],num=rows+1, endpoint=True).reshape(rows+1, 1) # radius as a function of z
|
r = np.linspace(radius[0],radius[1],num=rows+1, endpoint=True).reshape(rows+1, 1) # radius as a function of z
|
||||||
verts[...,2] = np.linspace(0, length, num=rows+1, endpoint=True).reshape(rows+1, 1) # z
|
verts[...,2] = np.linspace(0, length, num=rows+1, endpoint=True).reshape(rows+1, 1) # z
|
||||||
if offset:
|
if offset:
|
||||||
|
@ -10,10 +10,10 @@ class GLGridItem(GLGraphicsItem):
|
|||||||
"""
|
"""
|
||||||
**Bases:** :class:`GLGraphicsItem <pyqtgraph.opengl.GLGraphicsItem>`
|
**Bases:** :class:`GLGraphicsItem <pyqtgraph.opengl.GLGraphicsItem>`
|
||||||
|
|
||||||
Displays a wire-grame grid.
|
Displays a wire-frame grid.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, size=None, color=None, antialias=True, glOptions='translucent'):
|
def __init__(self, size=None, color=(1, 1, 1, .3), antialias=True, glOptions='translucent'):
|
||||||
GLGraphicsItem.__init__(self)
|
GLGraphicsItem.__init__(self)
|
||||||
self.setGLOptions(glOptions)
|
self.setGLOptions(glOptions)
|
||||||
self.antialias = antialias
|
self.antialias = antialias
|
||||||
@ -21,6 +21,7 @@ class GLGridItem(GLGraphicsItem):
|
|||||||
size = QtGui.QVector3D(20,20,1)
|
size = QtGui.QVector3D(20,20,1)
|
||||||
self.setSize(size=size)
|
self.setSize(size=size)
|
||||||
self.setSpacing(1, 1, 1)
|
self.setSpacing(1, 1, 1)
|
||||||
|
self.color = color
|
||||||
|
|
||||||
def setSize(self, x=None, y=None, z=None, size=None):
|
def setSize(self, x=None, y=None, z=None, size=None):
|
||||||
"""
|
"""
|
||||||
@ -67,7 +68,7 @@ class GLGridItem(GLGraphicsItem):
|
|||||||
xs,ys,zs = self.spacing()
|
xs,ys,zs = self.spacing()
|
||||||
xvals = np.arange(-x/2., x/2. + xs*0.001, xs)
|
xvals = np.arange(-x/2., x/2. + xs*0.001, xs)
|
||||||
yvals = np.arange(-y/2., y/2. + ys*0.001, ys)
|
yvals = np.arange(-y/2., y/2. + ys*0.001, ys)
|
||||||
glColor4f(1, 1, 1, .3)
|
glColor4f(*self.color)
|
||||||
for x in xvals:
|
for x in xvals:
|
||||||
glVertex3f(x, yvals[0], 0)
|
glVertex3f(x, yvals[0], 0)
|
||||||
glVertex3f(x, yvals[-1], 0)
|
glVertex3f(x, yvals[-1], 0)
|
||||||
|
@ -152,6 +152,8 @@ class GLScatterPlotItem(GLGraphicsItem):
|
|||||||
glDisableClientState(GL_VERTEX_ARRAY)
|
glDisableClientState(GL_VERTEX_ARRAY)
|
||||||
glDisableClientState(GL_COLOR_ARRAY)
|
glDisableClientState(GL_COLOR_ARRAY)
|
||||||
#posVBO.unbind()
|
#posVBO.unbind()
|
||||||
|
##fixes #145
|
||||||
|
glDisable( GL_TEXTURE_2D )
|
||||||
|
|
||||||
#for i in range(len(self.pos)):
|
#for i in range(len(self.pos)):
|
||||||
#pos = self.pos[i]
|
#pos = self.pos[i]
|
||||||
|
@ -105,6 +105,7 @@ class WidgetParameterItem(ParameterItem):
|
|||||||
if t == 'int':
|
if t == 'int':
|
||||||
defs['int'] = True
|
defs['int'] = True
|
||||||
defs['minStep'] = 1.0
|
defs['minStep'] = 1.0
|
||||||
|
defs['format'] = '{value:d}'
|
||||||
for k in defs:
|
for k in defs:
|
||||||
if k in opts:
|
if k in opts:
|
||||||
defs[k] = opts[k]
|
defs[k] = opts[k]
|
||||||
|
@ -21,13 +21,17 @@ Does NOT:
|
|||||||
print module.someObject
|
print module.someObject
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
import inspect, os, sys, gc, traceback
|
import inspect, os, sys, gc, traceback, types
|
||||||
try:
|
|
||||||
import __builtin__ as builtins
|
|
||||||
except ImportError:
|
|
||||||
import builtins
|
|
||||||
from .debug import printExc
|
from .debug import printExc
|
||||||
|
try:
|
||||||
|
from importlib import reload as orig_reload
|
||||||
|
except ImportError:
|
||||||
|
orig_reload = reload
|
||||||
|
|
||||||
|
|
||||||
|
py3 = sys.version_info >= (3,)
|
||||||
|
|
||||||
|
|
||||||
def reloadAll(prefix=None, debug=False):
|
def reloadAll(prefix=None, debug=False):
|
||||||
"""Automatically reload everything whose __file__ begins with prefix.
|
"""Automatically reload everything whose __file__ begins with prefix.
|
||||||
@ -79,7 +83,7 @@ def reload(module, debug=False, lists=False, dicts=False):
|
|||||||
|
|
||||||
## make a copy of the old module dictionary, reload, then grab the new module dictionary for comparison
|
## make a copy of the old module dictionary, reload, then grab the new module dictionary for comparison
|
||||||
oldDict = module.__dict__.copy()
|
oldDict = module.__dict__.copy()
|
||||||
builtins.reload(module)
|
orig_reload(module)
|
||||||
newDict = module.__dict__
|
newDict = module.__dict__
|
||||||
|
|
||||||
## Allow modules access to the old dictionary after they reload
|
## Allow modules access to the old dictionary after they reload
|
||||||
@ -97,6 +101,8 @@ def reload(module, debug=False, lists=False, dicts=False):
|
|||||||
if debug:
|
if debug:
|
||||||
print(" Updating class %s.%s (0x%x -> 0x%x)" % (module.__name__, k, id(old), id(new)))
|
print(" Updating class %s.%s (0x%x -> 0x%x)" % (module.__name__, k, id(old), id(new)))
|
||||||
updateClass(old, new, debug)
|
updateClass(old, new, debug)
|
||||||
|
# don't put this inside updateClass because it is reentrant.
|
||||||
|
new.__previous_reload_version__ = old
|
||||||
|
|
||||||
elif inspect.isfunction(old):
|
elif inspect.isfunction(old):
|
||||||
depth = updateFunction(old, new, debug)
|
depth = updateFunction(old, new, debug)
|
||||||
@ -127,6 +133,9 @@ def updateFunction(old, new, debug, depth=0, visited=None):
|
|||||||
|
|
||||||
old.__code__ = new.__code__
|
old.__code__ = new.__code__
|
||||||
old.__defaults__ = new.__defaults__
|
old.__defaults__ = new.__defaults__
|
||||||
|
if hasattr(old, '__kwdefaults'):
|
||||||
|
old.__kwdefaults__ = new.__kwdefaults__
|
||||||
|
old.__doc__ = new.__doc__
|
||||||
|
|
||||||
if visited is None:
|
if visited is None:
|
||||||
visited = []
|
visited = []
|
||||||
@ -151,8 +160,9 @@ def updateFunction(old, new, debug, depth=0, visited=None):
|
|||||||
## For classes:
|
## For classes:
|
||||||
## 1) find all instances of the old class and set instance.__class__ to the new class
|
## 1) find all instances of the old class and set instance.__class__ to the new class
|
||||||
## 2) update all old class methods to use code from the new class methods
|
## 2) update all old class methods to use code from the new class methods
|
||||||
def updateClass(old, new, debug):
|
|
||||||
|
|
||||||
|
|
||||||
|
def updateClass(old, new, debug):
|
||||||
## Track town all instances and subclasses of old
|
## Track town all instances and subclasses of old
|
||||||
refs = gc.get_referrers(old)
|
refs = gc.get_referrers(old)
|
||||||
for ref in refs:
|
for ref in refs:
|
||||||
@ -174,13 +184,20 @@ def updateClass(old, new, debug):
|
|||||||
## This seems to work. Is there any reason not to?
|
## This seems to work. Is there any reason not to?
|
||||||
## Note that every time we reload, the class hierarchy becomes more complex.
|
## Note that every time we reload, the class hierarchy becomes more complex.
|
||||||
## (and I presume this may slow things down?)
|
## (and I presume this may slow things down?)
|
||||||
ref.__bases__ = ref.__bases__[:ind] + (new,old) + ref.__bases__[ind+1:]
|
newBases = ref.__bases__[:ind] + (new,old) + ref.__bases__[ind+1:]
|
||||||
|
try:
|
||||||
|
ref.__bases__ = newBases
|
||||||
|
except TypeError:
|
||||||
|
print(" Error setting bases for class %s" % ref)
|
||||||
|
print(" old bases: %s" % repr(ref.__bases__))
|
||||||
|
print(" new bases: %s" % repr(newBases))
|
||||||
|
raise
|
||||||
if debug:
|
if debug:
|
||||||
print(" Changed superclass for %s" % safeStr(ref))
|
print(" Changed superclass for %s" % safeStr(ref))
|
||||||
#else:
|
#else:
|
||||||
#if debug:
|
#if debug:
|
||||||
#print " Ignoring reference", type(ref)
|
#print " Ignoring reference", type(ref)
|
||||||
except:
|
except Exception:
|
||||||
print("Error updating reference (%s) for class change (%s -> %s)" % (safeStr(ref), safeStr(old), safeStr(new)))
|
print("Error updating reference (%s) for class change (%s -> %s)" % (safeStr(ref), safeStr(old), safeStr(new)))
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@ -189,7 +206,8 @@ def updateClass(old, new, debug):
|
|||||||
## but it fixes a few specific cases (pyqt signals, for one)
|
## but it fixes a few specific cases (pyqt signals, for one)
|
||||||
for attr in dir(old):
|
for attr in dir(old):
|
||||||
oa = getattr(old, attr)
|
oa = getattr(old, attr)
|
||||||
if inspect.ismethod(oa):
|
if (py3 and inspect.isfunction(oa)) or inspect.ismethod(oa):
|
||||||
|
# note python2 has unbound methods, whereas python3 just uses plain functions
|
||||||
try:
|
try:
|
||||||
na = getattr(new, attr)
|
na = getattr(new, attr)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
@ -197,9 +215,14 @@ def updateClass(old, new, debug):
|
|||||||
print(" Skipping method update for %s; new class does not have this attribute" % attr)
|
print(" Skipping method update for %s; new class does not have this attribute" % attr)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if hasattr(oa, 'im_func') and hasattr(na, 'im_func') and oa.__func__ is not na.__func__:
|
ofunc = getattr(oa, '__func__', oa) # in py2 we have to get the __func__ from unbound method,
|
||||||
depth = updateFunction(oa.__func__, na.__func__, debug)
|
nfunc = getattr(na, '__func__', na) # in py3 the attribute IS the function
|
||||||
#oa.im_class = new ## bind old method to new class ## not allowed
|
|
||||||
|
if ofunc is not nfunc:
|
||||||
|
depth = updateFunction(ofunc, nfunc, debug)
|
||||||
|
if not hasattr(nfunc, '__previous_reload_method__'):
|
||||||
|
nfunc.__previous_reload_method__ = oa # important for managing signal connection
|
||||||
|
#oa.__class__ = new ## bind old method to new class ## not allowed
|
||||||
if debug:
|
if debug:
|
||||||
extra = ""
|
extra = ""
|
||||||
if depth > 0:
|
if depth > 0:
|
||||||
@ -208,6 +231,8 @@ def updateClass(old, new, debug):
|
|||||||
|
|
||||||
## And copy in new functions that didn't exist previously
|
## And copy in new functions that didn't exist previously
|
||||||
for attr in dir(new):
|
for attr in dir(new):
|
||||||
|
if attr == '__previous_reload_version__':
|
||||||
|
continue
|
||||||
if not hasattr(old, attr):
|
if not hasattr(old, attr):
|
||||||
if debug:
|
if debug:
|
||||||
print(" Adding missing attribute %s" % attr)
|
print(" Adding missing attribute %s" % attr)
|
||||||
@ -223,14 +248,37 @@ def updateClass(old, new, debug):
|
|||||||
def safeStr(obj):
|
def safeStr(obj):
|
||||||
try:
|
try:
|
||||||
s = str(obj)
|
s = str(obj)
|
||||||
except:
|
except Exception:
|
||||||
try:
|
try:
|
||||||
s = repr(obj)
|
s = repr(obj)
|
||||||
except:
|
except Exception:
|
||||||
s = "<instance of %s at 0x%x>" % (safeStr(type(obj)), id(obj))
|
s = "<instance of %s at 0x%x>" % (safeStr(type(obj)), id(obj))
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def getPreviousVersion(obj):
|
||||||
|
"""Return the previous version of *obj*, or None if this object has not
|
||||||
|
been reloaded.
|
||||||
|
"""
|
||||||
|
if isinstance(obj, type) or inspect.isfunction(obj):
|
||||||
|
return getattr(obj, '__previous_reload_version__', None)
|
||||||
|
elif inspect.ismethod(obj):
|
||||||
|
if obj.__self__ is None:
|
||||||
|
# unbound method
|
||||||
|
return getattr(obj.__func__, '__previous_reload_method__', None)
|
||||||
|
else:
|
||||||
|
oldmethod = getattr(obj.__func__, '__previous_reload_method__', None)
|
||||||
|
if oldmethod is None:
|
||||||
|
return None
|
||||||
|
self = obj.__self__
|
||||||
|
oldfunc = getattr(oldmethod, '__func__', oldmethod)
|
||||||
|
if hasattr(oldmethod, 'im_class'):
|
||||||
|
# python 2
|
||||||
|
cls = oldmethod.im_class
|
||||||
|
return types.MethodType(oldfunc, self, cls)
|
||||||
|
else:
|
||||||
|
# python 3
|
||||||
|
return types.MethodType(oldfunc, self)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
116
pyqtgraph/tests/test_reload.py
Normal file
116
pyqtgraph/tests/test_reload.py
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import tempfile, os, sys, shutil
|
||||||
|
import pyqtgraph as pg
|
||||||
|
import pyqtgraph.reload
|
||||||
|
|
||||||
|
|
||||||
|
pgpath = os.path.join(os.path.dirname(pg.__file__), '..')
|
||||||
|
|
||||||
|
# make temporary directory to write module code
|
||||||
|
path = None
|
||||||
|
|
||||||
|
def setup_module():
|
||||||
|
# make temporary directory to write module code
|
||||||
|
global path
|
||||||
|
path = tempfile.mkdtemp()
|
||||||
|
sys.path.insert(0, path)
|
||||||
|
|
||||||
|
def teardown_module():
|
||||||
|
global path
|
||||||
|
shutil.rmtree(path)
|
||||||
|
sys.path.remove(path)
|
||||||
|
|
||||||
|
|
||||||
|
code = """
|
||||||
|
import sys
|
||||||
|
sys.path.append('{path}')
|
||||||
|
|
||||||
|
import pyqtgraph as pg
|
||||||
|
|
||||||
|
class C(pg.QtCore.QObject):
|
||||||
|
sig = pg.QtCore.Signal()
|
||||||
|
def fn(self):
|
||||||
|
print("{msg}")
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def remove_cache(mod):
|
||||||
|
if os.path.isfile(mod+'c'):
|
||||||
|
os.remove(mod+'c')
|
||||||
|
cachedir = os.path.join(os.path.dirname(mod), '__pycache__')
|
||||||
|
if os.path.isdir(cachedir):
|
||||||
|
shutil.rmtree(cachedir)
|
||||||
|
|
||||||
|
|
||||||
|
def test_reload():
|
||||||
|
py3 = sys.version_info >= (3,)
|
||||||
|
|
||||||
|
# write a module
|
||||||
|
mod = os.path.join(path, 'reload_test_mod.py')
|
||||||
|
print("\nRELOAD FILE:", mod)
|
||||||
|
open(mod, 'w').write(code.format(path=pgpath, msg="C.fn() Version1"))
|
||||||
|
|
||||||
|
# import the new module
|
||||||
|
import reload_test_mod
|
||||||
|
print("RELOAD MOD:", reload_test_mod.__file__)
|
||||||
|
|
||||||
|
c = reload_test_mod.C()
|
||||||
|
c.sig.connect(c.fn)
|
||||||
|
if py3:
|
||||||
|
v1 = (reload_test_mod.C, reload_test_mod.C.sig, reload_test_mod.C.fn, c.sig, c.fn, c.fn.__func__)
|
||||||
|
else:
|
||||||
|
v1 = (reload_test_mod.C, reload_test_mod.C.sig, reload_test_mod.C.fn, reload_test_mod.C.fn.__func__, c.sig, c.fn, c.fn.__func__)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# write again and reload
|
||||||
|
open(mod, 'w').write(code.format(path=pgpath, msg="C.fn() Version2"))
|
||||||
|
remove_cache(mod)
|
||||||
|
pg.reload.reloadAll(path, debug=True)
|
||||||
|
if py3:
|
||||||
|
v2 = (reload_test_mod.C, reload_test_mod.C.sig, reload_test_mod.C.fn, c.sig, c.fn, c.fn.__func__)
|
||||||
|
else:
|
||||||
|
v2 = (reload_test_mod.C, reload_test_mod.C.sig, reload_test_mod.C.fn, reload_test_mod.C.fn.__func__, c.sig, c.fn, c.fn.__func__)
|
||||||
|
|
||||||
|
if not py3:
|
||||||
|
assert c.fn.im_class is v2[0]
|
||||||
|
oldcfn = pg.reload.getPreviousVersion(c.fn)
|
||||||
|
if oldcfn is None:
|
||||||
|
# Function did not reload; are we using pytest's assertion rewriting?
|
||||||
|
raise Exception("Function did not reload. (This can happen when using py.test"
|
||||||
|
" with assertion rewriting; use --assert=plain for this test.)")
|
||||||
|
if py3:
|
||||||
|
assert oldcfn.__func__ is v1[2]
|
||||||
|
else:
|
||||||
|
assert oldcfn.im_class is v1[0]
|
||||||
|
assert oldcfn.__func__ is v1[2].__func__
|
||||||
|
assert oldcfn.__self__ is c
|
||||||
|
|
||||||
|
|
||||||
|
# write again and reload
|
||||||
|
open(mod, 'w').write(code.format(path=pgpath, msg="C.fn() Version2"))
|
||||||
|
remove_cache(mod)
|
||||||
|
pg.reload.reloadAll(path, debug=True)
|
||||||
|
if py3:
|
||||||
|
v3 = (reload_test_mod.C, reload_test_mod.C.sig, reload_test_mod.C.fn, c.sig, c.fn, c.fn.__func__)
|
||||||
|
else:
|
||||||
|
v3 = (reload_test_mod.C, reload_test_mod.C.sig, reload_test_mod.C.fn, reload_test_mod.C.fn.__func__, c.sig, c.fn, c.fn.__func__)
|
||||||
|
|
||||||
|
#for i in range(len(old)):
|
||||||
|
#print id(old[i]), id(new1[i]), id(new2[i]), old[i], new1[i]
|
||||||
|
|
||||||
|
cfn1 = pg.reload.getPreviousVersion(c.fn)
|
||||||
|
cfn2 = pg.reload.getPreviousVersion(cfn1)
|
||||||
|
|
||||||
|
if py3:
|
||||||
|
assert cfn1.__func__ is v2[2]
|
||||||
|
assert cfn2.__func__ is v1[2]
|
||||||
|
else:
|
||||||
|
assert cfn1.__func__ is v2[2].__func__
|
||||||
|
assert cfn2.__func__ is v1[2].__func__
|
||||||
|
assert cfn1.im_class is v2[0]
|
||||||
|
assert cfn2.im_class is v1[0]
|
||||||
|
assert cfn1.__self__ is c
|
||||||
|
assert cfn2.__self__ is c
|
||||||
|
|
||||||
|
pg.functions.disconnect(c.sig, c.fn)
|
||||||
|
|
@ -9,16 +9,22 @@ class BusyCursor(object):
|
|||||||
with pyqtgraph.BusyCursor():
|
with pyqtgraph.BusyCursor():
|
||||||
doLongOperation()
|
doLongOperation()
|
||||||
|
|
||||||
May be nested.
|
May be nested. If called from a non-gui thread, then the cursor will not be affected.
|
||||||
"""
|
"""
|
||||||
active = []
|
active = []
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
|
app = QtCore.QCoreApplication.instance()
|
||||||
|
isGuiThread = (app is not None) and (QtCore.QThread.currentThread() == app.thread())
|
||||||
|
if isGuiThread and QtGui.QApplication.instance() is not None:
|
||||||
QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))
|
QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))
|
||||||
BusyCursor.active.append(self)
|
BusyCursor.active.append(self)
|
||||||
|
self._active = True
|
||||||
|
else:
|
||||||
|
self._active = False
|
||||||
|
|
||||||
def __exit__(self, *args):
|
def __exit__(self, *args):
|
||||||
|
if self._active:
|
||||||
BusyCursor.active.pop(-1)
|
BusyCursor.active.pop(-1)
|
||||||
if len(BusyCursor.active) == 0:
|
|
||||||
QtGui.QApplication.restoreOverrideCursor()
|
QtGui.QApplication.restoreOverrideCursor()
|
||||||
|
|
@ -42,6 +42,11 @@ class ColorMapWidget(ptree.ParameterTree):
|
|||||||
def restoreState(self, state):
|
def restoreState(self, state):
|
||||||
self.params.restoreState(state)
|
self.params.restoreState(state)
|
||||||
|
|
||||||
|
def addColorMap(self, name):
|
||||||
|
"""Add a new color mapping and return the created parameter.
|
||||||
|
"""
|
||||||
|
return self.params.addNew(name)
|
||||||
|
|
||||||
|
|
||||||
class ColorMapParameter(ptree.types.GroupParameter):
|
class ColorMapParameter(ptree.types.GroupParameter):
|
||||||
sigColorMapChanged = QtCore.Signal(object)
|
sigColorMapChanged = QtCore.Signal(object)
|
||||||
|
@ -3,13 +3,18 @@ from .. import parametertree as ptree
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from ..pgcollections import OrderedDict
|
from ..pgcollections import OrderedDict
|
||||||
from .. import functions as fn
|
from .. import functions as fn
|
||||||
|
from ..python2_3 import basestring
|
||||||
|
|
||||||
__all__ = ['DataFilterWidget']
|
__all__ = ['DataFilterWidget']
|
||||||
|
|
||||||
|
|
||||||
class DataFilterWidget(ptree.ParameterTree):
|
class DataFilterWidget(ptree.ParameterTree):
|
||||||
"""
|
"""
|
||||||
This class allows the user to filter multi-column data sets by specifying
|
This class allows the user to filter multi-column data sets by specifying
|
||||||
multiple criteria
|
multiple criteria
|
||||||
|
|
||||||
|
Wraps methods from DataFilterParameter: setFields, generateMask,
|
||||||
|
filterData, and describe.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
sigFilterChanged = QtCore.Signal(object)
|
sigFilterChanged = QtCore.Signal(object)
|
||||||
@ -22,6 +27,7 @@ class DataFilterWidget(ptree.ParameterTree):
|
|||||||
self.params.sigTreeStateChanged.connect(self.filterChanged)
|
self.params.sigTreeStateChanged.connect(self.filterChanged)
|
||||||
|
|
||||||
self.setFields = self.params.setFields
|
self.setFields = self.params.setFields
|
||||||
|
self.generateMask = self.params.generateMask
|
||||||
self.filterData = self.params.filterData
|
self.filterData = self.params.filterData
|
||||||
self.describe = self.params.describe
|
self.describe = self.params.describe
|
||||||
|
|
||||||
@ -31,9 +37,15 @@ class DataFilterWidget(ptree.ParameterTree):
|
|||||||
def parameters(self):
|
def parameters(self):
|
||||||
return self.params
|
return self.params
|
||||||
|
|
||||||
|
def addFilter(self, name):
|
||||||
|
"""Add a new filter and return the created parameter item.
|
||||||
|
"""
|
||||||
|
return self.params.addNew(name)
|
||||||
|
|
||||||
|
|
||||||
class DataFilterParameter(ptree.types.GroupParameter):
|
class DataFilterParameter(ptree.types.GroupParameter):
|
||||||
|
"""A parameter group that specifies a set of filters to apply to tabular data.
|
||||||
|
"""
|
||||||
sigFilterChanged = QtCore.Signal(object)
|
sigFilterChanged = QtCore.Signal(object)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -47,25 +59,46 @@ class DataFilterParameter(ptree.types.GroupParameter):
|
|||||||
def addNew(self, name):
|
def addNew(self, name):
|
||||||
mode = self.fields[name].get('mode', 'range')
|
mode = self.fields[name].get('mode', 'range')
|
||||||
if mode == 'range':
|
if mode == 'range':
|
||||||
self.addChild(RangeFilterItem(name, self.fields[name]))
|
child = self.addChild(RangeFilterItem(name, self.fields[name]))
|
||||||
elif mode == 'enum':
|
elif mode == 'enum':
|
||||||
self.addChild(EnumFilterItem(name, self.fields[name]))
|
child = self.addChild(EnumFilterItem(name, self.fields[name]))
|
||||||
|
return child
|
||||||
|
|
||||||
def fieldNames(self):
|
def fieldNames(self):
|
||||||
return self.fields.keys()
|
return self.fields.keys()
|
||||||
|
|
||||||
def setFields(self, fields):
|
def setFields(self, fields):
|
||||||
|
"""Set the list of fields that are available to be filtered.
|
||||||
|
|
||||||
|
*fields* must be a dict or list of tuples that maps field names
|
||||||
|
to a specification describing the field. Each specification is
|
||||||
|
itself a dict with either ``'mode':'range'`` or ``'mode':'enum'``::
|
||||||
|
|
||||||
|
filter.setFields([
|
||||||
|
('field1', {'mode': 'range'}),
|
||||||
|
('field2', {'mode': 'enum', 'values': ['val1', 'val2', 'val3']}),
|
||||||
|
('field3', {'mode': 'enum', 'values': {'val1':True, 'val2':False, 'val3':True}}),
|
||||||
|
])
|
||||||
|
"""
|
||||||
self.fields = OrderedDict(fields)
|
self.fields = OrderedDict(fields)
|
||||||
names = self.fieldNames()
|
names = self.fieldNames()
|
||||||
self.setAddList(names)
|
self.setAddList(names)
|
||||||
|
|
||||||
|
# update any existing filters
|
||||||
|
for ch in self.children():
|
||||||
|
name = ch.fieldName
|
||||||
|
if name in fields:
|
||||||
|
ch.updateFilter(fields[name])
|
||||||
|
|
||||||
def filterData(self, data):
|
def filterData(self, data):
|
||||||
if len(data) == 0:
|
if len(data) == 0:
|
||||||
return data
|
return data
|
||||||
return data[self.generateMask(data)]
|
return data[self.generateMask(data)]
|
||||||
|
|
||||||
def generateMask(self, data):
|
def generateMask(self, data):
|
||||||
|
"""Return a boolean mask indicating whether each item in *data* passes
|
||||||
|
the filter critera.
|
||||||
|
"""
|
||||||
mask = np.ones(len(data), dtype=bool)
|
mask = np.ones(len(data), dtype=bool)
|
||||||
if len(data) == 0:
|
if len(data) == 0:
|
||||||
return mask
|
return mask
|
||||||
@ -89,6 +122,7 @@ class DataFilterParameter(ptree.types.GroupParameter):
|
|||||||
desc.append(fp.describe())
|
desc.append(fp.describe())
|
||||||
return desc
|
return desc
|
||||||
|
|
||||||
|
|
||||||
class RangeFilterItem(ptree.types.SimpleParameter):
|
class RangeFilterItem(ptree.types.SimpleParameter):
|
||||||
def __init__(self, name, opts):
|
def __init__(self, name, opts):
|
||||||
self.fieldName = name
|
self.fieldName = name
|
||||||
@ -110,24 +144,16 @@ class RangeFilterItem(ptree.types.SimpleParameter):
|
|||||||
def describe(self):
|
def describe(self):
|
||||||
return "%s < %s < %s" % (fn.siFormat(self['Min'], suffix=self.units), self.fieldName, fn.siFormat(self['Max'], suffix=self.units))
|
return "%s < %s < %s" % (fn.siFormat(self['Min'], suffix=self.units), self.fieldName, fn.siFormat(self['Max'], suffix=self.units))
|
||||||
|
|
||||||
|
def updateFilter(self, opts):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class EnumFilterItem(ptree.types.SimpleParameter):
|
class EnumFilterItem(ptree.types.SimpleParameter):
|
||||||
def __init__(self, name, opts):
|
def __init__(self, name, opts):
|
||||||
self.fieldName = name
|
self.fieldName = name
|
||||||
vals = opts.get('values', [])
|
|
||||||
childs = []
|
|
||||||
if isinstance(vals, list):
|
|
||||||
vals = OrderedDict([(v,str(v)) for v in vals])
|
|
||||||
for val,vname in vals.items():
|
|
||||||
ch = ptree.Parameter.create(name=vname, type='bool', value=True)
|
|
||||||
ch.maskValue = val
|
|
||||||
childs.append(ch)
|
|
||||||
ch = ptree.Parameter.create(name='(other)', type='bool', value=True)
|
|
||||||
ch.maskValue = '__other__'
|
|
||||||
childs.append(ch)
|
|
||||||
|
|
||||||
ptree.types.SimpleParameter.__init__(self,
|
ptree.types.SimpleParameter.__init__(self,
|
||||||
name=name, autoIncrementName=True, type='bool', value=True, removable=True, renamable=True,
|
name=name, autoIncrementName=True, type='bool', value=True, removable=True, renamable=True)
|
||||||
children=childs)
|
self.setEnumVals(opts)
|
||||||
|
|
||||||
def generateMask(self, data, startMask):
|
def generateMask(self, data, startMask):
|
||||||
vals = data[self.fieldName][startMask]
|
vals = data[self.fieldName][startMask]
|
||||||
@ -148,3 +174,37 @@ class EnumFilterItem(ptree.types.SimpleParameter):
|
|||||||
def describe(self):
|
def describe(self):
|
||||||
vals = [ch.name() for ch in self if ch.value() is True]
|
vals = [ch.name() for ch in self if ch.value() is True]
|
||||||
return "%s: %s" % (self.fieldName, ', '.join(vals))
|
return "%s: %s" % (self.fieldName, ', '.join(vals))
|
||||||
|
|
||||||
|
def updateFilter(self, opts):
|
||||||
|
self.setEnumVals(opts)
|
||||||
|
|
||||||
|
def setEnumVals(self, opts):
|
||||||
|
vals = opts.get('values', {})
|
||||||
|
|
||||||
|
prevState = {}
|
||||||
|
for ch in self.children():
|
||||||
|
prevState[ch.name()] = ch.value()
|
||||||
|
self.removeChild(ch)
|
||||||
|
|
||||||
|
if not isinstance(vals, dict):
|
||||||
|
vals = OrderedDict([(v,(str(v), True)) for v in vals])
|
||||||
|
|
||||||
|
# Each filterable value can come with either (1) a string name, (2) a bool
|
||||||
|
# indicating whether the value is enabled by default, or (3) a tuple providing
|
||||||
|
# both.
|
||||||
|
for val,valopts in vals.items():
|
||||||
|
if isinstance(valopts, bool):
|
||||||
|
enabled = valopts
|
||||||
|
vname = str(val)
|
||||||
|
elif isinstance(valopts, basestring):
|
||||||
|
enabled = True
|
||||||
|
vname = valopts
|
||||||
|
elif isinstance(valopts, tuple):
|
||||||
|
vname, enabled = valopts
|
||||||
|
|
||||||
|
ch = ptree.Parameter.create(name=vname, type='bool', value=prevState.get(vname, enabled))
|
||||||
|
ch.maskValue = val
|
||||||
|
self.addChild(ch)
|
||||||
|
ch = ptree.Parameter.create(name='(other)', type='bool', value=prevState.get('(other)', True))
|
||||||
|
ch.maskValue = '__other__'
|
||||||
|
self.addChild(ch)
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from ..Qt import QtGui, QtCore
|
from ..Qt import QtGui, QtCore
|
||||||
from ..pgcollections import OrderedDict
|
from ..pgcollections import OrderedDict
|
||||||
|
from .TableWidget import TableWidget
|
||||||
|
from ..python2_3 import asUnicode
|
||||||
import types, traceback
|
import types, traceback
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
@ -17,67 +19,106 @@ class DataTreeWidget(QtGui.QTreeWidget):
|
|||||||
Widget for displaying hierarchical python data structures
|
Widget for displaying hierarchical python data structures
|
||||||
(eg, nested dicts, lists, and arrays)
|
(eg, nested dicts, lists, and arrays)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, parent=None, data=None):
|
def __init__(self, parent=None, data=None):
|
||||||
QtGui.QTreeWidget.__init__(self, parent)
|
QtGui.QTreeWidget.__init__(self, parent)
|
||||||
self.setVerticalScrollMode(self.ScrollPerPixel)
|
self.setVerticalScrollMode(self.ScrollPerPixel)
|
||||||
self.setData(data)
|
self.setData(data)
|
||||||
self.setColumnCount(3)
|
self.setColumnCount(3)
|
||||||
self.setHeaderLabels(['key / index', 'type', 'value'])
|
self.setHeaderLabels(['key / index', 'type', 'value'])
|
||||||
|
self.setAlternatingRowColors(True)
|
||||||
|
|
||||||
def setData(self, data, hideRoot=False):
|
def setData(self, data, hideRoot=False):
|
||||||
"""data should be a dictionary."""
|
"""data should be a dictionary."""
|
||||||
self.clear()
|
self.clear()
|
||||||
|
self.widgets = []
|
||||||
|
self.nodes = {}
|
||||||
self.buildTree(data, self.invisibleRootItem(), hideRoot=hideRoot)
|
self.buildTree(data, self.invisibleRootItem(), hideRoot=hideRoot)
|
||||||
#node = self.mkNode('', data)
|
|
||||||
#while node.childCount() > 0:
|
|
||||||
#c = node.child(0)
|
|
||||||
#node.removeChild(c)
|
|
||||||
#self.invisibleRootItem().addChild(c)
|
|
||||||
self.expandToDepth(3)
|
self.expandToDepth(3)
|
||||||
self.resizeColumnToContents(0)
|
self.resizeColumnToContents(0)
|
||||||
|
|
||||||
def buildTree(self, data, parent, name='', hideRoot=False):
|
def buildTree(self, data, parent, name='', hideRoot=False, path=()):
|
||||||
if hideRoot:
|
if hideRoot:
|
||||||
node = parent
|
node = parent
|
||||||
else:
|
else:
|
||||||
|
node = QtGui.QTreeWidgetItem([name, "", ""])
|
||||||
|
parent.addChild(node)
|
||||||
|
|
||||||
|
# record the path to the node so it can be retrieved later
|
||||||
|
# (this is used by DiffTreeWidget)
|
||||||
|
self.nodes[path] = node
|
||||||
|
|
||||||
|
typeStr, desc, childs, widget = self.parse(data)
|
||||||
|
node.setText(1, typeStr)
|
||||||
|
node.setText(2, desc)
|
||||||
|
|
||||||
|
# Truncate description and add text box if needed
|
||||||
|
if len(desc) > 100:
|
||||||
|
desc = desc[:97] + '...'
|
||||||
|
if widget is None:
|
||||||
|
widget = QtGui.QPlainTextEdit(asUnicode(data))
|
||||||
|
widget.setMaximumHeight(200)
|
||||||
|
widget.setReadOnly(True)
|
||||||
|
|
||||||
|
# Add widget to new subnode
|
||||||
|
if widget is not None:
|
||||||
|
self.widgets.append(widget)
|
||||||
|
subnode = QtGui.QTreeWidgetItem(["", "", ""])
|
||||||
|
node.addChild(subnode)
|
||||||
|
self.setItemWidget(subnode, 0, widget)
|
||||||
|
self.setFirstItemColumnSpanned(subnode, True)
|
||||||
|
|
||||||
|
# recurse to children
|
||||||
|
for key, data in childs.items():
|
||||||
|
self.buildTree(data, node, asUnicode(key), path=path+(key,))
|
||||||
|
|
||||||
|
def parse(self, data):
|
||||||
|
"""
|
||||||
|
Given any python object, return:
|
||||||
|
* type
|
||||||
|
* a short string representation
|
||||||
|
* a dict of sub-objects to be parsed
|
||||||
|
* optional widget to display as sub-node
|
||||||
|
"""
|
||||||
|
# defaults for all objects
|
||||||
typeStr = type(data).__name__
|
typeStr = type(data).__name__
|
||||||
if typeStr == 'instance':
|
if typeStr == 'instance':
|
||||||
typeStr += ": " + data.__class__.__name__
|
typeStr += ": " + data.__class__.__name__
|
||||||
node = QtGui.QTreeWidgetItem([name, typeStr, ""])
|
widget = None
|
||||||
parent.addChild(node)
|
desc = ""
|
||||||
|
childs = {}
|
||||||
if isinstance(data, types.TracebackType): ## convert traceback to a list of strings
|
|
||||||
data = list(map(str.strip, traceback.format_list(traceback.extract_tb(data))))
|
|
||||||
elif HAVE_METAARRAY and (hasattr(data, 'implements') and data.implements('MetaArray')):
|
|
||||||
data = {
|
|
||||||
'data': data.view(np.ndarray),
|
|
||||||
'meta': data.infoCopy()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
# type-specific changes
|
||||||
if isinstance(data, dict):
|
if isinstance(data, dict):
|
||||||
for k in data.keys():
|
desc = "length=%d" % len(data)
|
||||||
self.buildTree(data[k], node, str(k))
|
if isinstance(data, OrderedDict):
|
||||||
elif isinstance(data, list) or isinstance(data, tuple):
|
childs = data
|
||||||
for i in range(len(data)):
|
|
||||||
self.buildTree(data[i], node, str(i))
|
|
||||||
else:
|
else:
|
||||||
node.setText(2, str(data))
|
childs = OrderedDict(sorted(data.items()))
|
||||||
|
elif isinstance(data, (list, tuple)):
|
||||||
|
desc = "length=%d" % len(data)
|
||||||
|
childs = OrderedDict(enumerate(data))
|
||||||
|
elif HAVE_METAARRAY and (hasattr(data, 'implements') and data.implements('MetaArray')):
|
||||||
|
childs = OrderedDict([
|
||||||
|
('data', data.view(np.ndarray)),
|
||||||
|
('meta', data.infoCopy())
|
||||||
|
])
|
||||||
|
elif isinstance(data, np.ndarray):
|
||||||
|
desc = "shape=%s dtype=%s" % (data.shape, data.dtype)
|
||||||
|
table = TableWidget()
|
||||||
|
table.setData(data)
|
||||||
|
table.setMaximumHeight(200)
|
||||||
|
widget = table
|
||||||
|
elif isinstance(data, types.TracebackType): ## convert traceback to a list of strings
|
||||||
|
frames = list(map(str.strip, traceback.format_list(traceback.extract_tb(data))))
|
||||||
|
#childs = OrderedDict([
|
||||||
|
#(i, {'file': child[0], 'line': child[1], 'function': child[2], 'code': child[3]})
|
||||||
|
#for i, child in enumerate(frames)])
|
||||||
|
#childs = OrderedDict([(i, ch) for i,ch in enumerate(frames)])
|
||||||
|
widget = QtGui.QPlainTextEdit(asUnicode('\n'.join(frames)))
|
||||||
|
widget.setMaximumHeight(200)
|
||||||
|
widget.setReadOnly(True)
|
||||||
|
else:
|
||||||
|
desc = asUnicode(data)
|
||||||
|
|
||||||
|
return typeStr, desc, childs, widget
|
||||||
#def mkNode(self, name, v):
|
|
||||||
#if type(v) is list and len(v) > 0 and isinstance(v[0], dict):
|
|
||||||
#inds = map(unicode, range(len(v)))
|
|
||||||
#v = OrderedDict(zip(inds, v))
|
|
||||||
#if isinstance(v, dict):
|
|
||||||
##print "\nadd tree", k, v
|
|
||||||
#node = QtGui.QTreeWidgetItem([name])
|
|
||||||
#for k in v:
|
|
||||||
#newNode = self.mkNode(k, v[k])
|
|
||||||
#node.addChild(newNode)
|
|
||||||
#else:
|
|
||||||
##print "\nadd value", k, str(v)
|
|
||||||
#node = QtGui.QTreeWidgetItem([unicode(name), unicode(v)])
|
|
||||||
#return node
|
|
||||||
|
|
164
pyqtgraph/widgets/DiffTreeWidget.py
Normal file
164
pyqtgraph/widgets/DiffTreeWidget.py
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from ..Qt import QtGui, QtCore
|
||||||
|
from ..pgcollections import OrderedDict
|
||||||
|
from .DataTreeWidget import DataTreeWidget
|
||||||
|
from .. import functions as fn
|
||||||
|
import types, traceback
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
__all__ = ['DiffTreeWidget']
|
||||||
|
|
||||||
|
|
||||||
|
class DiffTreeWidget(QtGui.QWidget):
|
||||||
|
"""
|
||||||
|
Widget for displaying differences between hierarchical python data structures
|
||||||
|
(eg, nested dicts, lists, and arrays)
|
||||||
|
"""
|
||||||
|
def __init__(self, parent=None, a=None, b=None):
|
||||||
|
QtGui.QWidget.__init__(self, parent)
|
||||||
|
self.layout = QtGui.QHBoxLayout()
|
||||||
|
self.setLayout(self.layout)
|
||||||
|
self.trees = [DataTreeWidget(self), DataTreeWidget(self)]
|
||||||
|
for t in self.trees:
|
||||||
|
self.layout.addWidget(t)
|
||||||
|
if a is not None:
|
||||||
|
self.setData(a, b)
|
||||||
|
|
||||||
|
def setData(self, a, b):
|
||||||
|
"""
|
||||||
|
Set the data to be compared in this widget.
|
||||||
|
"""
|
||||||
|
self.data = (a, b)
|
||||||
|
self.trees[0].setData(a)
|
||||||
|
self.trees[1].setData(b)
|
||||||
|
|
||||||
|
return self.compare(a, b)
|
||||||
|
|
||||||
|
def compare(self, a, b, path=()):
|
||||||
|
"""
|
||||||
|
Compare data structure *a* to structure *b*.
|
||||||
|
|
||||||
|
Return True if the objects match completely.
|
||||||
|
Otherwise, return a structure that describes the differences:
|
||||||
|
|
||||||
|
{ 'type': bool
|
||||||
|
'len': bool,
|
||||||
|
'str': bool,
|
||||||
|
'shape': bool,
|
||||||
|
'dtype': bool,
|
||||||
|
'mask': array,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
bad = (255, 200, 200)
|
||||||
|
diff = []
|
||||||
|
# generate typestr, desc, childs for each object
|
||||||
|
typeA, descA, childsA, _ = self.trees[0].parse(a)
|
||||||
|
typeB, descB, childsB, _ = self.trees[1].parse(b)
|
||||||
|
|
||||||
|
if typeA != typeB:
|
||||||
|
self.setColor(path, 1, bad)
|
||||||
|
if descA != descB:
|
||||||
|
self.setColor(path, 2, bad)
|
||||||
|
|
||||||
|
if isinstance(a, dict) and isinstance(b, dict):
|
||||||
|
keysA = set(a.keys())
|
||||||
|
keysB = set(b.keys())
|
||||||
|
for key in keysA - keysB:
|
||||||
|
self.setColor(path+(key,), 0, bad, tree=0)
|
||||||
|
for key in keysB - keysA:
|
||||||
|
self.setColor(path+(key,), 0, bad, tree=1)
|
||||||
|
for key in keysA & keysB:
|
||||||
|
self.compare(a[key], b[key], path+(key,))
|
||||||
|
|
||||||
|
elif isinstance(a, (list, tuple)) and isinstance(b, (list, tuple)):
|
||||||
|
for i in range(max(len(a), len(b))):
|
||||||
|
if len(a) <= i:
|
||||||
|
self.setColor(path+(i,), 0, bad, tree=1)
|
||||||
|
elif len(b) <= i:
|
||||||
|
self.setColor(path+(i,), 0, bad, tree=0)
|
||||||
|
else:
|
||||||
|
self.compare(a[i], b[i], path+(i,))
|
||||||
|
|
||||||
|
elif isinstance(a, np.ndarray) and isinstance(b, np.ndarray) and a.shape == b.shape:
|
||||||
|
tableNodes = [tree.nodes[path].child(0) for tree in self.trees]
|
||||||
|
if a.dtype.fields is None and b.dtype.fields is None:
|
||||||
|
eq = self.compareArrays(a, b)
|
||||||
|
if not np.all(eq):
|
||||||
|
for n in tableNodes:
|
||||||
|
n.setBackground(0, fn.mkBrush(bad))
|
||||||
|
#for i in np.argwhere(~eq):
|
||||||
|
|
||||||
|
else:
|
||||||
|
if a.dtype == b.dtype:
|
||||||
|
for i,k in enumerate(a.dtype.fields.keys()):
|
||||||
|
eq = self.compareArrays(a[k], b[k])
|
||||||
|
if not np.all(eq):
|
||||||
|
for n in tableNodes:
|
||||||
|
n.setBackground(0, fn.mkBrush(bad))
|
||||||
|
#for j in np.argwhere(~eq):
|
||||||
|
|
||||||
|
# dict: compare keys, then values where keys match
|
||||||
|
# list:
|
||||||
|
# array: compare elementwise for same shape
|
||||||
|
|
||||||
|
def compareArrays(self, a, b):
|
||||||
|
intnan = -9223372036854775808 # happens when np.nan is cast to int
|
||||||
|
anans = np.isnan(a) | (a == intnan)
|
||||||
|
bnans = np.isnan(b) | (b == intnan)
|
||||||
|
eq = anans == bnans
|
||||||
|
mask = ~anans
|
||||||
|
eq[mask] = np.allclose(a[mask], b[mask])
|
||||||
|
return eq
|
||||||
|
|
||||||
|
def setColor(self, path, column, color, tree=None):
|
||||||
|
brush = fn.mkBrush(color)
|
||||||
|
|
||||||
|
# Color only one tree if specified.
|
||||||
|
if tree is None:
|
||||||
|
trees = self.trees
|
||||||
|
else:
|
||||||
|
trees = [self.trees[tree]]
|
||||||
|
|
||||||
|
for tree in trees:
|
||||||
|
item = tree.nodes[path]
|
||||||
|
item.setBackground(column, brush)
|
||||||
|
|
||||||
|
def _compare(self, a, b):
|
||||||
|
"""
|
||||||
|
Compare data structure *a* to structure *b*.
|
||||||
|
"""
|
||||||
|
# Check test structures are the same
|
||||||
|
assert type(info) is type(expect)
|
||||||
|
if hasattr(info, '__len__'):
|
||||||
|
assert len(info) == len(expect)
|
||||||
|
|
||||||
|
if isinstance(info, dict):
|
||||||
|
for k in info:
|
||||||
|
assert k in expect
|
||||||
|
for k in expect:
|
||||||
|
assert k in info
|
||||||
|
self.compare_results(info[k], expect[k])
|
||||||
|
elif isinstance(info, list):
|
||||||
|
for i in range(len(info)):
|
||||||
|
self.compare_results(info[i], expect[i])
|
||||||
|
elif isinstance(info, np.ndarray):
|
||||||
|
assert info.shape == expect.shape
|
||||||
|
assert info.dtype == expect.dtype
|
||||||
|
if info.dtype.fields is None:
|
||||||
|
intnan = -9223372036854775808 # happens when np.nan is cast to int
|
||||||
|
inans = np.isnan(info) | (info == intnan)
|
||||||
|
enans = np.isnan(expect) | (expect == intnan)
|
||||||
|
assert np.all(inans == enans)
|
||||||
|
mask = ~inans
|
||||||
|
assert np.allclose(info[mask], expect[mask])
|
||||||
|
else:
|
||||||
|
for k in info.dtype.fields.keys():
|
||||||
|
self.compare_results(info[k], expect[k])
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
assert info == expect
|
||||||
|
except Exception:
|
||||||
|
raise NotImplementedError("Cannot compare objects of type %s" % type(info))
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
from ..Qt import QtGui
|
from ..Qt import QtGui, mkQApp
|
||||||
from ..graphicsItems.GraphicsLayout import GraphicsLayout
|
from ..graphicsItems.GraphicsLayout import GraphicsLayout
|
||||||
from .GraphicsView import GraphicsView
|
from .GraphicsView import GraphicsView
|
||||||
|
|
||||||
@ -48,6 +48,7 @@ class GraphicsLayoutWidget(GraphicsView):
|
|||||||
:func:`clear <pyqtgraph.GraphicsLayout.clear>`
|
:func:`clear <pyqtgraph.GraphicsLayout.clear>`
|
||||||
"""
|
"""
|
||||||
def __init__(self, parent=None, show=False, size=None, title=None, **kargs):
|
def __init__(self, parent=None, show=False, size=None, title=None, **kargs):
|
||||||
|
mkQApp()
|
||||||
GraphicsView.__init__(self, parent)
|
GraphicsView.__init__(self, parent)
|
||||||
self.ci = GraphicsLayout(**kargs)
|
self.ci = GraphicsLayout(**kargs)
|
||||||
for n in ['nextRow', 'nextCol', 'nextColumn', 'addPlot', 'addViewBox', 'addItem', 'getItem', 'addLayout', 'addLabel', 'removeItem', 'itemIndex', 'clear']:
|
for n in ['nextRow', 'nextCol', 'nextColumn', 'addPlot', 'addViewBox', 'addItem', 'getItem', 'addLayout', 'addLabel', 'removeItem', 'itemIndex', 'clear']:
|
||||||
|
@ -122,7 +122,7 @@ if HAVE_OPENGL:
|
|||||||
if not self.uploaded:
|
if not self.uploaded:
|
||||||
self.uploadTexture()
|
self.uploadTexture()
|
||||||
|
|
||||||
glViewport(0, 0, self.width(), self.height())
|
glViewport(0, 0, self.width() * self.devicePixelRatio(), self.height() * self.devicePixelRatio())
|
||||||
glEnable(GL_TEXTURE_2D)
|
glEnable(GL_TEXTURE_2D)
|
||||||
glBindTexture(GL_TEXTURE_2D, self.texture)
|
glBindTexture(GL_TEXTURE_2D, self.texture)
|
||||||
glColor4f(1,1,1,1)
|
glColor4f(1,1,1,1)
|
||||||
|
@ -52,16 +52,23 @@ class ScatterPlotWidget(QtGui.QSplitter):
|
|||||||
self.ctrlPanel.addWidget(self.ptree)
|
self.ctrlPanel.addWidget(self.ptree)
|
||||||
self.addWidget(self.plot)
|
self.addWidget(self.plot)
|
||||||
|
|
||||||
bg = fn.mkColor(getConfigOption('background'))
|
fg = fn.mkColor(getConfigOption('foreground'))
|
||||||
bg.setAlpha(150)
|
fg.setAlpha(150)
|
||||||
self.filterText = TextItem(border=getConfigOption('foreground'), color=bg)
|
self.filterText = TextItem(border=getConfigOption('foreground'), color=fg)
|
||||||
self.filterText.setPos(60,20)
|
self.filterText.setPos(60,20)
|
||||||
self.filterText.setParentItem(self.plot.plotItem)
|
self.filterText.setParentItem(self.plot.plotItem)
|
||||||
|
|
||||||
self.data = None
|
self.data = None
|
||||||
|
self.indices = None
|
||||||
self.mouseOverField = None
|
self.mouseOverField = None
|
||||||
self.scatterPlot = None
|
self.scatterPlot = None
|
||||||
|
self.selectionScatter = None
|
||||||
|
self.selectedIndices = []
|
||||||
self.style = dict(pen=None, symbol='o')
|
self.style = dict(pen=None, symbol='o')
|
||||||
|
self._visibleXY = None # currently plotted points
|
||||||
|
self._visibleData = None # currently plotted records
|
||||||
|
self._visibleIndices = None
|
||||||
|
self._indexMap = None
|
||||||
|
|
||||||
self.fieldList.itemSelectionChanged.connect(self.fieldSelectionChanged)
|
self.fieldList.itemSelectionChanged.connect(self.fieldSelectionChanged)
|
||||||
self.filter.sigFilterChanged.connect(self.filterChanged)
|
self.filter.sigFilterChanged.connect(self.filterChanged)
|
||||||
@ -84,15 +91,44 @@ class ScatterPlotWidget(QtGui.QSplitter):
|
|||||||
self.filter.setFields(fields)
|
self.filter.setFields(fields)
|
||||||
self.colorMap.setFields(fields)
|
self.colorMap.setFields(fields)
|
||||||
|
|
||||||
|
def setSelectedFields(self, *fields):
|
||||||
|
self.fieldList.itemSelectionChanged.disconnect(self.fieldSelectionChanged)
|
||||||
|
try:
|
||||||
|
self.fieldList.clearSelection()
|
||||||
|
for f in fields:
|
||||||
|
i = self.fields.keys().index(f)
|
||||||
|
item = self.fieldList.item(i)
|
||||||
|
item.setSelected(True)
|
||||||
|
finally:
|
||||||
|
self.fieldList.itemSelectionChanged.connect(self.fieldSelectionChanged)
|
||||||
|
self.fieldSelectionChanged()
|
||||||
|
|
||||||
def setData(self, data):
|
def setData(self, data):
|
||||||
"""
|
"""
|
||||||
Set the data to be processed and displayed.
|
Set the data to be processed and displayed.
|
||||||
Argument must be a numpy record array.
|
Argument must be a numpy record array.
|
||||||
"""
|
"""
|
||||||
self.data = data
|
self.data = data
|
||||||
|
self.indices = np.arange(len(data))
|
||||||
self.filtered = None
|
self.filtered = None
|
||||||
|
self.filteredIndices = None
|
||||||
self.updatePlot()
|
self.updatePlot()
|
||||||
|
|
||||||
|
def setSelectedIndices(self, inds):
|
||||||
|
"""Mark the specified indices as selected.
|
||||||
|
|
||||||
|
Must be a sequence of integers that index into the array given in setData().
|
||||||
|
"""
|
||||||
|
self.selectedIndices = inds
|
||||||
|
self.updateSelected()
|
||||||
|
|
||||||
|
def setSelectedPoints(self, points):
|
||||||
|
"""Mark the specified points as selected.
|
||||||
|
|
||||||
|
Must be a list of points as generated by the sigScatterPlotClicked signal.
|
||||||
|
"""
|
||||||
|
self.setSelectedIndices([pt.originalIndex for pt in points])
|
||||||
|
|
||||||
def fieldSelectionChanged(self):
|
def fieldSelectionChanged(self):
|
||||||
sel = self.fieldList.selectedItems()
|
sel = self.fieldList.selectedItems()
|
||||||
if len(sel) > 2:
|
if len(sel) > 2:
|
||||||
@ -115,14 +151,15 @@ class ScatterPlotWidget(QtGui.QSplitter):
|
|||||||
self.filterText.setText('\n'.join(desc))
|
self.filterText.setText('\n'.join(desc))
|
||||||
self.filterText.setVisible(True)
|
self.filterText.setVisible(True)
|
||||||
|
|
||||||
|
|
||||||
def updatePlot(self):
|
def updatePlot(self):
|
||||||
self.plot.clear()
|
self.plot.clear()
|
||||||
if self.data is None:
|
if self.data is None or len(self.data) == 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.filtered is None:
|
if self.filtered is None:
|
||||||
self.filtered = self.filter.filterData(self.data)
|
mask = self.filter.generateMask(self.data)
|
||||||
|
self.filtered = self.data[mask]
|
||||||
|
self.filteredIndices = self.indices[mask]
|
||||||
data = self.filtered
|
data = self.filtered
|
||||||
if len(data) == 0:
|
if len(data) == 0:
|
||||||
return
|
return
|
||||||
@ -177,12 +214,14 @@ class ScatterPlotWidget(QtGui.QSplitter):
|
|||||||
## mask out any nan values
|
## mask out any nan values
|
||||||
mask = np.ones(len(xy[0]), dtype=bool)
|
mask = np.ones(len(xy[0]), dtype=bool)
|
||||||
if xy[0].dtype.kind == 'f':
|
if xy[0].dtype.kind == 'f':
|
||||||
mask &= ~np.isnan(xy[0])
|
mask &= np.isfinite(xy[0])
|
||||||
if xy[1] is not None and xy[1].dtype.kind == 'f':
|
if xy[1] is not None and xy[1].dtype.kind == 'f':
|
||||||
mask &= ~np.isnan(xy[1])
|
mask &= np.isfinite(xy[1])
|
||||||
|
|
||||||
xy[0] = xy[0][mask]
|
xy[0] = xy[0][mask]
|
||||||
style['symbolBrush'] = colors[mask]
|
style['symbolBrush'] = colors[mask]
|
||||||
|
data = data[mask]
|
||||||
|
indices = self.filteredIndices[mask]
|
||||||
|
|
||||||
## Scatter y-values for a histogram-like appearance
|
## Scatter y-values for a histogram-like appearance
|
||||||
if xy[1] is None:
|
if xy[1] is None:
|
||||||
@ -205,15 +244,43 @@ class ScatterPlotWidget(QtGui.QSplitter):
|
|||||||
scatter *= 0.2 / smax
|
scatter *= 0.2 / smax
|
||||||
xy[ax][keymask] += scatter
|
xy[ax][keymask] += scatter
|
||||||
|
|
||||||
|
|
||||||
if self.scatterPlot is not None:
|
if self.scatterPlot is not None:
|
||||||
try:
|
try:
|
||||||
self.scatterPlot.sigPointsClicked.disconnect(self.plotClicked)
|
self.scatterPlot.sigPointsClicked.disconnect(self.plotClicked)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
self.scatterPlot = self.plot.plot(xy[0], xy[1], data=data[mask], **style)
|
|
||||||
|
self._visibleXY = xy
|
||||||
|
self._visibleData = data
|
||||||
|
self._visibleIndices = indices
|
||||||
|
self._indexMap = None
|
||||||
|
self.scatterPlot = self.plot.plot(xy[0], xy[1], data=data, **style)
|
||||||
self.scatterPlot.sigPointsClicked.connect(self.plotClicked)
|
self.scatterPlot.sigPointsClicked.connect(self.plotClicked)
|
||||||
|
self.updateSelected()
|
||||||
|
|
||||||
|
def updateSelected(self):
|
||||||
|
if self._visibleXY is None:
|
||||||
|
return
|
||||||
|
# map from global index to visible index
|
||||||
|
indMap = self._getIndexMap()
|
||||||
|
inds = [indMap[i] for i in self.selectedIndices if i in indMap]
|
||||||
|
x,y = self._visibleXY[0][inds], self._visibleXY[1][inds]
|
||||||
|
|
||||||
|
if self.selectionScatter is not None:
|
||||||
|
self.plot.plotItem.removeItem(self.selectionScatter)
|
||||||
|
if len(x) == 0:
|
||||||
|
return
|
||||||
|
self.selectionScatter = self.plot.plot(x, y, pen=None, symbol='s', symbolSize=12, symbolBrush=None, symbolPen='y')
|
||||||
|
|
||||||
|
def _getIndexMap(self):
|
||||||
|
# mapping from original data index to visible point index
|
||||||
|
if self._indexMap is None:
|
||||||
|
self._indexMap = {j:i for i,j in enumerate(self._visibleIndices)}
|
||||||
|
return self._indexMap
|
||||||
|
|
||||||
def plotClicked(self, plot, points):
|
def plotClicked(self, plot, points):
|
||||||
|
# Tag each point with its index into the original dataset
|
||||||
|
for pt in points:
|
||||||
|
pt.originalIndex = self._visibleIndices[pt.index()]
|
||||||
self.sigScatterPlotClicked.emit(self, points)
|
self.sigScatterPlotClicked.emit(self, points)
|
||||||
|
|
||||||
|
|
||||||
|
@ -146,7 +146,8 @@ class TableWidget(QtGui.QTableWidget):
|
|||||||
i += 1
|
i += 1
|
||||||
self.setRow(i, [x for x in fn1(row)])
|
self.setRow(i, [x for x in fn1(row)])
|
||||||
|
|
||||||
if self._sorting and self.horizontalHeader().sortIndicatorSection() >= self.columnCount():
|
if (self._sorting and self.horizontalHeadersSet and
|
||||||
|
self.horizontalHeader().sortIndicatorSection() >= self.columnCount()):
|
||||||
self.sortByColumn(0, QtCore.Qt.AscendingOrder)
|
self.sortByColumn(0, QtCore.Qt.AscendingOrder)
|
||||||
|
|
||||||
def setEditable(self, editable=True):
|
def setEditable(self, editable=True):
|
||||||
@ -216,6 +217,8 @@ class TableWidget(QtGui.QTableWidget):
|
|||||||
return self.iterate, list(map(asUnicode, data.dtype.names))
|
return self.iterate, list(map(asUnicode, data.dtype.names))
|
||||||
elif data is None:
|
elif data is None:
|
||||||
return (None,None)
|
return (None,None)
|
||||||
|
elif np.isscalar(data):
|
||||||
|
return self.iterateScalar, None
|
||||||
else:
|
else:
|
||||||
msg = "Don't know how to iterate over data type: {!s}".format(type(data))
|
msg = "Don't know how to iterate over data type: {!s}".format(type(data))
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
@ -230,6 +233,9 @@ class TableWidget(QtGui.QTableWidget):
|
|||||||
for x in data:
|
for x in data:
|
||||||
yield x
|
yield x
|
||||||
|
|
||||||
|
def iterateScalar(self, data):
|
||||||
|
yield data
|
||||||
|
|
||||||
def appendRow(self, data):
|
def appendRow(self, data):
|
||||||
self.appendData([data])
|
self.appendData([data])
|
||||||
|
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
## just import everything from sub-modules
|
|
||||||
|
|
||||||
#import os
|
|
||||||
|
|
||||||
#d = os.path.split(__file__)[0]
|
|
||||||
#files = []
|
|
||||||
#for f in os.listdir(d):
|
|
||||||
#if os.path.isdir(os.path.join(d, f)):
|
|
||||||
#files.append(f)
|
|
||||||
#elif f[-3:] == '.py' and f != '__init__.py':
|
|
||||||
#files.append(f[:-3])
|
|
||||||
|
|
||||||
#for modName in files:
|
|
||||||
#mod = __import__(modName, globals(), locals(), fromlist=['*'])
|
|
||||||
#if hasattr(mod, '__all__'):
|
|
||||||
#names = mod.__all__
|
|
||||||
#else:
|
|
||||||
#names = [n for n in dir(mod) if n[0] != '_']
|
|
||||||
#for k in names:
|
|
||||||
#print modName, k
|
|
||||||
#globals()[k] = getattr(mod, k)
|
|
Loading…
Reference in New Issue
Block a user