merge from inp
This commit is contained in:
commit
194eba1e3a
@ -10,5 +10,7 @@ Contents:
|
||||
graphicsItems/index
|
||||
widgets/index
|
||||
3dgraphics/index
|
||||
colormap
|
||||
parametertree/index
|
||||
graphicsscene/index
|
||||
flowchart/index
|
||||
|
8
doc/source/colormap.rst
Normal file
8
doc/source/colormap.rst
Normal file
@ -0,0 +1,8 @@
|
||||
ColorMap
|
||||
========
|
||||
|
||||
.. autoclass:: pyqtgraph.ColorMap
|
||||
:members:
|
||||
|
||||
.. automethod:: pyqtgraph.ColorMap.__init__
|
||||
|
@ -91,6 +91,8 @@ Mesh Generation Functions
|
||||
Miscellaneous Functions
|
||||
-----------------------
|
||||
|
||||
.. autofunction:: pyqtgraph.arrayToQPath
|
||||
|
||||
.. autofunction:: pyqtgraph.pseudoScatter
|
||||
|
||||
.. autofunction:: pyqtgraph.systemInfo
|
||||
|
8
doc/source/graphicsItems/graphitem.rst
Normal file
8
doc/source/graphicsItems/graphitem.rst
Normal file
@ -0,0 +1,8 @@
|
||||
GraphItem
|
||||
=========
|
||||
|
||||
.. autoclass:: pyqtgraph.GraphItem
|
||||
:members:
|
||||
|
||||
.. automethod:: pyqtgraph.GraphItem.__init__
|
||||
|
@ -12,6 +12,7 @@ Contents:
|
||||
plotdataitem
|
||||
plotitem
|
||||
imageitem
|
||||
graphitem
|
||||
viewbox
|
||||
linearregionitem
|
||||
infiniteline
|
||||
|
@ -15,6 +15,7 @@ Contents:
|
||||
mouse_interaction
|
||||
how_to_use
|
||||
installation
|
||||
qtcrashcourse
|
||||
plotting
|
||||
images
|
||||
3dgraphics
|
||||
|
@ -3,20 +3,76 @@ Qt Crash Course
|
||||
|
||||
Pyqtgraph makes extensive use of Qt for generating nearly all of its visual output and interfaces. Qt's documentation is very well written and we encourage all pyqtgraph developers to familiarize themselves with it. The purpose of this section is to provide an introduction to programming with Qt (using either PyQt or PySide) for the pyqtgraph developer.
|
||||
|
||||
|
||||
QWidgets and Layouts
|
||||
--------------------
|
||||
|
||||
A Qt GUI is almost always composed of a few basic components:
|
||||
|
||||
* A window. This is often provided by QMainWindow, but note that all QWidgets can be displayed in their window by simply calling widget.show() if the widget does not have a parent.
|
||||
* Multiple QWidget instances such as QPushButton, QLabel, QComboBox, etc.
|
||||
* QLayout instances (optional, but strongly encouraged) which automatically manage the positioning of widgets to allow the GUI to resize in a usable way.
|
||||
|
||||
Pyqtgraph fits into this scheme by providing its own QWidget subclasses to be inserted into your GUI.
|
||||
|
||||
|
||||
Example::
|
||||
|
||||
from PyQt4 import QtGui # (the example applies equally well to PySide)
|
||||
import pyqtgraph as pg
|
||||
|
||||
## Always start by initializing Qt (only once per application)
|
||||
app = QtGui.QApplication([])
|
||||
|
||||
## Define a top-level widget to hold everything
|
||||
w = QtGui.QWidget()
|
||||
|
||||
## Create some widgets to be placed inside
|
||||
btn = QtGui.QPushButton('press me')
|
||||
text = QtGui.QLineEdit('enter text')
|
||||
listw = QtGui.QListWidget()
|
||||
plot = pg.PlotWidget()
|
||||
|
||||
## Create a grid layout to manage the widgets size and position
|
||||
layout = QtGui.QGridLayout()
|
||||
w.setLayout(layout)
|
||||
|
||||
## Add widgets to the layout in their proper positions
|
||||
layout.addWidget(btn, 0, 0) # button goes in upper-left
|
||||
layout.addWidget(text, 1, 0) # text edit goes in middle-left
|
||||
layout.addWidget(listw, 2, 0) # list widget goes in bottom-left
|
||||
layout.addWidget(plot, 0, 1, 3, 1) # plot goes on right side, spanning 3 rows
|
||||
|
||||
## Display the widget as a new window
|
||||
w.show()
|
||||
|
||||
## Start the Qt event loop
|
||||
app.exec_()
|
||||
|
||||
More complex interfaces may be designed graphically using Qt Designer, which allows you to simply drag widgets into your window to define its appearance.
|
||||
|
||||
|
||||
Naming Conventions
|
||||
------------------
|
||||
|
||||
Virtually every class in pyqtgraph is an extension of base classes provided by Qt. When reading the documentation, remember that all of Qt's classes start with the letter 'Q', whereas pyqtgraph's classes do not. When reading through the methods for any class, it is often helpful to see which Qt base classes are used and look through the Qt documentation as well.
|
||||
|
||||
Most of Qt's classes define signals which can be difficult to tell apart from regular methods. Almost all signals explicity defined by pyqtgraph are named beginning with 'sig' to indicate that these signals are not defined at the Qt level.
|
||||
|
||||
In most cases, classes which end in 'Widget' are subclassed from QWidget and can therefore be used as a GUI element in a Qt window. Classes which end in 'Item' are subclasses of QGraphicsItem and can only be displayed within a QGraphicsView instance (such as GraphicsLayoutWidget or PlotWidget).
|
||||
|
||||
|
||||
Signals, Slots, and Events
|
||||
--------------------------
|
||||
|
||||
[ to be continued.. please post a request on the pyqtgraph forum if you'd like to read more ]
|
||||
|
||||
|
||||
GraphicsView and GraphicsItems
|
||||
------------------------------
|
||||
|
||||
|
||||
Coordinate Systems
|
||||
------------------
|
||||
Coordinate Systems and Transformations
|
||||
--------------------------------------
|
||||
|
||||
|
||||
Mouse and Keyboard Input
|
||||
@ -26,3 +82,7 @@ Mouse and Keyboard Input
|
||||
QTimer, the Event Loop, and Multi-Threading
|
||||
-------------------------------------------
|
||||
|
||||
|
||||
Multi-threading vs Multi-processing in Qt
|
||||
-----------------------------------------
|
||||
|
||||
|
12
doc/source/widgets/colormapwidget.rst
Normal file
12
doc/source/widgets/colormapwidget.rst
Normal file
@ -0,0 +1,12 @@
|
||||
ColorMapWidget
|
||||
==============
|
||||
|
||||
.. autoclass:: pyqtgraph.ColorMapWidget
|
||||
:members:
|
||||
|
||||
.. automethod:: pyqtgraph.ColorMapWidget.__init__
|
||||
|
||||
.. automethod:: pyqtgraph.widgets.ColorMapWidget.ColorMapParameter.setFields
|
||||
|
||||
.. automethod:: pyqtgraph.widgets.ColorMapWidget.ColorMapParameter.map
|
||||
|
@ -17,6 +17,8 @@ Contents:
|
||||
gradientwidget
|
||||
histogramlutwidget
|
||||
parametertree
|
||||
colormapwidget
|
||||
scatterplotwidget
|
||||
graphicsview
|
||||
rawimagewidget
|
||||
datatreewidget
|
||||
|
8
doc/source/widgets/scatterplotwidget.rst
Normal file
8
doc/source/widgets/scatterplotwidget.rst
Normal file
@ -0,0 +1,8 @@
|
||||
ScatterPlotWidget
|
||||
=================
|
||||
|
||||
.. autoclass:: pyqtgraph.ScatterPlotWidget
|
||||
:members:
|
||||
|
||||
.. automethod:: pyqtgraph.ScatterPlotWidget.__init__
|
||||
|
32
examples/ErrorBarItem.py
Normal file
32
examples/ErrorBarItem.py
Normal file
@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Demonstrates basic use of ErrorBarItem
|
||||
|
||||
"""
|
||||
|
||||
import initExample ## Add path to library (just for examples; you do not need this)
|
||||
|
||||
import pyqtgraph as pg
|
||||
from pyqtgraph.Qt import QtGui
|
||||
import numpy as np
|
||||
|
||||
import pyqtgraph as pg
|
||||
import numpy as np
|
||||
|
||||
pg.setConfigOptions(antialias=True)
|
||||
|
||||
x = np.arange(10)
|
||||
y = np.arange(10) %3
|
||||
top = np.linspace(1.0, 3.0, 10)
|
||||
bottom = np.linspace(2, 0.5, 10)
|
||||
|
||||
plt = pg.plot()
|
||||
err = pg.ErrorBarItem(x=x, y=y, top=top, bottom=bottom, beam=0.5)
|
||||
plt.addItem(err)
|
||||
plt.plot(x, y, symbol='o', pen={'color': 0.8, 'width': 2})
|
||||
|
||||
## 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_()
|
42
examples/LogPlotTest.py
Normal file
42
examples/LogPlotTest.py
Normal file
@ -0,0 +1,42 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
## This example demonstrates many of the 2D plotting capabilities
|
||||
## in pyqtgraph. All of the plots may be panned/scaled by dragging with
|
||||
## the left/right mouse buttons. Right click on any plot to show a context menu.
|
||||
|
||||
|
||||
import initExample ## Add path to library (just for examples; you do not need this)
|
||||
|
||||
|
||||
from pyqtgraph.Qt import QtGui, QtCore
|
||||
import numpy as np
|
||||
import pyqtgraph as pg
|
||||
|
||||
#QtGui.QApplication.setGraphicsSystem('raster')
|
||||
app = QtGui.QApplication([])
|
||||
#mw = QtGui.QMainWindow()
|
||||
#mw.resize(800,800)
|
||||
|
||||
win = pg.GraphicsWindow(title="Basic plotting examples")
|
||||
win.resize(1000,600)
|
||||
|
||||
|
||||
|
||||
p5 = win.addPlot(title="Scatter plot, axis labels, log scale")
|
||||
x = np.random.normal(size=1000) * 1e-5
|
||||
y = x*1000 + 0.005 * np.random.normal(size=1000)
|
||||
y -= y.min()-1.0
|
||||
mask = x > 1e-15
|
||||
x = x[mask]
|
||||
y = y[mask]
|
||||
p5.plot(x, y, pen=None, symbol='t', symbolPen=None, symbolSize=10, symbolBrush=(100, 100, 255, 50))
|
||||
p5.setLabel('left', "Y Axis", units='A')
|
||||
p5.setLabel('bottom', "Y Axis", units='s')
|
||||
p5.setLogMode(x=True, y=False)
|
||||
|
||||
|
||||
## 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_()
|
@ -59,7 +59,6 @@ pos = np.random.normal(size=(2,n), scale=1e-5)
|
||||
spots = [{'pos': pos[:,i], 'data': 1, 'brush':pg.intColor(i, n), 'symbol': i%5, 'size': 5+i/10.} for i in range(n)]
|
||||
s2.addPoints(spots)
|
||||
w2.addItem(s2)
|
||||
w2.setRange(s2.boundingRect())
|
||||
s2.sigClicked.connect(clicked)
|
||||
|
||||
|
||||
@ -71,7 +70,7 @@ s3 = pg.ScatterPlotItem(pxMode=False) ## Set pxMode=False to allow spots to tr
|
||||
spots3 = []
|
||||
for i in range(10):
|
||||
for j in range(10):
|
||||
spots3.append({'pos': (1e-6*i, 1e-6*j), 'size': 1e-6, 'brush':pg.intColor(i*10+j, 100)})
|
||||
spots3.append({'pos': (1e-6*i, 1e-6*j), 'size': 1e-6, 'pen': {'color': 'w', 'width': 2}, 'brush':pg.intColor(i*10+j, 100)})
|
||||
s3.addPoints(spots3)
|
||||
w3.addItem(s3)
|
||||
s3.sigClicked.connect(clicked)
|
||||
|
12
examples/SimplePlot.py
Normal file
12
examples/SimplePlot.py
Normal file
@ -0,0 +1,12 @@
|
||||
import initExample ## Add path to library (just for examples; you do not need this)
|
||||
|
||||
from pyqtgraph.Qt import QtGui, QtCore
|
||||
import pyqtgraph as pg
|
||||
import numpy as np
|
||||
pg.plot(np.random.normal(size=100000), title="Simplest possible plotting example")
|
||||
|
||||
## 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'):
|
||||
pg.QtGui.QApplication.exec_()
|
@ -27,6 +27,8 @@ examples = OrderedDict([
|
||||
('Scatter Plot', 'ScatterPlot.py'),
|
||||
#('PlotItem', 'PlotItem.py'),
|
||||
('IsocurveItem', 'isocurve.py'),
|
||||
('GraphItem', 'GraphItem.py'),
|
||||
('ErrorBarItem', 'ErrorBarItem.py'),
|
||||
('ImageItem - video', 'ImageItem.py'),
|
||||
('ImageItem - draw', 'Draw.py'),
|
||||
('Region-of-Interest', 'ROIExamples.py'),
|
||||
|
93
examples/multiplePlotSpeedTest.py
Normal file
93
examples/multiplePlotSpeedTest.py
Normal file
@ -0,0 +1,93 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
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 = pg.mkQApp()
|
||||
plt = pg.PlotWidget()
|
||||
|
||||
app.processEvents()
|
||||
|
||||
## Putting this at the beginning or end does not have much effect
|
||||
plt.show()
|
||||
|
||||
## The auto-range is recomputed after each item is added,
|
||||
## so disabling it before plotting helps
|
||||
plt.enableAutoRange(False, False)
|
||||
|
||||
def plot():
|
||||
start = pg.ptime.time()
|
||||
n = 15
|
||||
pts = 100
|
||||
x = np.linspace(0, 0.8, pts)
|
||||
y = np.random.random(size=pts)*0.8
|
||||
for i in xrange(n):
|
||||
for j in xrange(n):
|
||||
## calling PlotWidget.plot() generates a PlotDataItem, which
|
||||
## has a bit more overhead than PlotCurveItem, which is all
|
||||
## we need here. This overhead adds up quickly and makes a big
|
||||
## difference in speed.
|
||||
|
||||
#plt.plot(x=x+i, y=y+j)
|
||||
plt.addItem(pg.PlotCurveItem(x=x+i, y=y+j))
|
||||
|
||||
#path = pg.arrayToQPath(x+i, y+j)
|
||||
#item = QtGui.QGraphicsPathItem(path)
|
||||
#item.setPen(pg.mkPen('w'))
|
||||
#plt.addItem(item)
|
||||
|
||||
dt = pg.ptime.time() - start
|
||||
print "Create plots took: %0.3fms" % (dt*1000)
|
||||
|
||||
## Plot and clear 5 times, printing the time it took
|
||||
for i in range(5):
|
||||
plt.clear()
|
||||
plot()
|
||||
app.processEvents()
|
||||
plt.autoRange()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def fastPlot():
|
||||
## Different approach: generate a single item with all data points.
|
||||
## This runs about 20x faster.
|
||||
start = pg.ptime.time()
|
||||
n = 15
|
||||
pts = 100
|
||||
x = np.linspace(0, 0.8, pts)
|
||||
y = np.random.random(size=pts)*0.8
|
||||
xdata = np.empty((n, n, pts))
|
||||
xdata[:] = x.reshape(1,1,pts) + np.arange(n).reshape(n,1,1)
|
||||
ydata = np.empty((n, n, pts))
|
||||
ydata[:] = y.reshape(1,1,pts) + np.arange(n).reshape(1,n,1)
|
||||
conn = np.ones((n*n,pts))
|
||||
conn[:,-1] = False # make sure plots are disconnected
|
||||
path = pg.arrayToQPath(xdata.flatten(), ydata.flatten(), conn.flatten())
|
||||
item = QtGui.QGraphicsPathItem(path)
|
||||
item.setPen(pg.mkPen('w'))
|
||||
plt.addItem(item)
|
||||
|
||||
dt = pg.ptime.time() - start
|
||||
print "Create plots took: %0.3fms" % (dt*1000)
|
||||
|
||||
|
||||
## Plot and clear 5 times, printing the time it took
|
||||
if hasattr(pg, 'arrayToQPath'):
|
||||
for i in range(5):
|
||||
plt.clear()
|
||||
fastPlot()
|
||||
app.processEvents()
|
||||
else:
|
||||
print "Skipping fast tests--arrayToQPath function is missing."
|
||||
|
||||
plt.autoRange()
|
||||
|
||||
## 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_()
|
@ -117,7 +117,7 @@ class GraphicsScene(QtGui.QGraphicsScene):
|
||||
|
||||
def render(self, *args):
|
||||
self.prepareForPaint()
|
||||
return QGraphicsScene.render(self, *args)
|
||||
return QtGui.QGraphicsScene.render(self, *args)
|
||||
|
||||
def prepareForPaint(self):
|
||||
"""Called before every render. This method will inform items that the scene is about to
|
||||
|
@ -4,7 +4,7 @@ PyQtGraph - Scientific Graphics and GUI Library for Python
|
||||
www.pyqtgraph.org
|
||||
"""
|
||||
|
||||
__version__ = '0.9.5'
|
||||
__version__ = None
|
||||
|
||||
### import all the goodies and add some helper functions for easy CLI use
|
||||
|
||||
@ -187,6 +187,7 @@ from .SRTTransform3D import SRTTransform3D
|
||||
from .functions import *
|
||||
from .graphicsWindows import *
|
||||
from .SignalProxy import *
|
||||
from .colormap import *
|
||||
from .ptime import time
|
||||
|
||||
|
||||
|
@ -3,6 +3,25 @@ import scipy.interpolate
|
||||
from pyqtgraph.Qt import QtGui, QtCore
|
||||
|
||||
class ColorMap(object):
|
||||
"""
|
||||
A ColorMap defines a relationship between a scalar value and a range of colors.
|
||||
ColorMaps are commonly used for false-coloring monochromatic images, coloring
|
||||
scatter-plot points, and coloring surface plots by height.
|
||||
|
||||
Each color map is defined by a set of colors, each corresponding to a
|
||||
particular scalar value. For example:
|
||||
|
||||
| 0.0 -> black
|
||||
| 0.2 -> red
|
||||
| 0.6 -> yellow
|
||||
| 1.0 -> white
|
||||
|
||||
The colors for intermediate values are determined by interpolating between
|
||||
the two nearest colors in either RGB or HSV color space.
|
||||
|
||||
To provide user-defined color mappings, see :class:`GradientWidget <pyqtgraph.GradientWidget>`.
|
||||
"""
|
||||
|
||||
|
||||
## color interpolation modes
|
||||
RGB = 1
|
||||
@ -54,7 +73,16 @@ class ColorMap(object):
|
||||
|
||||
def map(self, data, mode='byte'):
|
||||
"""
|
||||
Return an array of colors corresponding to the values in *data*.
|
||||
Data must be either a scalar position or an array (any shape) of positions.
|
||||
|
||||
The *mode* argument determines the type of data returned:
|
||||
|
||||
=========== ===============================================================
|
||||
byte (default) Values are returned as 0-255 unsigned bytes.
|
||||
float Values are returned as 0.0-1.0 floats.
|
||||
qcolor Values are returned as an array of QColor objects.
|
||||
=========== ===============================================================
|
||||
"""
|
||||
if isinstance(mode, basestring):
|
||||
mode = self.enumMap[mode.lower()]
|
||||
@ -80,16 +108,19 @@ class ColorMap(object):
|
||||
return interp
|
||||
|
||||
def mapToQColor(self, data):
|
||||
"""Convenience function; see :func:`map() <pyqtgraph.ColorMap.map>`."""
|
||||
return self.map(data, mode=self.QCOLOR)
|
||||
|
||||
def mapToByte(self, data):
|
||||
"""Convenience function; see :func:`map() <pyqtgraph.ColorMap.map>`."""
|
||||
return self.map(data, mode=self.BYTE)
|
||||
|
||||
def mapToFloat(self, data):
|
||||
"""Convenience function; see :func:`map() <pyqtgraph.ColorMap.map>`."""
|
||||
return self.map(data, mode=self.FLOAT)
|
||||
|
||||
def getGradient(self, p1=None, p2=None):
|
||||
"""Return a QLinearGradient object."""
|
||||
"""Return a QLinearGradient object spanning from QPoints p1 to p2."""
|
||||
if p1 == None:
|
||||
p1 = QtCore.QPointF(0,0)
|
||||
if p2 == None:
|
||||
@ -119,7 +150,7 @@ class ColorMap(object):
|
||||
return g
|
||||
|
||||
def getColors(self, mode=None):
|
||||
"""Return list of all colors converted to the specified mode.
|
||||
"""Return list of all color stops converted to the specified mode.
|
||||
If mode is None, then no conversion is done."""
|
||||
if isinstance(mode, basestring):
|
||||
mode = self.enumMap[mode.lower()]
|
||||
@ -158,75 +189,19 @@ class ColorMap(object):
|
||||
self.stopsCache[mode] = (self.pos, color)
|
||||
return self.stopsCache[mode]
|
||||
|
||||
#def getColor(self, x, toQColor=True):
|
||||
#"""
|
||||
#Return a color for a given value.
|
||||
|
||||
#============= ==================================================================
|
||||
#**Arguments**
|
||||
#x Value (position on gradient) of requested color.
|
||||
#toQColor If true, returns a QColor object, else returns a (r,g,b,a) tuple.
|
||||
#============= ==================================================================
|
||||
#"""
|
||||
#ticks = self.listTicks()
|
||||
#if x <= ticks[0][1]:
|
||||
#c = ticks[0][0].color
|
||||
#if toQColor:
|
||||
#return QtGui.QColor(c) # always copy colors before handing them out
|
||||
#else:
|
||||
#return (c.red(), c.green(), c.blue(), c.alpha())
|
||||
#if x >= ticks[-1][1]:
|
||||
#c = ticks[-1][0].color
|
||||
#if toQColor:
|
||||
#return QtGui.QColor(c) # always copy colors before handing them out
|
||||
#else:
|
||||
#return (c.red(), c.green(), c.blue(), c.alpha())
|
||||
|
||||
#x2 = ticks[0][1]
|
||||
#for i in range(1,len(ticks)):
|
||||
#x1 = x2
|
||||
#x2 = ticks[i][1]
|
||||
#if x1 <= x and x2 >= x:
|
||||
#break
|
||||
|
||||
#dx = (x2-x1)
|
||||
#if dx == 0:
|
||||
#f = 0.
|
||||
#else:
|
||||
#f = (x-x1) / dx
|
||||
#c1 = ticks[i-1][0].color
|
||||
#c2 = ticks[i][0].color
|
||||
#if self.colorMode == 'rgb':
|
||||
#r = c1.red() * (1.-f) + c2.red() * f
|
||||
#g = c1.green() * (1.-f) + c2.green() * f
|
||||
#b = c1.blue() * (1.-f) + c2.blue() * f
|
||||
#a = c1.alpha() * (1.-f) + c2.alpha() * f
|
||||
#if toQColor:
|
||||
#return QtGui.QColor(int(r), int(g), int(b), int(a))
|
||||
#else:
|
||||
#return (r,g,b,a)
|
||||
#elif self.colorMode == 'hsv':
|
||||
#h1,s1,v1,_ = c1.getHsv()
|
||||
#h2,s2,v2,_ = c2.getHsv()
|
||||
#h = h1 * (1.-f) + h2 * f
|
||||
#s = s1 * (1.-f) + s2 * f
|
||||
#v = v1 * (1.-f) + v2 * f
|
||||
#c = QtGui.QColor()
|
||||
#c.setHsv(h,s,v)
|
||||
#if toQColor:
|
||||
#return c
|
||||
#else:
|
||||
#return (c.red(), c.green(), c.blue(), c.alpha())
|
||||
|
||||
def getLookupTable(self, start=0.0, stop=1.0, nPts=512, alpha=None, mode='byte'):
|
||||
"""
|
||||
Return an RGB(A) lookup table (ndarray).
|
||||
|
||||
============= ============================================================================
|
||||
**Arguments**
|
||||
nPts The number of points in the returned lookup table.
|
||||
alpha True, False, or None - Specifies whether or not alpha values are included
|
||||
in the table. If alpha is None, it will be automatically determined.
|
||||
start The starting value in the lookup table (default=0.0)
|
||||
stop The final value in the lookup table (default=1.0)
|
||||
nPts The number of points in the returned lookup table.
|
||||
alpha True, False, or None - Specifies whether or not alpha values are included
|
||||
in the table. If alpha is None, it will be automatically determined.
|
||||
mode Determines return type: 'byte' (0-255), 'float' (0.0-1.0), or 'qcolor'.
|
||||
See :func:`map() <pyqtgraph.ColorMap.map>`.
|
||||
============= ============================================================================
|
||||
"""
|
||||
if isinstance(mode, basestring):
|
||||
@ -249,7 +224,9 @@ class ColorMap(object):
|
||||
return np.any(self.color[:,3] != max)
|
||||
|
||||
def isMapTrivial(self):
|
||||
"""Return True if the gradient has exactly two stops in it: black at 0.0 and white at 1.0"""
|
||||
"""
|
||||
Return True if the gradient has exactly two stops in it: black at 0.0 and white at 1.0.
|
||||
"""
|
||||
if len(self.pos) != 2:
|
||||
return False
|
||||
if self.pos[0] != 0.0 or self.pos[1] != 1.0:
|
||||
|
@ -66,7 +66,7 @@ class Exporter(object):
|
||||
if selectedExt is not None:
|
||||
selectedExt = selectedExt.groups()[0].lower()
|
||||
if ext != selectedExt:
|
||||
fileName = fileName + selectedExt
|
||||
fileName = fileName + '.' + selectedExt.lstrip('.')
|
||||
|
||||
self.export(fileName=fileName, **self.fileDialog.opts)
|
||||
|
||||
|
@ -566,8 +566,8 @@ def transformCoordinates(tr, coords, transpose=False):
|
||||
|
||||
def solve3DTransform(points1, points2):
|
||||
"""
|
||||
Find a 3D transformation matrix that maps points1 onto points2
|
||||
points must be specified as a list of 4 Vectors.
|
||||
Find a 3D transformation matrix that maps points1 onto points2.
|
||||
Points must be specified as a list of 4 Vectors.
|
||||
"""
|
||||
if not HAVE_SCIPY:
|
||||
raise Exception("This function depends on the scipy library, but it does not appear to be importable.")
|
||||
@ -583,8 +583,8 @@ def solve3DTransform(points1, points2):
|
||||
|
||||
def solveBilinearTransform(points1, points2):
|
||||
"""
|
||||
Find a bilinear transformation matrix (2x4) that maps points1 onto points2
|
||||
points must be specified as a list of 4 Vector, Point, QPointF, etc.
|
||||
Find a bilinear transformation matrix (2x4) that maps points1 onto points2.
|
||||
Points must be specified as a list of 4 Vector, Point, QPointF, etc.
|
||||
|
||||
To use this matrix to map a point [x,y]::
|
||||
|
||||
@ -951,8 +951,15 @@ def makeQImage(imgData, alpha=None, copy=True, transpose=True):
|
||||
ch = ctypes.c_char.from_buffer(imgData, 0)
|
||||
img = QtGui.QImage(ch, imgData.shape[1], imgData.shape[0], imgFormat)
|
||||
else:
|
||||
addr = ctypes.addressof(ctypes.c_char.from_buffer(imgData, 0))
|
||||
img = QtGui.QImage(addr, imgData.shape[1], imgData.shape[0], imgFormat)
|
||||
#addr = ctypes.addressof(ctypes.c_char.from_buffer(imgData, 0))
|
||||
## PyQt API for QImage changed between 4.9.3 and 4.9.6 (I don't know exactly which version it was)
|
||||
## So we first attempt the 4.9.6 API, then fall back to 4.9.3
|
||||
addr = ctypes.c_char.from_buffer(imgData, 0)
|
||||
try:
|
||||
img = QtGui.QImage(addr, imgData.shape[1], imgData.shape[0], imgFormat)
|
||||
except TypeError:
|
||||
addr = ctypes.addressof(addr)
|
||||
img = QtGui.QImage(addr, imgData.shape[1], imgData.shape[0], imgFormat)
|
||||
img.data = imgData
|
||||
return img
|
||||
#try:
|
||||
@ -1043,7 +1050,7 @@ def colorToAlpha(data, color):
|
||||
|
||||
|
||||
def arrayToQPath(x, y, connect='all'):
|
||||
"""Convert an array of x,y coordinats to QPath as efficiently as possible.
|
||||
"""Convert an array of x,y coordinats to QPainterPath as efficiently as possible.
|
||||
The *connect* argument may be 'all', indicating that each point should be
|
||||
connected to the next; 'pairs', indicating that each pair of points
|
||||
should be connected, or an array of int32 values (0 or 1) indicating
|
||||
|
@ -12,6 +12,10 @@ class ArrowItem(QtGui.QGraphicsPathItem):
|
||||
|
||||
|
||||
def __init__(self, **opts):
|
||||
"""
|
||||
Arrows can be initialized with any keyword arguments accepted by
|
||||
the setStyle() method.
|
||||
"""
|
||||
QtGui.QGraphicsPathItem.__init__(self, opts.get('parent', None))
|
||||
if 'size' in opts:
|
||||
opts['headLen'] = opts['size']
|
||||
@ -40,6 +44,32 @@ class ArrowItem(QtGui.QGraphicsPathItem):
|
||||
self.moveBy(*self.opts['pos'])
|
||||
|
||||
def setStyle(self, **opts):
|
||||
"""
|
||||
Changes the appearance of the arrow.
|
||||
All arguments are optional:
|
||||
|
||||
================= =================================================
|
||||
Keyword Arguments
|
||||
angle Orientation of the arrow in degrees. Default is
|
||||
0; arrow pointing to the left.
|
||||
headLen Length of the arrow head, from tip to base.
|
||||
default=20
|
||||
headWidth Width of the arrow head at its base.
|
||||
tipAngle Angle of the tip of the arrow in degrees. Smaller
|
||||
values make a 'sharper' arrow. If tipAngle is
|
||||
specified, ot overrides headWidth. default=25
|
||||
baseAngle Angle of the base of the arrow head. Default is
|
||||
0, which means that the base of the arrow head
|
||||
is perpendicular to the arrow shaft.
|
||||
tailLen Length of the arrow tail, measured from the base
|
||||
of the arrow head to the tip of the tail. If
|
||||
this value is None, no tail will be drawn.
|
||||
default=None
|
||||
tailWidth Width of the tail. default=3
|
||||
pen The pen used to draw the outline of the arrow.
|
||||
brush The brush used to fill the arrow.
|
||||
================= =================================================
|
||||
"""
|
||||
self.opts = opts
|
||||
|
||||
opt = dict([(k,self.opts[k]) for k in ['headLen', 'tipAngle', 'baseAngle', 'tailLen', 'tailWidth']])
|
||||
|
133
pyqtgraph/graphicsItems/ErrorBarItem.py
Normal file
133
pyqtgraph/graphicsItems/ErrorBarItem.py
Normal file
@ -0,0 +1,133 @@
|
||||
import pyqtgraph as pg
|
||||
from pyqtgraph.Qt import QtGui, QtCore
|
||||
from .GraphicsObject import GraphicsObject
|
||||
|
||||
__all__ = ['ErrorBarItem']
|
||||
|
||||
class ErrorBarItem(GraphicsObject):
|
||||
def __init__(self, **opts):
|
||||
"""
|
||||
Valid keyword options are:
|
||||
x, y, height, width, top, bottom, left, right, beam, pen
|
||||
|
||||
x and y must be numpy arrays specifying the coordinates of data points.
|
||||
height, width, top, bottom, left, right, and beam may be numpy arrays,
|
||||
single values, or None to disable. All values should be positive.
|
||||
|
||||
If height is specified, it overrides top and bottom.
|
||||
If width is specified, it overrides left and right.
|
||||
"""
|
||||
GraphicsObject.__init__(self)
|
||||
self.opts = dict(
|
||||
x=None,
|
||||
y=None,
|
||||
height=None,
|
||||
width=None,
|
||||
top=None,
|
||||
bottom=None,
|
||||
left=None,
|
||||
right=None,
|
||||
beam=None,
|
||||
pen=None
|
||||
)
|
||||
self.setOpts(**opts)
|
||||
|
||||
def setOpts(self, **opts):
|
||||
self.opts.update(opts)
|
||||
self.path = None
|
||||
self.update()
|
||||
self.informViewBoundsChanged()
|
||||
|
||||
def drawPath(self):
|
||||
p = QtGui.QPainterPath()
|
||||
|
||||
x, y = self.opts['x'], self.opts['y']
|
||||
if x is None or y is None:
|
||||
return
|
||||
|
||||
beam = self.opts['beam']
|
||||
|
||||
|
||||
height, top, bottom = self.opts['height'], self.opts['top'], self.opts['bottom']
|
||||
if height is not None or top is not None or bottom is not None:
|
||||
## draw vertical error bars
|
||||
if height is not None:
|
||||
y1 = y - height/2.
|
||||
y2 = y + height/2.
|
||||
else:
|
||||
if bottom is None:
|
||||
y1 = y
|
||||
else:
|
||||
y1 = y - bottom
|
||||
if top is None:
|
||||
y2 = y
|
||||
else:
|
||||
y2 = y + top
|
||||
|
||||
for i in range(len(x)):
|
||||
p.moveTo(x[i], y1[i])
|
||||
p.lineTo(x[i], y2[i])
|
||||
|
||||
if beam is not None and beam > 0:
|
||||
x1 = x - beam/2.
|
||||
x2 = x + beam/2.
|
||||
if height is not None or top is not None:
|
||||
for i in range(len(x)):
|
||||
p.moveTo(x1[i], y2[i])
|
||||
p.lineTo(x2[i], y2[i])
|
||||
if height is not None or bottom is not None:
|
||||
for i in range(len(x)):
|
||||
p.moveTo(x1[i], y1[i])
|
||||
p.lineTo(x2[i], y1[i])
|
||||
|
||||
width, right, left = self.opts['width'], self.opts['right'], self.opts['left']
|
||||
if width is not None or right is not None or left is not None:
|
||||
## draw vertical error bars
|
||||
if width is not None:
|
||||
x1 = x - width/2.
|
||||
x2 = x + width/2.
|
||||
else:
|
||||
if left is None:
|
||||
x1 = x
|
||||
else:
|
||||
x1 = x - left
|
||||
if right is None:
|
||||
x2 = x
|
||||
else:
|
||||
x2 = x + right
|
||||
|
||||
for i in range(len(x)):
|
||||
p.moveTo(x1[i], y[i])
|
||||
p.lineTo(x2[i], y[i])
|
||||
|
||||
if beam is not None and beam > 0:
|
||||
y1 = y - beam/2.
|
||||
y2 = y + beam/2.
|
||||
if width is not None or right is not None:
|
||||
for i in range(len(x)):
|
||||
p.moveTo(x2[i], y1[i])
|
||||
p.lineTo(x2[i], y2[i])
|
||||
if width is not None or left is not None:
|
||||
for i in range(len(x)):
|
||||
p.moveTo(x1[i], y1[i])
|
||||
p.lineTo(x1[i], y2[i])
|
||||
|
||||
self.path = p
|
||||
self.prepareGeometryChange()
|
||||
|
||||
|
||||
def paint(self, p, *args):
|
||||
if self.path is None:
|
||||
self.drawPath()
|
||||
pen = self.opts['pen']
|
||||
if pen is None:
|
||||
pen = pg.getConfigOption('foreground')
|
||||
p.setPen(pg.mkPen(pen))
|
||||
p.drawPath(self.path)
|
||||
|
||||
def boundingRect(self):
|
||||
if self.path is None:
|
||||
self.drawPath()
|
||||
return self.path.boundingRect()
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
from .. import functions as fn
|
||||
from GraphicsObject import GraphicsObject
|
||||
from ScatterPlotItem import ScatterPlotItem
|
||||
from .GraphicsObject import GraphicsObject
|
||||
from .ScatterPlotItem import ScatterPlotItem
|
||||
import pyqtgraph as pg
|
||||
import numpy as np
|
||||
|
||||
@ -8,8 +8,9 @@ __all__ = ['GraphItem']
|
||||
|
||||
|
||||
class GraphItem(GraphicsObject):
|
||||
"""A GraphItem displays graph information (as in 'graph theory', not 'graphics') as
|
||||
a set of nodes connected by lines.
|
||||
"""A GraphItem displays graph information as
|
||||
a set of nodes connected by lines (as in 'graph theory', not 'graphics').
|
||||
Useful for drawing networks, trees, etc.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwds):
|
||||
@ -28,19 +29,24 @@ class GraphItem(GraphicsObject):
|
||||
|
||||
============ =========================================================
|
||||
Arguments
|
||||
pos (N,2) array of the positions of each node in the graph
|
||||
pos (N,2) array of the positions of each node in the graph.
|
||||
adj (M,2) array of connection data. Each row contains indexes
|
||||
of two nodes that are connected.
|
||||
pen The pen to use when drawing lines between connected
|
||||
nodes. May be one of:
|
||||
|
||||
* QPen
|
||||
* a single argument to pass to pg.mkPen
|
||||
* a record array of length M
|
||||
with fields (red, green, blue, alpha, width).
|
||||
with fields (red, green, blue, alpha, width). Note
|
||||
that using this option may have a significant performance
|
||||
cost.
|
||||
* None (to disable connection drawing)
|
||||
* 'default' to use the default foreground color.
|
||||
|
||||
symbolPen The pen used for drawing nodes.
|
||||
**opts All other keyword arguments are given to ScatterPlotItem
|
||||
``**opts`` All other keyword arguments are given to
|
||||
:func:`ScatterPlotItem.setData() <pyqtgraph.ScatterPlotItem.setData>`
|
||||
to affect the appearance of nodes (symbol, size, brush,
|
||||
etc.)
|
||||
============ =========================================================
|
||||
|
@ -204,7 +204,8 @@ class GraphicsItem(object):
|
||||
return tuple(map(Point, self._pixelVectorCache[1])) ## return a *copy*
|
||||
|
||||
## check global cache
|
||||
key = (dt.m11(), dt.m21(), dt.m31(), dt.m12(), dt.m22(), dt.m32(), dt.m31(), dt.m32())
|
||||
#key = (dt.m11(), dt.m21(), dt.m31(), dt.m12(), dt.m22(), dt.m32(), dt.m31(), dt.m32())
|
||||
key = (dt.m11(), dt.m21(), dt.m12(), dt.m22())
|
||||
pv = self._pixelVectorGlobalCache.get(key, None)
|
||||
if direction is None and pv is not None:
|
||||
self._pixelVectorCache = [dt, pv]
|
||||
|
@ -93,7 +93,7 @@ class PlotCurveItem(GraphicsObject):
|
||||
|
||||
(x, y) = self.getData()
|
||||
if x is None or len(x) == 0:
|
||||
return (0, 0)
|
||||
return (None, None)
|
||||
|
||||
if ax == 0:
|
||||
d = x
|
||||
@ -102,20 +102,106 @@ class PlotCurveItem(GraphicsObject):
|
||||
d = y
|
||||
d2 = x
|
||||
|
||||
## If an orthogonal range is specified, mask the data now
|
||||
if orthoRange is not None:
|
||||
mask = (d2 >= orthoRange[0]) * (d2 <= orthoRange[1])
|
||||
d = d[mask]
|
||||
d2 = d2[mask]
|
||||
|
||||
|
||||
## Get min/max (or percentiles) of the requested data range
|
||||
if frac >= 1.0:
|
||||
b = (d.min(), d.max())
|
||||
elif frac <= 0.0:
|
||||
raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac))
|
||||
else:
|
||||
b = (scipy.stats.scoreatpercentile(d, 50 - (frac * 50)), scipy.stats.scoreatpercentile(d, 50 + (frac * 50)))
|
||||
|
||||
## adjust for fill level
|
||||
if ax == 1 and self.opts['fillLevel'] is not None:
|
||||
b = (min(b[0], self.opts['fillLevel']), max(b[1], self.opts['fillLevel']))
|
||||
|
||||
## Add pen width only if it is non-cosmetic.
|
||||
pen = self.opts['pen']
|
||||
spen = self.opts['shadowPen']
|
||||
if not pen.isCosmetic():
|
||||
b = (b[0] - pen.widthF()*0.7072, b[1] + pen.widthF()*0.7072)
|
||||
if spen is not None and not spen.isCosmetic() and spen.style() != QtCore.Qt.NoPen:
|
||||
b = (b[0] - spen.widthF()*0.7072, b[1] + spen.widthF()*0.7072)
|
||||
|
||||
self._boundsCache[ax] = [(frac, orthoRange), b]
|
||||
return b
|
||||
|
||||
def pixelPadding(self):
|
||||
pen = self.opts['pen']
|
||||
spen = self.opts['shadowPen']
|
||||
w = 0
|
||||
if pen.isCosmetic():
|
||||
w += pen.widthF()*0.7072
|
||||
if spen is not None and spen.isCosmetic() and spen.style() != QtCore.Qt.NoPen:
|
||||
w = max(w, spen.widthF()*0.7072)
|
||||
return w
|
||||
|
||||
def boundingRect(self):
|
||||
if self._boundingRect is None:
|
||||
(xmn, xmx) = self.dataBounds(ax=0)
|
||||
(ymn, ymx) = self.dataBounds(ax=1)
|
||||
if xmn is None:
|
||||
return QtCore.QRectF()
|
||||
|
||||
px = py = 0.0
|
||||
pxPad = self.pixelPadding()
|
||||
if pxPad > 0:
|
||||
# determine length of pixel in local x, y directions
|
||||
px, py = self.pixelVectors()
|
||||
px = 0 if px is None else px.length()
|
||||
py = 0 if py is None else py.length()
|
||||
|
||||
# return bounds expanded by pixel size
|
||||
px *= pxPad
|
||||
py *= pxPad
|
||||
#px += self._maxSpotWidth * 0.5
|
||||
#py += self._maxSpotWidth * 0.5
|
||||
self._boundingRect = QtCore.QRectF(xmn-px, ymn-py, (2*px)+xmx-xmn, (2*py)+ymx-ymn)
|
||||
return self._boundingRect
|
||||
|
||||
def viewTransformChanged(self):
|
||||
self.invalidateBounds()
|
||||
self.prepareGeometryChange()
|
||||
|
||||
#def boundingRect(self):
|
||||
#if self._boundingRect is None:
|
||||
#(x, y) = self.getData()
|
||||
#if x is None or y is None or len(x) == 0 or len(y) == 0:
|
||||
#return QtCore.QRectF()
|
||||
|
||||
|
||||
#if self.opts['shadowPen'] is not None:
|
||||
#lineWidth = (max(self.opts['pen'].width(), self.opts['shadowPen'].width()) + 1)
|
||||
#else:
|
||||
#lineWidth = (self.opts['pen'].width()+1)
|
||||
|
||||
|
||||
#pixels = self.pixelVectors()
|
||||
#if pixels == (None, None):
|
||||
#pixels = [Point(0,0), Point(0,0)]
|
||||
|
||||
#xmin = x.min()
|
||||
#xmax = x.max()
|
||||
#ymin = y.min()
|
||||
#ymax = y.max()
|
||||
|
||||
#if self.opts['fillLevel'] is not None:
|
||||
#ymin = min(ymin, self.opts['fillLevel'])
|
||||
#ymax = max(ymax, self.opts['fillLevel'])
|
||||
|
||||
#xmin -= pixels[0].x() * lineWidth
|
||||
#xmax += pixels[0].x() * lineWidth
|
||||
#ymin -= abs(pixels[1].y()) * lineWidth
|
||||
#ymax += abs(pixels[1].y()) * lineWidth
|
||||
|
||||
#self._boundingRect = QtCore.QRectF(xmin, ymin, xmax-xmin, ymax-ymin)
|
||||
#return self._boundingRect
|
||||
|
||||
|
||||
def invalidateBounds(self):
|
||||
self._boundingRect = None
|
||||
@ -280,40 +366,6 @@ class PlotCurveItem(GraphicsObject):
|
||||
return QtGui.QPainterPath()
|
||||
return self.path
|
||||
|
||||
def boundingRect(self):
|
||||
if self._boundingRect is None:
|
||||
(x, y) = self.getData()
|
||||
if x is None or y is None or len(x) == 0 or len(y) == 0:
|
||||
return QtCore.QRectF()
|
||||
|
||||
|
||||
if self.opts['shadowPen'] is not None:
|
||||
lineWidth = (max(self.opts['pen'].width(), self.opts['shadowPen'].width()) + 1)
|
||||
else:
|
||||
lineWidth = (self.opts['pen'].width()+1)
|
||||
|
||||
|
||||
pixels = self.pixelVectors()
|
||||
if pixels == (None, None):
|
||||
pixels = [Point(0,0), Point(0,0)]
|
||||
|
||||
xmin = x.min()
|
||||
xmax = x.max()
|
||||
ymin = y.min()
|
||||
ymax = y.max()
|
||||
|
||||
if self.opts['fillLevel'] is not None:
|
||||
ymin = min(ymin, self.opts['fillLevel'])
|
||||
ymax = max(ymax, self.opts['fillLevel'])
|
||||
|
||||
xmin -= pixels[0].x() * lineWidth
|
||||
xmax += pixels[0].x() * lineWidth
|
||||
ymin -= abs(pixels[1].y()) * lineWidth
|
||||
ymax += abs(pixels[1].y()) * lineWidth
|
||||
|
||||
self._boundingRect = QtCore.QRectF(xmin, ymin, xmax-xmin, ymax-ymin)
|
||||
return self._boundingRect
|
||||
|
||||
def paint(self, p, opt, widget):
|
||||
prof = debug.Profiler('PlotCurveItem.paint '+str(id(self)), disabled=True)
|
||||
if self.xData is None:
|
||||
|
@ -471,33 +471,57 @@ class PlotDataItem(GraphicsObject):
|
||||
and max)
|
||||
=============== =============================================================
|
||||
"""
|
||||
if frac <= 0.0:
|
||||
raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac))
|
||||
|
||||
(x, y) = self.getData()
|
||||
if x is None or len(x) == 0:
|
||||
return None
|
||||
range = [None, None]
|
||||
if self.curve.isVisible():
|
||||
range = self.curve.dataBounds(ax, frac, orthoRange)
|
||||
elif self.scatter.isVisible():
|
||||
r2 = self.scatter.dataBounds(ax, frac, orthoRange)
|
||||
range = [
|
||||
r2[0] if range[0] is None else (range[0] if r2[0] is None else min(r2[0], range[0])),
|
||||
r2[1] if range[1] is None else (range[1] if r2[1] is None else min(r2[1], range[1]))
|
||||
]
|
||||
return range
|
||||
|
||||
#if frac <= 0.0:
|
||||
#raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac))
|
||||
|
||||
#(x, y) = self.getData()
|
||||
#if x is None or len(x) == 0:
|
||||
#return None
|
||||
|
||||
if ax == 0:
|
||||
d = x
|
||||
d2 = y
|
||||
elif ax == 1:
|
||||
d = y
|
||||
d2 = x
|
||||
#if ax == 0:
|
||||
#d = x
|
||||
#d2 = y
|
||||
#elif ax == 1:
|
||||
#d = y
|
||||
#d2 = x
|
||||
|
||||
if orthoRange is not None:
|
||||
mask = (d2 >= orthoRange[0]) * (d2 <= orthoRange[1])
|
||||
d = d[mask]
|
||||
#d2 = d2[mask]
|
||||
#if orthoRange is not None:
|
||||
#mask = (d2 >= orthoRange[0]) * (d2 <= orthoRange[1])
|
||||
#d = d[mask]
|
||||
##d2 = d2[mask]
|
||||
|
||||
if len(d) > 0:
|
||||
if frac >= 1.0:
|
||||
return (np.min(d), np.max(d))
|
||||
else:
|
||||
return (scipy.stats.scoreatpercentile(d, 50 - (frac * 50)), scipy.stats.scoreatpercentile(d, 50 + (frac * 50)))
|
||||
else:
|
||||
return None
|
||||
|
||||
#if len(d) > 0:
|
||||
#if frac >= 1.0:
|
||||
#return (np.min(d), np.max(d))
|
||||
#else:
|
||||
#return (scipy.stats.scoreatpercentile(d, 50 - (frac * 50)), scipy.stats.scoreatpercentile(d, 50 + (frac * 50)))
|
||||
#else:
|
||||
#return None
|
||||
|
||||
def pixelPadding(self):
|
||||
"""
|
||||
Return the size in pixels that this item may draw beyond the values returned by dataBounds().
|
||||
This method is called by ViewBox when auto-scaling.
|
||||
"""
|
||||
pad = 0
|
||||
if self.curve.isVisible():
|
||||
pad = max(pad, self.curve.pixelPadding())
|
||||
elif self.scatter.isVisible():
|
||||
pad = max(pad, self.scatter.pixelPadding())
|
||||
return pad
|
||||
|
||||
|
||||
def clear(self):
|
||||
#for i in self.curves+self.scatters:
|
||||
|
@ -60,7 +60,7 @@ def renderSymbol(symbol, size, pen, brush, device=None):
|
||||
#return SymbolPixmapCache[key]
|
||||
|
||||
## Render a spot with the given parameters to a pixmap
|
||||
penPxWidth = max(np.ceil(pen.width()), 1)
|
||||
penPxWidth = max(np.ceil(pen.widthF()), 1)
|
||||
image = QtGui.QImage(int(size+penPxWidth), int(size+penPxWidth), QtGui.QImage.Format_ARGB32)
|
||||
image.fill(0)
|
||||
p = QtGui.QPainter(image)
|
||||
@ -115,7 +115,7 @@ class SymbolAtlas(object):
|
||||
symbol, size, pen, brush = rec['symbol'], rec['size'], rec['pen'], rec['brush']
|
||||
pen = fn.mkPen(pen) if not isinstance(pen, QtGui.QPen) else pen
|
||||
brush = fn.mkBrush(brush) if not isinstance(pen, QtGui.QBrush) else brush
|
||||
key = (symbol, size, fn.colorTuple(pen.color()), pen.width(), pen.style(), fn.colorTuple(brush.color()))
|
||||
key = (symbol, size, fn.colorTuple(pen.color()), pen.widthF(), pen.style(), fn.colorTuple(brush.color()))
|
||||
if key not in self.symbolMap:
|
||||
newCoords = SymbolAtlas.SymbolCoords()
|
||||
self.symbolMap[key] = newCoords
|
||||
@ -472,8 +472,8 @@ class ScatterPlotItem(GraphicsObject):
|
||||
|
||||
if isinstance(symbol, np.ndarray) or isinstance(symbol, list):
|
||||
symbols = symbol
|
||||
if kargs['mask'] is not None:
|
||||
symbols = symbols[kargs['mask']]
|
||||
if mask is not None:
|
||||
symbols = symbols[mask]
|
||||
if len(symbols) != len(dataSet):
|
||||
raise Exception("Number of symbols does not match number of points (%d != %d)" % (len(symbols), len(dataSet)))
|
||||
dataSet['symbol'] = symbols
|
||||
@ -589,13 +589,13 @@ class ScatterPlotItem(GraphicsObject):
|
||||
width = 0
|
||||
pxWidth = 0
|
||||
if self.opts['pxMode']:
|
||||
pxWidth = size + pen.width()
|
||||
pxWidth = size + pen.widthF()
|
||||
else:
|
||||
width = size
|
||||
if pen.isCosmetic():
|
||||
pxWidth += pen.width()
|
||||
pxWidth += pen.widthF()
|
||||
else:
|
||||
width += pen.width()
|
||||
width += pen.widthF()
|
||||
self._maxSpotWidth = max(self._maxSpotWidth, width)
|
||||
self._maxSpotPxWidth = max(self._maxSpotPxWidth, pxWidth)
|
||||
self.bounds = [None, None]
|
||||
@ -629,31 +629,15 @@ class ScatterPlotItem(GraphicsObject):
|
||||
d2 = d2[mask]
|
||||
|
||||
if frac >= 1.0:
|
||||
## increase size of bounds based on spot size and pen width
|
||||
#px = self.pixelLength(Point(1, 0) if ax == 0 else Point(0, 1)) ## determine length of pixel along this axis
|
||||
px = self.pixelVectors()[ax]
|
||||
if px is None:
|
||||
px = 0
|
||||
else:
|
||||
px = px.length()
|
||||
minIndex = np.argmin(d)
|
||||
maxIndex = np.argmax(d)
|
||||
minVal = d[minIndex]
|
||||
maxVal = d[maxIndex]
|
||||
spotSize = 0.5 * (self._maxSpotWidth + px * self._maxSpotPxWidth)
|
||||
self.bounds[ax] = (minVal-spotSize, maxVal+spotSize)
|
||||
self.bounds[ax] = (d.min() - self._maxSpotWidth*0.7072, d.max() + self._maxSpotWidth*0.7072)
|
||||
return self.bounds[ax]
|
||||
elif frac <= 0.0:
|
||||
raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac))
|
||||
else:
|
||||
return (scipy.stats.scoreatpercentile(d, 50 - (frac * 50)), scipy.stats.scoreatpercentile(d, 50 + (frac * 50)))
|
||||
|
||||
|
||||
#def defaultSpotPixmap(self):
|
||||
### Return the default spot pixmap
|
||||
#if self._spotPixmap is None:
|
||||
#self._spotPixmap = makeSymbolPixmap(size=self.opts['size'], brush=self.opts['brush'], pen=self.opts['pen'], symbol=self.opts['symbol'])
|
||||
#return self._spotPixmap
|
||||
def pixelPadding(self):
|
||||
return self._maxSpotPxWidth*0.7072
|
||||
|
||||
def boundingRect(self):
|
||||
(xmn, xmx) = self.dataBounds(ax=0)
|
||||
@ -664,7 +648,19 @@ class ScatterPlotItem(GraphicsObject):
|
||||
if ymn is None or ymx is None:
|
||||
ymn = 0
|
||||
ymx = 0
|
||||
return QtCore.QRectF(xmn, ymn, xmx-xmn, ymx-ymn)
|
||||
|
||||
px = py = 0.0
|
||||
pxPad = self.pixelPadding()
|
||||
if pxPad > 0:
|
||||
# determine length of pixel in local x, y directions
|
||||
px, py = self.pixelVectors()
|
||||
px = 0 if px is None else px.length()
|
||||
py = 0 if py is None else py.length()
|
||||
|
||||
# return bounds expanded by pixel size
|
||||
px *= pxPad
|
||||
py *= pxPad
|
||||
return QtCore.QRectF(xmn-px, ymn-py, (2*px)+xmx-xmn, (2*py)+ymx-ymn)
|
||||
|
||||
def viewTransformChanged(self):
|
||||
self.prepareGeometryChange()
|
||||
|
@ -297,10 +297,11 @@ class ViewBox(GraphicsWidget):
|
||||
|
||||
def resizeEvent(self, ev):
|
||||
#self.setRange(self.range, padding=0)
|
||||
#self.updateAutoRange()
|
||||
self.updateAutoRange()
|
||||
self.updateMatrix()
|
||||
self.sigStateChanged.emit(self)
|
||||
self.background.setRect(self.rect())
|
||||
#self._itemBoundsCache.clear()
|
||||
#self.linkedXChanged()
|
||||
#self.linkedYChanged()
|
||||
|
||||
@ -1000,63 +1001,71 @@ class ViewBox(GraphicsWidget):
|
||||
Values may be None if there are no specific bounds for an axis.
|
||||
"""
|
||||
prof = debug.Profiler('updateAutoRange', disabled=True)
|
||||
|
||||
|
||||
#items = self.allChildren()
|
||||
items = self.addedItems
|
||||
|
||||
#if item is None:
|
||||
##print "children bounding rect:"
|
||||
#item = self.childGroup
|
||||
|
||||
range = [None, None]
|
||||
|
||||
## measure pixel dimensions in view box
|
||||
px, py = [v.length() if v is not None else 0 for v in self.childGroup.pixelVectors()]
|
||||
|
||||
## First collect all boundary information
|
||||
itemBounds = []
|
||||
for item in items:
|
||||
if not item.isVisible():
|
||||
continue
|
||||
|
||||
useX = True
|
||||
useY = True
|
||||
|
||||
if hasattr(item, 'dataBounds'):
|
||||
bounds = self._itemBoundsCache.get(item, None)
|
||||
if bounds is None:
|
||||
if frac is None:
|
||||
frac = (1.0, 1.0)
|
||||
xr = item.dataBounds(0, frac=frac[0], orthoRange=orthoRange[0])
|
||||
yr = item.dataBounds(1, frac=frac[1], orthoRange=orthoRange[1])
|
||||
if xr is None or xr == (None, None):
|
||||
useX = False
|
||||
xr = (0,0)
|
||||
if yr is None or yr == (None, None):
|
||||
useY = False
|
||||
yr = (0,0)
|
||||
#bounds = self._itemBoundsCache.get(item, None)
|
||||
#if bounds is None:
|
||||
if frac is None:
|
||||
frac = (1.0, 1.0)
|
||||
xr = item.dataBounds(0, frac=frac[0], orthoRange=orthoRange[0])
|
||||
yr = item.dataBounds(1, frac=frac[1], orthoRange=orthoRange[1])
|
||||
pxPad = 0 if not hasattr(item, 'pixelPadding') else item.pixelPadding()
|
||||
if xr is None or xr == (None, None):
|
||||
useX = False
|
||||
xr = (0,0)
|
||||
if yr is None or yr == (None, None):
|
||||
useY = False
|
||||
yr = (0,0)
|
||||
|
||||
bounds = QtCore.QRectF(xr[0], yr[0], xr[1]-xr[0], yr[1]-yr[0])
|
||||
bounds = self.mapFromItemToView(item, bounds).boundingRect()
|
||||
self._itemBoundsCache[item] = (bounds, useX, useY)
|
||||
else:
|
||||
bounds, useX, useY = bounds
|
||||
bounds = QtCore.QRectF(xr[0], yr[0], xr[1]-xr[0], yr[1]-yr[0])
|
||||
bounds = self.mapFromItemToView(item, bounds).boundingRect()
|
||||
|
||||
if not any([useX, useY]):
|
||||
continue
|
||||
|
||||
## If we are ignoring only one axis, we need to check for rotations
|
||||
if useX != useY: ## != means xor
|
||||
ang = round(item.transformAngle())
|
||||
if ang == 0 or ang == 180:
|
||||
pass
|
||||
elif ang == 90 or ang == 270:
|
||||
useX, useY = useY, useX
|
||||
else:
|
||||
## Item is rotated at non-orthogonal angle, ignore bounds entirely.
|
||||
## Not really sure what is the expected behavior in this case.
|
||||
continue ## need to check for item rotations and decide how best to apply this boundary.
|
||||
|
||||
|
||||
itemBounds.append((bounds, useX, useY, pxPad))
|
||||
#self._itemBoundsCache[item] = (bounds, useX, useY)
|
||||
#else:
|
||||
#bounds, useX, useY = bounds
|
||||
else:
|
||||
if int(item.flags() & item.ItemHasNoContents) > 0:
|
||||
continue
|
||||
else:
|
||||
bounds = item.boundingRect()
|
||||
bounds = self.mapFromItemToView(item, bounds).boundingRect()
|
||||
|
||||
prof.mark('1')
|
||||
|
||||
if not any([useX, useY]):
|
||||
continue
|
||||
|
||||
if useX != useY: ## != means xor
|
||||
ang = item.transformAngle()
|
||||
if ang == 0 or ang == 180:
|
||||
pass
|
||||
elif ang == 90 or ang == 270:
|
||||
useX, useY = useY, useX
|
||||
else:
|
||||
continue ## need to check for item rotations and decide how best to apply this boundary.
|
||||
|
||||
itemBounds.append((bounds, True, True, 0))
|
||||
|
||||
#print itemBounds
|
||||
|
||||
## determine tentative new range
|
||||
range = [None, None]
|
||||
for bounds, useX, useY, px in itemBounds:
|
||||
if useY:
|
||||
if range[1] is not None:
|
||||
range[1] = [min(bounds.top(), range[1][0]), max(bounds.bottom(), range[1][1])]
|
||||
@ -1068,7 +1077,32 @@ class ViewBox(GraphicsWidget):
|
||||
else:
|
||||
range[0] = [bounds.left(), bounds.right()]
|
||||
prof.mark('2')
|
||||
|
||||
|
||||
#print "range", range
|
||||
|
||||
## Now expand any bounds that have a pixel margin
|
||||
## This must be done _after_ we have a good estimate of the new range
|
||||
## to ensure that the pixel size is roughly accurate.
|
||||
w = self.width()
|
||||
h = self.height()
|
||||
#print "w:", w, "h:", h
|
||||
if w > 0 and range[0] is not None:
|
||||
pxSize = (range[0][1] - range[0][0]) / w
|
||||
for bounds, useX, useY, px in itemBounds:
|
||||
if px == 0 or not useX:
|
||||
continue
|
||||
range[0][0] = min(range[0][0], bounds.left() - px*pxSize)
|
||||
range[0][1] = max(range[0][1], bounds.right() + px*pxSize)
|
||||
if h > 0 and range[1] is not None:
|
||||
pxSize = (range[1][1] - range[1][0]) / h
|
||||
for bounds, useX, useY, px in itemBounds:
|
||||
if px == 0 or not useY:
|
||||
continue
|
||||
range[1][0] = min(range[1][0], bounds.top() - px*pxSize)
|
||||
range[1][1] = max(range[1][1], bounds.bottom() + px*pxSize)
|
||||
|
||||
#print "final range", range
|
||||
|
||||
prof.finish()
|
||||
return range
|
||||
|
||||
@ -1086,6 +1120,8 @@ class ViewBox(GraphicsWidget):
|
||||
|
||||
|
||||
def updateMatrix(self, changed=None):
|
||||
## Make the childGroup's transform match the requested range.
|
||||
|
||||
if changed is None:
|
||||
changed = [False, False]
|
||||
changed = list(changed)
|
||||
|
@ -5,20 +5,25 @@ if __name__ == '__main__':
|
||||
if hasattr(os, 'setpgrp'):
|
||||
os.setpgrp() ## prevents signals (notably keyboard interrupt) being forwarded from parent to this process
|
||||
if sys.version[0] == '3':
|
||||
name, port, authkey, ppid, targetStr, path, pyside = pickle.load(sys.stdin.buffer)
|
||||
#name, port, authkey, ppid, targetStr, path, pyside = pickle.load(sys.stdin.buffer)
|
||||
opts = pickle.load(sys.stdin.buffer)
|
||||
else:
|
||||
name, port, authkey, ppid, targetStr, path, pyside = pickle.load(sys.stdin)
|
||||
#name, port, authkey, ppid, targetStr, path, pyside = pickle.load(sys.stdin)
|
||||
opts = pickle.load(sys.stdin)
|
||||
#print "key:", ' '.join([str(ord(x)) for x in authkey])
|
||||
path = opts.pop('path', None)
|
||||
if path is not None:
|
||||
## rewrite sys.path without assigning a new object--no idea who already has a reference to the existing list.
|
||||
while len(sys.path) > 0:
|
||||
sys.path.pop()
|
||||
sys.path.extend(path)
|
||||
|
||||
if pyside:
|
||||
if opts.pop('pyside', False):
|
||||
import PySide
|
||||
#import pyqtgraph
|
||||
#import pyqtgraph.multiprocess.processes
|
||||
targetStr = opts.pop('targetStr')
|
||||
target = pickle.loads(targetStr) ## unpickling the target should import everything we need
|
||||
target(name, port, authkey, ppid)
|
||||
#target(name, port, authkey, ppid)
|
||||
target(**opts) ## Send all other options to the target function
|
||||
sys.exit(0)
|
||||
|
@ -35,7 +35,7 @@ class Process(RemoteEventHandler):
|
||||
ProxyObject for more information.
|
||||
"""
|
||||
|
||||
def __init__(self, name=None, target=None, executable=None, copySysPath=True):
|
||||
def __init__(self, name=None, target=None, executable=None, copySysPath=True, debug=False):
|
||||
"""
|
||||
============ =============================================================
|
||||
Arguments:
|
||||
@ -46,7 +46,9 @@ class Process(RemoteEventHandler):
|
||||
process to process requests from the parent process until it
|
||||
is asked to quit. If you wish to specify a different target,
|
||||
it must be picklable (bound methods are not).
|
||||
copySysPath If true, copy the contents of sys.path to the remote process
|
||||
copySysPath If True, copy the contents of sys.path to the remote process
|
||||
debug If True, print detailed information about communication
|
||||
with the child process.
|
||||
============ =============================================================
|
||||
|
||||
"""
|
||||
@ -56,6 +58,7 @@ class Process(RemoteEventHandler):
|
||||
name = str(self)
|
||||
if executable is None:
|
||||
executable = sys.executable
|
||||
self.debug = debug
|
||||
|
||||
## random authentication key
|
||||
authkey = os.urandom(20)
|
||||
@ -75,23 +78,46 @@ class Process(RemoteEventHandler):
|
||||
## start remote process, instruct it to run target function
|
||||
sysPath = sys.path if copySysPath else None
|
||||
bootstrap = os.path.abspath(os.path.join(os.path.dirname(__file__), 'bootstrap.py'))
|
||||
self.debugMsg('Starting child process (%s %s)' % (executable, bootstrap))
|
||||
self.proc = subprocess.Popen((executable, bootstrap), stdin=subprocess.PIPE)
|
||||
targetStr = pickle.dumps(target) ## double-pickle target so that child has a chance to
|
||||
## set its sys.path properly before unpickling the target
|
||||
pid = os.getpid() # we must sent pid to child because windows does not have getppid
|
||||
pid = os.getpid() # we must send pid to child because windows does not have getppid
|
||||
pyside = USE_PYSIDE
|
||||
|
||||
## Send everything the remote process needs to start correctly
|
||||
pickle.dump((name+'_child', port, authkey, pid, targetStr, sysPath, pyside), self.proc.stdin)
|
||||
data = dict(
|
||||
name=name+'_child',
|
||||
port=port,
|
||||
authkey=authkey,
|
||||
ppid=pid,
|
||||
targetStr=targetStr,
|
||||
path=sysPath,
|
||||
pyside=pyside,
|
||||
debug=debug
|
||||
)
|
||||
pickle.dump(data, self.proc.stdin)
|
||||
self.proc.stdin.close()
|
||||
|
||||
## open connection for remote process
|
||||
conn = l.accept()
|
||||
RemoteEventHandler.__init__(self, conn, name+'_parent', pid=self.proc.pid)
|
||||
self.debugMsg('Listening for child process..')
|
||||
while True:
|
||||
try:
|
||||
conn = l.accept()
|
||||
break
|
||||
except IOError as err:
|
||||
if err.errno == 4: # interrupted; try again
|
||||
continue
|
||||
else:
|
||||
raise
|
||||
|
||||
RemoteEventHandler.__init__(self, conn, name+'_parent', pid=self.proc.pid, debug=debug)
|
||||
self.debugMsg('Connected to child process.')
|
||||
|
||||
atexit.register(self.join)
|
||||
|
||||
def join(self, timeout=10):
|
||||
self.debugMsg('Joining child process..')
|
||||
if self.proc.poll() is None:
|
||||
self.close()
|
||||
start = time.time()
|
||||
@ -99,13 +125,14 @@ class Process(RemoteEventHandler):
|
||||
if timeout is not None and time.time() - start > timeout:
|
||||
raise Exception('Timed out waiting for remote process to end.')
|
||||
time.sleep(0.05)
|
||||
self.debugMsg('Child process exited. (%d)' % self.proc.returncode)
|
||||
|
||||
|
||||
def startEventLoop(name, port, authkey, ppid):
|
||||
def startEventLoop(name, port, authkey, ppid, debug=False):
|
||||
conn = multiprocessing.connection.Client(('localhost', int(port)), authkey=authkey)
|
||||
global HANDLER
|
||||
#ppid = 0 if not hasattr(os, 'getppid') else os.getppid()
|
||||
HANDLER = RemoteEventHandler(conn, name, ppid)
|
||||
HANDLER = RemoteEventHandler(conn, name, ppid, debug=debug)
|
||||
while True:
|
||||
try:
|
||||
HANDLER.processRequests() # exception raised when the loop should exit
|
||||
@ -329,7 +356,7 @@ class QtProcess(Process):
|
||||
except ClosedError:
|
||||
self.timer.stop()
|
||||
|
||||
def startQtEventLoop(name, port, authkey, ppid):
|
||||
def startQtEventLoop(name, port, authkey, ppid, debug=False):
|
||||
conn = multiprocessing.connection.Client(('localhost', int(port)), authkey=authkey)
|
||||
from pyqtgraph.Qt import QtGui, QtCore
|
||||
#from PyQt4 import QtGui, QtCore
|
||||
@ -342,7 +369,7 @@ def startQtEventLoop(name, port, authkey, ppid):
|
||||
|
||||
global HANDLER
|
||||
#ppid = 0 if not hasattr(os, 'getppid') else os.getppid()
|
||||
HANDLER = RemoteQtEventHandler(conn, name, ppid)
|
||||
HANDLER = RemoteQtEventHandler(conn, name, ppid, debug=debug)
|
||||
HANDLER.startEventTimer()
|
||||
app.exec_()
|
||||
|
||||
|
@ -42,7 +42,8 @@ class RemoteEventHandler(object):
|
||||
handlers = {} ## maps {process ID : handler}. This allows unpickler to determine which process
|
||||
## an object proxy belongs to
|
||||
|
||||
def __init__(self, connection, name, pid):
|
||||
def __init__(self, connection, name, pid, debug=False):
|
||||
self.debug = debug
|
||||
self.conn = connection
|
||||
self.name = name
|
||||
self.results = {} ## reqId: (status, result); cache of request results received from the remote process
|
||||
@ -76,6 +77,11 @@ class RemoteEventHandler(object):
|
||||
print(pid, cls.handlers)
|
||||
raise
|
||||
|
||||
def debugMsg(self, msg):
|
||||
if not self.debug:
|
||||
return
|
||||
print("[%d] %s" % (os.getpid(), str(msg)))
|
||||
|
||||
def getProxyOption(self, opt):
|
||||
return self.proxyOptions[opt]
|
||||
|
||||
@ -91,7 +97,9 @@ class RemoteEventHandler(object):
|
||||
after no more events are immediately available. (non-blocking)
|
||||
Returns the number of events processed.
|
||||
"""
|
||||
self.debugMsg('processRequests:')
|
||||
if self.exited:
|
||||
self.debugMsg(' processRequests: exited already; raise ClosedError.')
|
||||
raise ClosedError()
|
||||
|
||||
numProcessed = 0
|
||||
@ -100,37 +108,64 @@ class RemoteEventHandler(object):
|
||||
self.handleRequest()
|
||||
numProcessed += 1
|
||||
except ClosedError:
|
||||
self.debugMsg(' processRequests: got ClosedError from handleRequest; setting exited=True.')
|
||||
self.exited = True
|
||||
raise
|
||||
except IOError as err:
|
||||
if err.errno == 4: ## interrupted system call; try again
|
||||
continue
|
||||
else:
|
||||
raise
|
||||
#except IOError as err: ## let handleRequest take care of this.
|
||||
#self.debugMsg(' got IOError from handleRequest; try again.')
|
||||
#if err.errno == 4: ## interrupted system call; try again
|
||||
#continue
|
||||
#else:
|
||||
#raise
|
||||
except:
|
||||
print("Error in process %s" % self.name)
|
||||
sys.excepthook(*sys.exc_info())
|
||||
|
||||
self.debugMsg(' processRequests: finished %d requests' % numProcessed)
|
||||
return numProcessed
|
||||
|
||||
def handleRequest(self):
|
||||
"""Handle a single request from the remote process.
|
||||
Blocks until a request is available."""
|
||||
result = None
|
||||
try:
|
||||
cmd, reqId, nByteMsgs, optStr = self.conn.recv() ## args, kwds are double-pickled to ensure this recv() call never fails
|
||||
except (EOFError, IOError):
|
||||
## remote process has shut down; end event loop
|
||||
raise ClosedError()
|
||||
#print os.getpid(), "received request:", cmd, reqId
|
||||
while True:
|
||||
try:
|
||||
## args, kwds are double-pickled to ensure this recv() call never fails
|
||||
cmd, reqId, nByteMsgs, optStr = self.conn.recv()
|
||||
break
|
||||
except EOFError:
|
||||
self.debugMsg(' handleRequest: got EOFError from recv; raise ClosedError.')
|
||||
## remote process has shut down; end event loop
|
||||
raise ClosedError()
|
||||
except IOError as err:
|
||||
if err.errno == 4: ## interrupted system call; try again
|
||||
self.debugMsg(' handleRequest: got IOError 4 from recv; try again.')
|
||||
continue
|
||||
else:
|
||||
self.debugMsg(' handleRequest: got IOError %d from recv (%s); raise ClosedError.' % (err.errno, err.strerror))
|
||||
raise ClosedError()
|
||||
|
||||
self.debugMsg(" handleRequest: received %s %s" % (str(cmd), str(reqId)))
|
||||
|
||||
## read byte messages following the main request
|
||||
byteData = []
|
||||
if nByteMsgs > 0:
|
||||
self.debugMsg(" handleRequest: reading %d byte messages" % nByteMsgs)
|
||||
for i in range(nByteMsgs):
|
||||
try:
|
||||
byteData.append(self.conn.recv_bytes())
|
||||
except (EOFError, IOError):
|
||||
raise ClosedError()
|
||||
while True:
|
||||
try:
|
||||
byteData.append(self.conn.recv_bytes())
|
||||
break
|
||||
except EOFError:
|
||||
self.debugMsg(" handleRequest: got EOF while reading byte messages; raise ClosedError.")
|
||||
raise ClosedError()
|
||||
except IOError as err:
|
||||
if err.errno == 4:
|
||||
self.debugMsg(" handleRequest: got IOError 4 while reading byte messages; try again.")
|
||||
continue
|
||||
else:
|
||||
self.debugMsg(" handleRequest: got IOError while reading byte messages; raise ClosedError.")
|
||||
raise ClosedError()
|
||||
|
||||
|
||||
try:
|
||||
@ -140,6 +175,7 @@ class RemoteEventHandler(object):
|
||||
## (this is already a return from a previous request)
|
||||
|
||||
opts = pickle.loads(optStr)
|
||||
self.debugMsg(" handleRequest: id=%s opts=%s" % (str(reqId), str(opts)))
|
||||
#print os.getpid(), "received request:", cmd, reqId, opts
|
||||
returnType = opts.get('returnType', 'auto')
|
||||
|
||||
@ -213,6 +249,7 @@ class RemoteEventHandler(object):
|
||||
|
||||
if reqId is not None:
|
||||
if exc is None:
|
||||
self.debugMsg(" handleRequest: sending return value for %d: %s" % (reqId, str(result)))
|
||||
#print "returnValue:", returnValue, result
|
||||
if returnType == 'auto':
|
||||
result = self.autoProxy(result, self.proxyOptions['noProxyTypes'])
|
||||
@ -225,6 +262,7 @@ class RemoteEventHandler(object):
|
||||
sys.excepthook(*sys.exc_info())
|
||||
self.replyError(reqId, *sys.exc_info())
|
||||
else:
|
||||
self.debugMsg(" handleRequest: returning exception for %d" % reqId)
|
||||
self.replyError(reqId, *exc)
|
||||
|
||||
elif exc is not None:
|
||||
@ -368,13 +406,16 @@ class RemoteEventHandler(object):
|
||||
|
||||
## Send primary request
|
||||
request = (request, reqId, nByteMsgs, optStr)
|
||||
self.debugMsg('send request: cmd=%s nByteMsgs=%d id=%s opts=%s' % (str(request[0]), nByteMsgs, str(reqId), str(opts)))
|
||||
self.conn.send(request)
|
||||
|
||||
## follow up by sending byte messages
|
||||
if byteData is not None:
|
||||
for obj in byteData: ## Remote process _must_ be prepared to read the same number of byte messages!
|
||||
self.conn.send_bytes(obj)
|
||||
self.debugMsg(' sent %d byte messages' % len(byteData))
|
||||
|
||||
self.debugMsg(' call sync: %s' % callSync)
|
||||
if callSync == 'off':
|
||||
return
|
||||
|
||||
|
@ -615,7 +615,7 @@ class Parameter(QtCore.QObject):
|
||||
if attr in self.names:
|
||||
import traceback
|
||||
traceback.print_stack()
|
||||
print "Warning: Use of Parameter.subParam is deprecated. Use Parameter.param(name) instead."
|
||||
print("Warning: Use of Parameter.subParam is deprecated. Use Parameter.param(name) instead.")
|
||||
return self.param(attr)
|
||||
else:
|
||||
raise AttributeError(attr)
|
||||
|
@ -14,10 +14,20 @@ class WidgetParameterItem(ParameterItem):
|
||||
"""
|
||||
ParameterTree item with:
|
||||
|
||||
- label in second column for displaying value
|
||||
- simple widget for editing value (displayed instead of label when item is selected)
|
||||
- button that resets value to default
|
||||
- provides SpinBox, CheckBox, LineEdit, and ColorButton types
|
||||
* label in second column for displaying value
|
||||
* simple widget for editing value (displayed instead of label when item is selected)
|
||||
* button that resets value to default
|
||||
|
||||
================= =============================================================
|
||||
Registered Types:
|
||||
int Displays a :class:`SpinBox <pyqtgraph.SpinBox>` in integer
|
||||
mode.
|
||||
float Displays a :class:`SpinBox <pyqtgraph.SpinBox>`.
|
||||
bool Displays a QCheckBox
|
||||
str Displays a QLineEdit
|
||||
color Displays a :class:`ColorButton <pyqtgraph.ColorButton>`
|
||||
colormap Displays a :class:`GradientWidget <pyqtgraph.GradientWidget>`
|
||||
================= =============================================================
|
||||
|
||||
This class can be subclassed by overriding makeWidget() to provide a custom widget.
|
||||
"""
|
||||
|
@ -9,9 +9,14 @@ __all__ = ['ColorMapWidget']
|
||||
class ColorMapWidget(ptree.ParameterTree):
|
||||
"""
|
||||
This class provides a widget allowing the user to customize color mapping
|
||||
for multi-column data.
|
||||
"""
|
||||
for multi-column data. Given a list of field names, the user may specify
|
||||
multiple criteria for assigning colors to each record in a numpy record array.
|
||||
Multiple criteria are evaluated and combined into a single color for each
|
||||
record by user-defined compositing methods.
|
||||
|
||||
For simpler color mapping using a single gradient editor, see
|
||||
:class:`GradientWidget <pyqtgraph.GradientWidget>`
|
||||
"""
|
||||
sigColorMapChanged = QtCore.Signal(object)
|
||||
|
||||
def __init__(self):
|
||||
@ -51,6 +56,25 @@ class ColorMapParameter(ptree.types.GroupParameter):
|
||||
return self.fields.keys()
|
||||
|
||||
def setFields(self, fields):
|
||||
"""
|
||||
Set the list of fields to be used by the mapper.
|
||||
|
||||
The format of *fields* is::
|
||||
|
||||
[ (fieldName, {options}), ... ]
|
||||
|
||||
============== ============================================================
|
||||
Field Options:
|
||||
mode Either 'range' or 'enum' (default is range). For 'range',
|
||||
The user may specify a gradient of colors to be applied
|
||||
linearly across a specific range of values. For 'enum',
|
||||
the user specifies a single color for each unique value
|
||||
(see *values* option).
|
||||
units String indicating the units of the data for this field.
|
||||
values List of unique values for which the user may assign a
|
||||
color when mode=='enum'.
|
||||
============== ============================================================
|
||||
"""
|
||||
self.fields = OrderedDict(fields)
|
||||
#self.fields = fields
|
||||
#self.fields.sort()
|
||||
@ -58,6 +82,18 @@ class ColorMapParameter(ptree.types.GroupParameter):
|
||||
self.setAddList(names)
|
||||
|
||||
def map(self, data, mode='byte'):
|
||||
"""
|
||||
Return an array of colors corresponding to *data*.
|
||||
|
||||
========= =================================================================
|
||||
Arguments
|
||||
data A numpy record array where the fields in data.dtype match those
|
||||
defined by a prior call to setFields().
|
||||
mode Either 'byte' or 'float'. For 'byte', the method returns an array
|
||||
of dtype ubyte with values scaled 0-255. For 'float', colors are
|
||||
returned as 0.0-1.0 float values.
|
||||
========= =================================================================
|
||||
"""
|
||||
colors = np.zeros((len(data),4))
|
||||
for item in self.children():
|
||||
if not item['Enabled']:
|
||||
|
@ -9,11 +9,27 @@ __all__ = ['TickSlider', 'GradientWidget', 'BlackWhiteSlider']
|
||||
|
||||
|
||||
class GradientWidget(GraphicsView):
|
||||
|
||||
"""
|
||||
Widget displaying an editable color gradient. The user may add, move, recolor,
|
||||
or remove colors from the gradient. Additionally, a context menu allows the
|
||||
user to select from pre-defined gradients.
|
||||
"""
|
||||
sigGradientChanged = QtCore.Signal(object)
|
||||
sigGradientChangeFinished = QtCore.Signal(object)
|
||||
|
||||
def __init__(self, parent=None, orientation='bottom', *args, **kargs):
|
||||
"""
|
||||
The *orientation* argument may be 'bottom', 'top', 'left', or 'right'
|
||||
indicating whether the gradient is displayed horizontally (top, bottom)
|
||||
or vertically (left, right) and on what side of the gradient the editable
|
||||
ticks will appear.
|
||||
|
||||
All other arguments are passed to
|
||||
:func:`GradientEditorItem.__init__ <pyqtgraph.GradientEditorItem.__init__>`.
|
||||
|
||||
Note: For convenience, this class wraps methods from
|
||||
:class:`GradientEditorItem <pyqtgraph.GradientEditorItem>`.
|
||||
"""
|
||||
GraphicsView.__init__(self, parent, useOpenGL=False, background=None)
|
||||
self.maxDim = 31
|
||||
kargs['tickPen'] = 'k'
|
||||
@ -32,6 +48,8 @@ class GradientWidget(GraphicsView):
|
||||
#self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent, True)
|
||||
|
||||
def setOrientation(self, ort):
|
||||
"""Set the orientation of the widget. May be one of 'bottom', 'top',
|
||||
'left', or 'right'."""
|
||||
self.item.setOrientation(ort)
|
||||
self.orientation = ort
|
||||
self.setMaxDim()
|
||||
|
@ -1,4 +1,4 @@
|
||||
from pyqtgraph.Qt import QtGui, QtCore
|
||||
from pyqtgraph.Qt import QtGui, QtCore, USE_PYSIDE
|
||||
import pyqtgraph.multiprocess as mp
|
||||
import pyqtgraph as pg
|
||||
from .GraphicsView import GraphicsView
|
||||
@ -21,13 +21,14 @@ class RemoteGraphicsView(QtGui.QWidget):
|
||||
self._sizeHint = (640,480) ## no clue why this is needed, but it seems to be the default sizeHint for GraphicsView.
|
||||
## without it, the widget will not compete for space against another GraphicsView.
|
||||
QtGui.QWidget.__init__(self)
|
||||
self._proc = mp.QtProcess()
|
||||
self._proc = mp.QtProcess(debug=False)
|
||||
self.pg = self._proc._import('pyqtgraph')
|
||||
self.pg.setConfigOptions(**self.pg.CONFIG_OPTIONS)
|
||||
rpgRemote = self._proc._import('pyqtgraph.widgets.RemoteGraphicsView')
|
||||
self._view = rpgRemote.Renderer(*args, **kwds)
|
||||
self._view._setProxyOptions(deferGetattr=True)
|
||||
self.setFocusPolicy(QtCore.Qt.FocusPolicy(self._view.focusPolicy()))
|
||||
|
||||
self.setFocusPolicy(QtCore.Qt.StrongFocus)
|
||||
self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
|
||||
self.setMouseTracking(True)
|
||||
self.shm = None
|
||||
@ -114,6 +115,7 @@ class RemoteGraphicsView(QtGui.QWidget):
|
||||
return self._proc
|
||||
|
||||
class Renderer(GraphicsView):
|
||||
## Created by the remote process to handle render requests
|
||||
|
||||
sceneRendered = QtCore.Signal(object)
|
||||
|
||||
@ -175,7 +177,12 @@ class Renderer(GraphicsView):
|
||||
address = ctypes.addressof(ctypes.c_char.from_buffer(self.shm, 0))
|
||||
|
||||
## render the scene directly to shared memory
|
||||
self.img = QtGui.QImage(address, self.width(), self.height(), QtGui.QImage.Format_ARGB32)
|
||||
if USE_PYSIDE:
|
||||
ch = ctypes.c_char.from_buffer(self.shm, 0)
|
||||
#ch = ctypes.c_char_p(address)
|
||||
self.img = QtGui.QImage(ch, self.width(), self.height(), QtGui.QImage.Format_ARGB32)
|
||||
else:
|
||||
self.img = QtGui.QImage(address, self.width(), self.height(), QtGui.QImage.Format_ARGB32)
|
||||
self.img.fill(0xffffffff)
|
||||
p = QtGui.QPainter(self.img)
|
||||
self.render(p, self.viewRect(), self.rect())
|
||||
|
@ -57,7 +57,9 @@ class ScatterPlotWidget(QtGui.QSplitter):
|
||||
def setFields(self, fields):
|
||||
"""
|
||||
Set the list of field names/units to be processed.
|
||||
Format is: [(name, units), ...]
|
||||
|
||||
The format of *fields* is the same as used by
|
||||
:func:`ColorMapWidget.setFields <pyqtgraph.widgets.ColorMapWidget.ColorMapParameter.setFields>`
|
||||
"""
|
||||
self.fields = OrderedDict(fields)
|
||||
self.fieldList.clear()
|
||||
|
Loading…
x
Reference in New Issue
Block a user