diff --git a/doc/source/apireference.rst b/doc/source/apireference.rst
index 777e6ad4..9742568a 100644
--- a/doc/source/apireference.rst
+++ b/doc/source/apireference.rst
@@ -10,5 +10,7 @@ Contents:
graphicsItems/index
widgets/index
3dgraphics/index
+ colormap
parametertree/index
graphicsscene/index
+ flowchart/index
diff --git a/doc/source/colormap.rst b/doc/source/colormap.rst
new file mode 100644
index 00000000..86ffe4a2
--- /dev/null
+++ b/doc/source/colormap.rst
@@ -0,0 +1,8 @@
+ColorMap
+========
+
+.. autoclass:: pyqtgraph.ColorMap
+ :members:
+
+ .. automethod:: pyqtgraph.ColorMap.__init__
+
diff --git a/doc/source/functions.rst b/doc/source/functions.rst
index 65f2c202..966fd926 100644
--- a/doc/source/functions.rst
+++ b/doc/source/functions.rst
@@ -91,6 +91,8 @@ Mesh Generation Functions
Miscellaneous Functions
-----------------------
+.. autofunction:: pyqtgraph.arrayToQPath
+
.. autofunction:: pyqtgraph.pseudoScatter
.. autofunction:: pyqtgraph.systemInfo
diff --git a/doc/source/graphicsItems/graphitem.rst b/doc/source/graphicsItems/graphitem.rst
new file mode 100644
index 00000000..95e31e28
--- /dev/null
+++ b/doc/source/graphicsItems/graphitem.rst
@@ -0,0 +1,8 @@
+GraphItem
+=========
+
+.. autoclass:: pyqtgraph.GraphItem
+ :members:
+
+ .. automethod:: pyqtgraph.GraphItem.__init__
+
diff --git a/doc/source/graphicsItems/index.rst b/doc/source/graphicsItems/index.rst
index 70786d20..b15c205c 100644
--- a/doc/source/graphicsItems/index.rst
+++ b/doc/source/graphicsItems/index.rst
@@ -12,6 +12,7 @@ Contents:
plotdataitem
plotitem
imageitem
+ graphitem
viewbox
linearregionitem
infiniteline
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 9727aaab..08c5528e 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -15,6 +15,7 @@ Contents:
mouse_interaction
how_to_use
installation
+ qtcrashcourse
plotting
images
3dgraphics
diff --git a/doc/source/qtcrashcourse.rst b/doc/source/qtcrashcourse.rst
index 58b88de4..ca2da797 100644
--- a/doc/source/qtcrashcourse.rst
+++ b/doc/source/qtcrashcourse.rst
@@ -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
+-----------------------------------------
+
diff --git a/doc/source/widgets/colormapwidget.rst b/doc/source/widgets/colormapwidget.rst
new file mode 100644
index 00000000..255ca238
--- /dev/null
+++ b/doc/source/widgets/colormapwidget.rst
@@ -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
+
\ No newline at end of file
diff --git a/doc/source/widgets/index.rst b/doc/source/widgets/index.rst
index 913557b7..7e6973a2 100644
--- a/doc/source/widgets/index.rst
+++ b/doc/source/widgets/index.rst
@@ -17,6 +17,8 @@ Contents:
gradientwidget
histogramlutwidget
parametertree
+ colormapwidget
+ scatterplotwidget
graphicsview
rawimagewidget
datatreewidget
diff --git a/doc/source/widgets/scatterplotwidget.rst b/doc/source/widgets/scatterplotwidget.rst
new file mode 100644
index 00000000..c9d40764
--- /dev/null
+++ b/doc/source/widgets/scatterplotwidget.rst
@@ -0,0 +1,8 @@
+ScatterPlotWidget
+=================
+
+.. autoclass:: pyqtgraph.ScatterPlotWidget
+ :members:
+
+ .. automethod:: pyqtgraph.ScatterPlotWidget.__init__
+
diff --git a/examples/ErrorBarItem.py b/examples/ErrorBarItem.py
new file mode 100644
index 00000000..816e8474
--- /dev/null
+++ b/examples/ErrorBarItem.py
@@ -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_()
diff --git a/examples/GLSurfacePlot.py b/examples/GLSurfacePlot.py
index c901d51e..d2151c46 100644
--- a/examples/GLSurfacePlot.py
+++ b/examples/GLSurfacePlot.py
@@ -62,7 +62,7 @@ w.addItem(p3)
## Animated example
## compute surface vertex data
-cols = 100
+cols = 90
rows = 100
x = np.linspace(-8, 8, cols+1).reshape(cols+1,1)
y = np.linspace(-8, 8, rows+1).reshape(1,rows+1)
diff --git a/examples/GraphItem.py b/examples/GraphItem.py
new file mode 100644
index 00000000..baeaf6c4
--- /dev/null
+++ b/examples/GraphItem.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+"""
+Simple example of GridItem use.
+"""
+
+
+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
+
+w = pg.GraphicsWindow()
+v = w.addViewBox()
+v.setAspectLocked()
+
+g = pg.GraphItem()
+v.addItem(g)
+
+## Define positions of nodes
+pos = np.array([
+ [0,0],
+ [10,0],
+ [0,10],
+ [10,10],
+ [5,5],
+ [15,5]
+ ])
+
+## Define the set of connections in the graph
+adj = np.array([
+ [0,1],
+ [1,3],
+ [3,2],
+ [2,0],
+ [1,5],
+ [3,5],
+ ])
+
+## Define the symbol to use for each node (this is optional)
+symbols = ['o','o','o','o','t','+']
+
+## Define the line style for each connection (this is optional)
+lines = np.array([
+ (255,0,0,255,1),
+ (255,0,255,255,2),
+ (255,0,255,255,3),
+ (255,255,0,255,2),
+ (255,0,0,255,1),
+ (255,255,255,255,4),
+ ], dtype=[('red',np.ubyte),('green',np.ubyte),('blue',np.ubyte),('alpha',np.ubyte),('width',float)])
+
+## Update the graph
+g.setData(pos=pos, adj=adj, pen=lines, size=1, symbol=symbols, pxMode=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_()
diff --git a/examples/LogPlotTest.py b/examples/LogPlotTest.py
new file mode 100644
index 00000000..a5b07520
--- /dev/null
+++ b/examples/LogPlotTest.py
@@ -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_()
diff --git a/examples/ScatterPlot.py b/examples/ScatterPlot.py
index 03e849ad..e72e2631 100644
--- a/examples/ScatterPlot.py
+++ b/examples/ScatterPlot.py
@@ -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)
diff --git a/examples/SimplePlot.py b/examples/SimplePlot.py
new file mode 100644
index 00000000..ec40cf16
--- /dev/null
+++ b/examples/SimplePlot.py
@@ -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_()
diff --git a/examples/__main__.py b/examples/__main__.py
index d8456781..096edba0 100644
--- a/examples/__main__.py
+++ b/examples/__main__.py
@@ -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'),
diff --git a/examples/multiplePlotSpeedTest.py b/examples/multiplePlotSpeedTest.py
new file mode 100644
index 00000000..bc54bb51
--- /dev/null
+++ b/examples/multiplePlotSpeedTest.py
@@ -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_()
diff --git a/examples/multiprocess.py b/examples/multiprocess.py
index f6756345..ba550f7f 100644
--- a/examples/multiprocess.py
+++ b/examples/multiprocess.py
@@ -8,32 +8,32 @@ import time
-print "\n=================\nStart Process"
+print("\n=================\nStart Process")
proc = mp.Process()
import os
-print "parent:", os.getpid(), "child:", proc.proc.pid
-print "started"
+print("parent:", os.getpid(), "child:", proc.proc.pid)
+print("started")
rnp = proc._import('numpy')
arr = rnp.array([1,2,3,4])
-print repr(arr)
-print str(arr)
-print "return value:", repr(arr.mean(_returnType='value'))
-print "return proxy:", repr(arr.mean(_returnType='proxy'))
-print "return auto: ", repr(arr.mean(_returnType='auto'))
+print(repr(arr))
+print(str(arr))
+print("return value:", repr(arr.mean(_returnType='value')))
+print( "return proxy:", repr(arr.mean(_returnType='proxy')))
+print( "return auto: ", repr(arr.mean(_returnType='auto')))
proc.join()
-print "process finished"
+print( "process finished")
-print "\n=================\nStart ForkedProcess"
+print( "\n=================\nStart ForkedProcess")
proc = mp.ForkedProcess()
rnp = proc._import('numpy')
arr = rnp.array([1,2,3,4])
-print repr(arr)
-print str(arr)
-print repr(arr.mean())
+print( repr(arr))
+print( str(arr))
+print( repr(arr.mean()))
proc.join()
-print "process finished"
+print( "process finished")
@@ -42,10 +42,10 @@ import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
app = pg.QtGui.QApplication([])
-print "\n=================\nStart QtProcess"
+print( "\n=================\nStart QtProcess")
import sys
if (sys.flags.interactive != 1):
- print " (not interactive; remote process will exit immediately.)"
+ print( " (not interactive; remote process will exit immediately.)")
proc = mp.QtProcess()
d1 = proc.transfer(np.random.normal(size=1000))
d2 = proc.transfer(np.random.normal(size=1000))
diff --git a/examples/parallelize.py b/examples/parallelize.py
index d2ba0ce0..768d6f00 100644
--- a/examples/parallelize.py
+++ b/examples/parallelize.py
@@ -5,7 +5,7 @@ import pyqtgraph.multiprocess as mp
import pyqtgraph as pg
import time
-print "\n=================\nParallelize"
+print( "\n=================\nParallelize")
## Do a simple task:
## for x in range(N):
@@ -36,7 +36,7 @@ with pg.ProgressDialog('processing serially..', maximum=len(tasks)) as dlg:
dlg += 1
if dlg.wasCanceled():
raise Exception('processing canceled')
-print "Serial time: %0.2f" % (time.time() - start)
+print( "Serial time: %0.2f" % (time.time() - start))
### Use parallelize, but force a single worker
### (this simulates the behavior seen on windows, which lacks os.fork)
@@ -47,8 +47,8 @@ with mp.Parallelize(enumerate(tasks), results=results2, workers=1, progressDialo
for j in xrange(size):
tot += j * x
tasker.results[i] = tot
-print "\nParallel time, 1 worker: %0.2f" % (time.time() - start)
-print "Results match serial: ", results2 == results
+print( "\nParallel time, 1 worker: %0.2f" % (time.time() - start))
+print( "Results match serial: %s" % str(results2 == results))
### Use parallelize with multiple workers
start = time.time()
@@ -58,6 +58,6 @@ with mp.Parallelize(enumerate(tasks), results=results3, progressDialog='processi
for j in xrange(size):
tot += j * x
tasker.results[i] = tot
-print "\nParallel time, %d workers: %0.2f" % (mp.Parallelize.suggestedWorkerCount(), time.time() - start)
-print "Results match serial: ", results3 == results
+print( "\nParallel time, %d workers: %0.2f" % (mp.Parallelize.suggestedWorkerCount(), time.time() - start))
+print( "Results match serial: %s" % str(results3 == results))
diff --git a/examples/parametertree.py b/examples/parametertree.py
index 243fd0fe..9bcbc5d2 100644
--- a/examples/parametertree.py
+++ b/examples/parametertree.py
@@ -70,6 +70,7 @@ params = [
{'name': 'Named List', 'type': 'list', 'values': {"one": 1, "two": 2, "three": 3}, 'value': 2},
{'name': 'Boolean', 'type': 'bool', 'value': True, 'tip': "This is a checkbox"},
{'name': 'Color', 'type': 'color', 'value': "FF0", 'tip': "This is a color button"},
+ {'name': 'Gradient', 'type': 'colormap'},
{'name': 'Subgroup', 'type': 'group', 'children': [
{'name': 'Sub-param 1', 'type': 'int', 'value': 10},
{'name': 'Sub-param 2', 'type': 'float', 'value': 1.2e6},
diff --git a/pyqtgraph/GraphicsScene/GraphicsScene.py b/pyqtgraph/GraphicsScene/GraphicsScene.py
index d0a75d16..8729d085 100644
--- a/pyqtgraph/GraphicsScene/GraphicsScene.py
+++ b/pyqtgraph/GraphicsScene/GraphicsScene.py
@@ -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
diff --git a/pyqtgraph/GraphicsScene/exportDialog.py b/pyqtgraph/GraphicsScene/exportDialog.py
index dafcd501..73a8c83f 100644
--- a/pyqtgraph/GraphicsScene/exportDialog.py
+++ b/pyqtgraph/GraphicsScene/exportDialog.py
@@ -27,6 +27,7 @@ class ExportDialog(QtGui.QWidget):
self.ui.closeBtn.clicked.connect(self.close)
self.ui.exportBtn.clicked.connect(self.exportClicked)
+ self.ui.copyBtn.clicked.connect(self.copyClicked)
self.ui.itemTree.currentItemChanged.connect(self.exportItemChanged)
self.ui.formatList.currentItemChanged.connect(self.exportFormatChanged)
@@ -116,11 +117,16 @@ class ExportDialog(QtGui.QWidget):
else:
self.ui.paramTree.setParameters(params)
self.currentExporter = exp
+ self.ui.copyBtn.setEnabled(exp.allowCopy)
def exportClicked(self):
self.selectBox.hide()
self.currentExporter.export()
+ def copyClicked(self):
+ self.selectBox.hide()
+ self.currentExporter.export(copy=True)
+
def close(self):
self.selectBox.setVisible(False)
self.setVisible(False)
diff --git a/pyqtgraph/GraphicsScene/exportDialogTemplate.ui b/pyqtgraph/GraphicsScene/exportDialogTemplate.ui
index c81c8831..c91fbc3f 100644
--- a/pyqtgraph/GraphicsScene/exportDialogTemplate.ui
+++ b/pyqtgraph/GraphicsScene/exportDialogTemplate.ui
@@ -79,6 +79,13 @@
+
+
+
+ Copy
+
+
+
diff --git a/pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt.py b/pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt.py
index 20609b51..c3056d1c 100644
--- a/pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt.py
+++ b/pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt.py
@@ -2,8 +2,8 @@
# Form implementation generated from reading ui file './GraphicsScene/exportDialogTemplate.ui'
#
-# Created: Sun Sep 9 14:41:31 2012
-# by: PyQt4 UI code generator 4.9.1
+# Created: Wed Jan 30 21:02:28 2013
+# by: PyQt4 UI code generator 4.9.3
#
# WARNING! All changes made in this file will be lost!
@@ -49,6 +49,9 @@ class Ui_Form(object):
self.label_3 = QtGui.QLabel(Form)
self.label_3.setObjectName(_fromUtf8("label_3"))
self.gridLayout.addWidget(self.label_3, 4, 0, 1, 3)
+ self.copyBtn = QtGui.QPushButton(Form)
+ self.copyBtn.setObjectName(_fromUtf8("copyBtn"))
+ self.gridLayout.addWidget(self.copyBtn, 6, 0, 1, 1)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
@@ -60,5 +63,6 @@ class Ui_Form(object):
self.exportBtn.setText(QtGui.QApplication.translate("Form", "Export", None, QtGui.QApplication.UnicodeUTF8))
self.closeBtn.setText(QtGui.QApplication.translate("Form", "Close", None, QtGui.QApplication.UnicodeUTF8))
self.label_3.setText(QtGui.QApplication.translate("Form", "Export options", None, QtGui.QApplication.UnicodeUTF8))
+ self.copyBtn.setText(QtGui.QApplication.translate("Form", "Copy", None, QtGui.QApplication.UnicodeUTF8))
from pyqtgraph.parametertree import ParameterTree
diff --git a/pyqtgraph/GraphicsScene/exportDialogTemplate_pyside.py b/pyqtgraph/GraphicsScene/exportDialogTemplate_pyside.py
index 4ffc0b9a..cf27f60a 100644
--- a/pyqtgraph/GraphicsScene/exportDialogTemplate_pyside.py
+++ b/pyqtgraph/GraphicsScene/exportDialogTemplate_pyside.py
@@ -2,8 +2,8 @@
# Form implementation generated from reading ui file './GraphicsScene/exportDialogTemplate.ui'
#
-# Created: Sun Sep 9 14:41:31 2012
-# by: pyside-uic 0.2.13 running on PySide 1.1.0
+# Created: Wed Jan 30 21:02:28 2013
+# by: pyside-uic 0.2.13 running on PySide 1.1.1
#
# WARNING! All changes made in this file will be lost!
@@ -44,6 +44,9 @@ class Ui_Form(object):
self.label_3 = QtGui.QLabel(Form)
self.label_3.setObjectName("label_3")
self.gridLayout.addWidget(self.label_3, 4, 0, 1, 3)
+ self.copyBtn = QtGui.QPushButton(Form)
+ self.copyBtn.setObjectName("copyBtn")
+ self.gridLayout.addWidget(self.copyBtn, 6, 0, 1, 1)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
@@ -55,5 +58,6 @@ class Ui_Form(object):
self.exportBtn.setText(QtGui.QApplication.translate("Form", "Export", None, QtGui.QApplication.UnicodeUTF8))
self.closeBtn.setText(QtGui.QApplication.translate("Form", "Close", None, QtGui.QApplication.UnicodeUTF8))
self.label_3.setText(QtGui.QApplication.translate("Form", "Export options", None, QtGui.QApplication.UnicodeUTF8))
+ self.copyBtn.setText(QtGui.QApplication.translate("Form", "Copy", None, QtGui.QApplication.UnicodeUTF8))
from pyqtgraph.parametertree import ParameterTree
diff --git a/pyqtgraph/PlotData.py b/pyqtgraph/PlotData.py
new file mode 100644
index 00000000..18531c14
--- /dev/null
+++ b/pyqtgraph/PlotData.py
@@ -0,0 +1,55 @@
+
+
+class PlotData(object):
+ """
+ Class used for managing plot data
+ - allows data sharing between multiple graphics items (curve, scatter, graph..)
+ - each item may define the columns it needs
+ - column groupings ('pos' or x, y, z)
+ - efficiently appendable
+ - log, fft transformations
+ - color mode conversion (float/byte/qcolor)
+ - pen/brush conversion
+ - per-field cached masking
+ - allows multiple masking fields (different graphics need to mask on different criteria)
+ - removal of nan/inf values
+ - option for single value shared by entire column
+ - cached downsampling
+ """
+ def __init__(self):
+ self.fields = {}
+
+ self.maxVals = {} ## cache for max/min
+ self.minVals = {}
+
+ def addFields(self, fields):
+ for f in fields:
+ if f not in self.fields:
+ self.fields[f] = None
+
+ def hasField(self, f):
+ return f in self.fields
+
+ def __getitem__(self, field):
+ return self.fields[field]
+
+ def __setitem__(self, field, val):
+ self.fields[field] = val
+
+ def max(self, field):
+ mx = self.maxVals.get(field, None)
+ if mx is None:
+ mx = np.max(self[field])
+ self.maxVals[field] = mx
+ return mx
+
+ def min(self, field):
+ mn = self.minVals.get(field, None)
+ if mn is None:
+ mn = np.min(self[field])
+ self.minVals[field] = mn
+ return mn
+
+
+
+
\ No newline at end of file
diff --git a/pyqtgraph/__init__.py b/pyqtgraph/__init__.py
index 93d9f7b8..d3aefa83 100644
--- a/pyqtgraph/__init__.py
+++ b/pyqtgraph/__init__.py
@@ -16,6 +16,9 @@ from .Qt import QtGui
#if QtGui.QApplication.instance() is None:
#app = QtGui.QApplication([])
+import numpy ## pyqtgraph requires numpy
+ ## (import here to avoid massive error dump later on if numpy is not available)
+
import os, sys
## check python version
@@ -49,6 +52,8 @@ CONFIG_OPTIONS = {
'background': (0, 0, 0), ## default background for GraphicsWidget
'antialias': False,
'editorCommand': None, ## command used to invoke code editor from ConsoleWidgets
+ 'useWeave': True, ## Use weave to speed up some operations, if it is available
+ 'weaveDebug': False, ## Print full error message if weave compile fails
}
@@ -182,6 +187,7 @@ from .SRTTransform3D import SRTTransform3D
from .functions import *
from .graphicsWindows import *
from .SignalProxy import *
+from .colormap import *
from .ptime import time
diff --git a/pyqtgraph/colormap.py b/pyqtgraph/colormap.py
new file mode 100644
index 00000000..d6169209
--- /dev/null
+++ b/pyqtgraph/colormap.py
@@ -0,0 +1,239 @@
+import numpy as np
+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 `.
+ """
+
+
+ ## color interpolation modes
+ RGB = 1
+ HSV_POS = 2
+ HSV_NEG = 3
+
+ ## boundary modes
+ CLIP = 1
+ REPEAT = 2
+ MIRROR = 3
+
+ ## return types
+ BYTE = 1
+ FLOAT = 2
+ QCOLOR = 3
+
+ enumMap = {
+ 'rgb': RGB,
+ 'hsv+': HSV_POS,
+ 'hsv-': HSV_NEG,
+ 'clip': CLIP,
+ 'repeat': REPEAT,
+ 'mirror': MIRROR,
+ 'byte': BYTE,
+ 'float': FLOAT,
+ 'qcolor': QCOLOR,
+ }
+
+ def __init__(self, pos, color, mode=None):
+ """
+ ========= ==============================================================
+ Arguments
+ pos Array of positions where each color is defined
+ color Array of RGBA colors.
+ Integer data types are interpreted as 0-255; float data types
+ are interpreted as 0.0-1.0
+ mode Array of color modes (ColorMap.RGB, HSV_POS, or HSV_NEG)
+ indicating the color space that should be used when
+ interpolating between stops. Note that the last mode value is
+ ignored. By default, the mode is entirely RGB.
+ ========= ==============================================================
+ """
+ self.pos = pos
+ self.color = color
+ if mode is None:
+ mode = np.ones(len(pos))
+ self.mode = mode
+ self.stopsCache = {}
+
+ 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()]
+
+ if mode == self.QCOLOR:
+ pos, color = self.getStops(self.BYTE)
+ else:
+ pos, color = self.getStops(mode)
+
+ data = np.clip(data, pos.min(), pos.max())
+
+ if not isinstance(data, np.ndarray):
+ interp = scipy.interpolate.griddata(pos, color, np.array([data]))[0]
+ else:
+ interp = scipy.interpolate.griddata(pos, color, data)
+
+ if mode == self.QCOLOR:
+ if not isinstance(data, np.ndarray):
+ return QtGui.QColor(*interp)
+ else:
+ return [QtGui.QColor(*x) for x in interp]
+ else:
+ return interp
+
+ def mapToQColor(self, data):
+ """Convenience function; see :func:`map() `."""
+ return self.map(data, mode=self.QCOLOR)
+
+ def mapToByte(self, data):
+ """Convenience function; see :func:`map() `."""
+ return self.map(data, mode=self.BYTE)
+
+ def mapToFloat(self, data):
+ """Convenience function; see :func:`map() `."""
+ return self.map(data, mode=self.FLOAT)
+
+ def getGradient(self, p1=None, p2=None):
+ """Return a QLinearGradient object spanning from QPoints p1 to p2."""
+ if p1 == None:
+ p1 = QtCore.QPointF(0,0)
+ if p2 == None:
+ p2 = QtCore.QPointF(self.pos.max()-self.pos.min(),0)
+ g = QtGui.QLinearGradient(p1, p2)
+
+ pos, color = self.getStops(mode=self.BYTE)
+ color = [QtGui.QColor(*x) for x in color]
+ g.setStops(zip(pos, color))
+
+ #if self.colorMode == 'rgb':
+ #ticks = self.listTicks()
+ #g.setStops([(x, QtGui.QColor(t.color)) for t,x in ticks])
+ #elif self.colorMode == 'hsv': ## HSV mode is approximated for display by interpolating 10 points between each stop
+ #ticks = self.listTicks()
+ #stops = []
+ #stops.append((ticks[0][1], ticks[0][0].color))
+ #for i in range(1,len(ticks)):
+ #x1 = ticks[i-1][1]
+ #x2 = ticks[i][1]
+ #dx = (x2-x1) / 10.
+ #for j in range(1,10):
+ #x = x1 + dx*j
+ #stops.append((x, self.getColor(x)))
+ #stops.append((x2, self.getColor(x2)))
+ #g.setStops(stops)
+ return g
+
+ def getColors(self, mode=None):
+ """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()]
+
+ color = self.color
+ if mode in [self.BYTE, self.QCOLOR] and color.dtype.kind == 'f':
+ color = (color * 255).astype(np.ubyte)
+ elif mode == self.FLOAT and color.dtype.kind != 'f':
+ color = color.astype(float) / 255.
+
+ if mode == self.QCOLOR:
+ color = [QtGui.QColor(*x) for x in color]
+
+ return color
+
+ def getStops(self, mode):
+ ## Get fully-expanded set of RGBA stops in either float or byte mode.
+ if mode not in self.stopsCache:
+ color = self.color
+ if mode == self.BYTE and color.dtype.kind == 'f':
+ color = (color * 255).astype(np.ubyte)
+ elif mode == self.FLOAT and color.dtype.kind != 'f':
+ color = color.astype(float) / 255.
+
+ ## to support HSV mode, we need to do a little more work..
+ #stops = []
+ #for i in range(len(self.pos)):
+ #pos = self.pos[i]
+ #color = color[i]
+
+ #imode = self.mode[i]
+ #if imode == self.RGB:
+ #stops.append((x,color))
+ #else:
+ #ns =
+ self.stopsCache[mode] = (self.pos, color)
+ return self.stopsCache[mode]
+
+ def getLookupTable(self, start=0.0, stop=1.0, nPts=512, alpha=None, mode='byte'):
+ """
+ Return an RGB(A) lookup table (ndarray).
+
+ ============= ============================================================================
+ **Arguments**
+ 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() `.
+ ============= ============================================================================
+ """
+ if isinstance(mode, basestring):
+ mode = self.enumMap[mode.lower()]
+
+ if alpha is None:
+ alpha = self.usesAlpha()
+
+ x = np.linspace(start, stop, nPts)
+ table = self.map(x, mode)
+
+ if not alpha:
+ return table[:,:3]
+ else:
+ return table
+
+ def usesAlpha(self):
+ """Return True if any stops have an alpha < 255"""
+ max = 1.0 if self.color.dtype.kind == 'f' else 255
+ 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.
+ """
+ if len(self.pos) != 2:
+ return False
+ if self.pos[0] != 0.0 or self.pos[1] != 1.0:
+ return False
+ if self.color.dtype.kind == 'f':
+ return np.all(self.color == np.array([[0.,0.,0.,1.], [1.,1.,1.,1.]]))
+ else:
+ return np.all(self.color == np.array([[0,0,0,255], [255,255,255,255]]))
+
+
diff --git a/pyqtgraph/debug.py b/pyqtgraph/debug.py
index d5f86139..ae2b21ac 100644
--- a/pyqtgraph/debug.py
+++ b/pyqtgraph/debug.py
@@ -393,7 +393,7 @@ class Profiler:
if self.delayed:
self.msgs.append(msg2)
else:
- print msg2
+ print(msg2)
self.t0 = ptime.time()
self.t1 = self.t0
@@ -410,7 +410,7 @@ class Profiler:
if self.delayed:
self.msgs.append(msg2)
else:
- print msg2
+ print(msg2)
self.t1 = ptime.time() ## don't measure time it took to print
def finish(self, msg=None):
@@ -425,10 +425,10 @@ class Profiler:
self.msgs.append(msg)
if self.depth == 0:
for line in self.msgs:
- print line
+ print(line)
Profiler.msgs = []
else:
- print msg
+ print(msg)
Profiler.depth = self.depth
self.finished = True
@@ -917,3 +917,21 @@ def qObjectReport(verbose=False):
for t in typs:
print(count[t], "\t", t)
+
+class PrintDetector(object):
+ def __init__(self):
+ self.stdout = sys.stdout
+ sys.stdout = self
+
+ def remove(self):
+ sys.stdout = self.stdout
+
+ def __del__(self):
+ self.remove()
+
+ def write(self, x):
+ self.stdout.write(x)
+ traceback.print_stack()
+
+ def flush(self):
+ self.stdout.flush()
\ No newline at end of file
diff --git a/pyqtgraph/exporters/Exporter.py b/pyqtgraph/exporters/Exporter.py
index b1a663bc..f5a93088 100644
--- a/pyqtgraph/exporters/Exporter.py
+++ b/pyqtgraph/exporters/Exporter.py
@@ -9,7 +9,8 @@ class Exporter(object):
"""
Abstract class used for exporting graphics to file / printer / whatever.
"""
-
+ allowCopy = False # subclasses set this to True if they can use the copy buffer
+
def __init__(self, item):
"""
Initialize with the item to be exported.
@@ -25,10 +26,11 @@ class Exporter(object):
"""Return the parameters used to configure this exporter."""
raise Exception("Abstract method must be overridden in subclass.")
- def export(self, fileName=None, toBytes=False):
+ def export(self, fileName=None, toBytes=False, copy=False):
"""
If *fileName* is None, pop-up a file dialog.
- If *toString* is True, return a bytes object rather than writing to file.
+ If *toBytes* is True, return a bytes object rather than writing to file.
+ If *copy* is True, export to the copy buffer rather than writing to file.
"""
raise Exception("Abstract method must be overridden in subclass.")
@@ -64,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)
diff --git a/pyqtgraph/exporters/ImageExporter.py b/pyqtgraph/exporters/ImageExporter.py
index cb6cf396..bdb8b9be 100644
--- a/pyqtgraph/exporters/ImageExporter.py
+++ b/pyqtgraph/exporters/ImageExporter.py
@@ -8,6 +8,8 @@ __all__ = ['ImageExporter']
class ImageExporter(Exporter):
Name = "Image File (PNG, TIF, JPG, ...)"
+ allowCopy = True
+
def __init__(self, item):
Exporter.__init__(self, item)
tr = self.getTargetRect()
@@ -38,8 +40,8 @@ class ImageExporter(Exporter):
def parameters(self):
return self.params
- def export(self, fileName=None):
- if fileName is None:
+ def export(self, fileName=None, toBytes=False, copy=False):
+ if fileName is None and not toBytes and not copy:
filter = ["*."+str(f) for f in QtGui.QImageWriter.supportedImageFormats()]
preferred = ['*.png', '*.tif', '*.jpg']
for p in preferred[::-1]:
@@ -78,6 +80,12 @@ class ImageExporter(Exporter):
finally:
self.setExportMode(False)
painter.end()
- self.png.save(fileName)
+
+ if copy:
+ QtGui.QApplication.clipboard().setImage(self.png)
+ elif toBytes:
+ return self.png
+ else:
+ self.png.save(fileName)
\ No newline at end of file
diff --git a/pyqtgraph/exporters/SVGExporter.py b/pyqtgraph/exporters/SVGExporter.py
index 70f1f632..b284db89 100644
--- a/pyqtgraph/exporters/SVGExporter.py
+++ b/pyqtgraph/exporters/SVGExporter.py
@@ -11,6 +11,8 @@ __all__ = ['SVGExporter']
class SVGExporter(Exporter):
Name = "Scalable Vector Graphics (SVG)"
+ allowCopy=True
+
def __init__(self, item):
Exporter.__init__(self, item)
#tr = self.getTargetRect()
@@ -37,8 +39,8 @@ class SVGExporter(Exporter):
def parameters(self):
return self.params
- def export(self, fileName=None, toBytes=False):
- if toBytes is False and fileName is None:
+ def export(self, fileName=None, toBytes=False, copy=False):
+ if toBytes is False and copy is False and fileName is None:
self.fileSaveDialog(filter="Scalable Vector Graphics (*.svg)")
return
#self.svg = QtSvg.QSvgGenerator()
@@ -83,11 +85,16 @@ class SVGExporter(Exporter):
xml = generateSvg(self.item)
if toBytes:
- return bytes(xml)
+ return xml.encode('UTF-8')
+ elif copy:
+ md = QtCore.QMimeData()
+ md.setData('image/svg+xml', QtCore.QByteArray(xml.encode('UTF-8')))
+ QtGui.QApplication.clipboard().setMimeData(md)
else:
with open(fileName, 'w') as fh:
fh.write(xml.encode('UTF-8'))
+
xmlHeader = """\