Commit 8828892e authored by Luke Campagnola's avatar Luke Campagnola
Browse files

merged many changes from acq4

parent f029e789
......@@ -10,6 +10,9 @@ import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
# Enable antialiasing for prettier plots
pg.setConfigOptions(antialias=True)
w = pg.GraphicsWindow()
w.setWindowTitle('pyqtgraph example: GraphItem')
v = w.addViewBox()
......
......@@ -21,7 +21,8 @@ win = pg.GraphicsWindow(title="Basic plotting examples")
win.resize(1000,600)
win.setWindowTitle('pyqtgraph example: Plotting')
# Enable antialiasing for prettier plots
pg.setConfigOptions(antialias=True)
p1 = win.addPlot(title="Basic array plotting", y=np.random.normal(size=100))
......
# -*- coding: utf-8 -*-
"""
Demonstrates ScaleBar
"""
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
pg.mkQApp()
win = pg.GraphicsWindow()
win.setWindowTitle('pyqtgraph example: ScaleBar')
vb = win.addViewBox()
vb.setAspectLocked()
img = pg.ImageItem()
img.setImage(np.random.normal(size=(100,100)))
img.scale(0.01, 0.01)
vb.addItem(img)
scale = pg.ScaleBar(size=0.1)
scale.setParentItem(vb)
scale.anchor((1, 1), (1, 1), offset=(-20, -20))
## 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_()
......@@ -16,8 +16,22 @@ data = np.array([
(3, 2, 5, 2, 'z'),
(4, 4, 6, 9, 'z'),
(5, 3, 6, 7, 'x'),
(6, 5, 2, 6, 'y'),
(7, 5, 7, 2, 'z'),
(6, 5, 4, 6, 'x'),
(7, 5, 8, 2, 'z'),
(8, 1, 2, 4, 'x'),
(9, 2, 3, 7, 'z'),
(0, 6, 0, 2, 'z'),
(1, 3, 1, 2, 'z'),
(2, 5, 4, 6, 'y'),
(3, 4, 8, 1, 'y'),
(4, 7, 6, 8, 'z'),
(5, 8, 7, 4, 'y'),
(6, 1, 2, 3, 'y'),
(7, 5, 3, 9, 'z'),
(8, 9, 3, 1, 'x'),
(9, 2, 6, 2, 'z'),
(0, 3, 4, 6, 'x'),
(1, 5, 9, 3, 'y'),
], dtype=[('col1', float), ('col2', float), ('col3', int), ('col4', int), ('col5', 'S10')])
spw.setFields([
......
......@@ -3,9 +3,12 @@ import initExample ## Add path to library (just for examples; you do not need th
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")
plt = pg.plot(np.random.normal(size=100), title="Simplest possible plotting example")
plt.getAxis('bottom').setTicks([[(x*20, str(x*20)) for x in range(6)]])
## Start Qt event loop unless running in interactive mode or using pyside.
ex = pg.exporters.SVGExporter.SVGExporter(plt.plotItem.scene())
ex.export('/home/luke/tmp/test.svg')
if __name__ == '__main__':
import sys
if sys.flags.interactive != 1 or not hasattr(QtCore, 'PYQT_VERSION'):
......
# -*- coding: utf-8 -*-
"""
Example beeswarm / bar chart
"""
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
win = pg.plot()
win.setWindowTitle('pyqtgraph example: beeswarm')
data = np.random.normal(size=(4,20))
data[0] += 5
data[1] += 7
data[2] += 5
data[3] = 10 + data[3] * 2
## Make bar graph
#bar = pg.BarGraphItem(x=range(4), height=data.mean(axis=1), width=0.5, brush=0.4)
#win.addItem(bar)
## add scatter plots on top
for i in range(4):
xvals = pg.pseudoScatter(data[i], spacing=0.4, bidir=True) * 0.2
win.plot(x=xvals+i, y=data[i], pen=None, symbol='o', symbolBrush=pg.intColor(i,6,maxValue=128))
## Make error bars
err = pg.ErrorBarItem(x=np.arange(4), y=data.mean(axis=1), height=data.std(axis=1), beam=0.5, pen={'color':'w', 'width':2})
win.addItem(err)
## 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_()
......@@ -304,7 +304,36 @@ def _generateItemSvg(item, nodes=None, root=None):
def correctCoordinates(node, item):
## Remove transformation matrices from <g> tags by applying matrix to coordinates inside.
## Each item is represented by a single top-level group with one or more groups inside.
## Each inner group contains one or more drawing primitives, possibly of different types.
groups = node.getElementsByTagName('g')
## Since we leave text unchanged, groups which combine text and non-text primitives must be split apart.
## (if at some point we start correcting text transforms as well, then it should be safe to remove this)
groups2 = []
for grp in groups:
subGroups = [grp.cloneNode(deep=False)]
textGroup = None
for ch in grp.childNodes[:]:
if isinstance(ch, xml.Element):
if textGroup is None:
textGroup = ch.tagName == 'text'
if ch.tagName == 'text':
if textGroup is False:
subGroups.append(grp.cloneNode(deep=False))
textGroup = True
else:
if textGroup is True:
subGroups.append(grp.cloneNode(deep=False))
textGroup = False
subGroups[-1].appendChild(ch)
groups2.extend(subGroups)
for sg in subGroups:
node.insertBefore(sg, grp)
node.removeChild(grp)
groups = groups2
for grp in groups:
matrix = grp.getAttribute('transform')
match = re.match(r'matrix\((.*)\)', matrix)
......@@ -374,7 +403,6 @@ def correctCoordinates(node, item):
if removeTransform:
grp.removeAttribute('transform')
def itemTransform(item, root):
## Return the transformation mapping item to root
......
......@@ -1930,9 +1930,9 @@ def invertQTransform(tr):
return QtGui.QTransform(inv[0,0], inv[0,1], inv[0,2], inv[1,0], inv[1,1], inv[1,2], inv[2,0], inv[2,1])
def pseudoScatter(data, spacing=None, shuffle=True):
def pseudoScatter(data, spacing=None, shuffle=True, bidir=False):
"""
Used for examining the distribution of values in a set.
Used for examining the distribution of values in a set. Produces scattering as in beeswarm or column scatter plots.
Given a list of x-values, construct a set of y-values such that an x,y scatter-plot
will not have overlapping points (it will look similar to a histogram).
......@@ -1959,23 +1959,41 @@ def pseudoScatter(data, spacing=None, shuffle=True):
xmask = dx < s2 # exclude anything too far away
if xmask.sum() > 0:
dx = dx[xmask]
dy = (s2 - dx)**0.5
limits = np.empty((2,len(dy))) # ranges of y-values to exclude
limits[0] = y0[xmask] - dy
limits[1] = y0[xmask] + dy
while True:
# ignore anything below this y-value
mask = limits[1] >= y
limits = limits[:,mask]
# are we inside an excluded region?
mask = (limits[0] < y) & (limits[1] > y)
if mask.sum() == 0:
break
y = limits[:,mask].max()
if bidir:
dirs = [-1, 1]
else:
dirs = [1]
yopts = []
for direction in dirs:
y = 0
dx2 = dx[xmask]
dy = (s2 - dx2)**0.5
limits = np.empty((2,len(dy))) # ranges of y-values to exclude
limits[0] = y0[xmask] - dy
limits[1] = y0[xmask] + dy
while True:
# ignore anything below this y-value
if direction > 0:
mask = limits[1] >= y
else:
mask = limits[0] <= y
limits2 = limits[:,mask]
# are we inside an excluded region?
mask = (limits2[0] < y) & (limits2[1] > y)
if mask.sum() == 0:
break
if direction > 0:
y = limits2[:,mask].max()
else:
y = limits2[:,mask].min()
yopts.append(y)
if bidir:
y = yopts[0] if -yopts[0] < yopts[1] else yopts[1]
else:
y = yopts[0]
yvals[i] = y
return yvals[np.argsort(inds)] ## un-shuffle values before returning
import pyqtgraph as pg
from pyqtgraph.Qt import QtGui, QtCore
from .GraphicsObject import GraphicsObject
import numpy as np
__all__ = ['BarGraphItem']
class BarGraphItem(GraphicsObject):
def __init__(self, **opts):
"""
Valid keyword options are:
x, x0, x1, y, y0, y1, width, height, pen, brush
x specifies the x-position of the center of the bar.
x0, x1 specify left and right edges of the bar, respectively.
width specifies distance from x0 to x1.
You may specify any combination:
x, width
x0, width
x1, width
x0, x1
Likewise y, y0, y1, and height.
If only height is specified, then y0 will be set to 0
Example uses:
BarGraphItem(x=range(5), height=[1,5,2,4,3], width=0.5)
"""
GraphicsObject.__init__(self)
self.opts = dict(
x=None,
y=None,
x0=None,
y0=None,
x1=None,
y1=None,
height=None,
width=None,
pen=None,
brush=None,
pens=None,
brushes=None,
)
self.setOpts(**opts)
def setOpts(self, **opts):
self.opts.update(opts)
self.picture = None
self.update()
self.informViewBoundsChanged()
def drawPicture(self):
self.picture = QtGui.QPicture()
p = QtGui.QPainter(self.picture)
pen = self.opts['pen']
pens = self.opts['pens']
if pen is None and pens is None:
pen = pg.getConfigOption('foreground')
brush = self.opts['brush']
brushes = self.opts['brushes']
if brush is None and brushes is None:
brush = (128, 128, 128)
def asarray(x):
if x is None or np.isscalar(x) or isinstance(x, np.ndarray):
return x
return np.array(x)
x = asarray(self.opts.get('x'))
x0 = asarray(self.opts.get('x0'))
x1 = asarray(self.opts.get('x1'))
width = asarray(self.opts.get('width'))
if x0 is None:
if width is None:
raise Exception('must specify either x0 or width')
if x1 is not None:
x0 = x1 - width
elif x is not None:
x0 = x - width/2.
else:
raise Exception('must specify at least one of x, x0, or x1')
if width is None:
if x1 is None:
raise Exception('must specify either x1 or width')
width = x1 - x0
y = asarray(self.opts.get('y'))
y0 = asarray(self.opts.get('y0'))
y1 = asarray(self.opts.get('y1'))
height = asarray(self.opts.get('height'))
if y0 is None:
if height is None:
y0 = 0
elif y1 is not None:
y0 = y1 - height
elif y is not None:
y0 = y - height/2.
else:
y0 = 0
if height is None:
if y1 is None:
raise Exception('must specify either y1 or height')
height = y1 - y0
p.setPen(pg.mkPen(pen))
p.setBrush(pg.mkBrush(brush))
for i in range(len(x0)):
if pens is not None:
p.setPen(pg.mkPen(pens[i]))
if brushes is not None:
p.setBrush(pg.mkBrush(brushes[i]))
if np.isscalar(y0):
y = y0
else:
y = y0[i]
if np.isscalar(width):
w = width
else:
w = width[i]
p.drawRect(QtCore.QRectF(x0[i], y, w, height[i]))
p.end()
self.prepareGeometryChange()
def paint(self, p, *args):
if self.picture is None:
self.drawPicture()
self.picture.play(p)
def boundingRect(self):
if self.picture is None:
self.drawPicture()
return QtCore.QRectF(self.picture.boundingRect())
\ No newline at end of file
......@@ -103,6 +103,8 @@ class GraphItem(GraphicsObject):
def paint(self, p, *args):
if self.picture == None:
self.generatePicture()
if pg.getConfigOption('antialias') is True:
p.setRenderHint(p.Antialiasing)
self.picture.play(p)
def boundingRect(self):
......
......@@ -446,6 +446,14 @@ class GraphicsItem(object):
#print " --> ", ch2.scene()
#self.setChildScene(ch2)
def parentChanged(self):
"""Called when the item's parent has changed.
This method handles connecting / disconnecting from ViewBox signals
to make sure viewRangeChanged works properly. It should generally be
extended, not overridden."""
self._updateView()
def _updateView(self):
## called to see whether this item has a new view to connect to
## NOTE: This is called from GraphicsObject.itemChange or GraphicsWidget.itemChange.
......@@ -496,6 +504,12 @@ class GraphicsItem(object):
## inform children that their view might have changed
self._replaceView(oldView)
self.viewChanged(view, oldView)
def viewChanged(self, view, oldView):
"""Called when this item's view has changed
(ie, the item has been added to or removed from a ViewBox)"""
pass
def _replaceView(self, oldView, item=None):
if item is None:
......
......@@ -19,7 +19,7 @@ class GraphicsObject(GraphicsItem, QtGui.QGraphicsObject):
def itemChange(self, change, value):
ret = QtGui.QGraphicsObject.itemChange(self, change, value)
if change in [self.ItemParentHasChanged, self.ItemSceneHasChanged]:
self._updateView()
self.parentChanged()
if change in [self.ItemPositionHasChanged, self.ItemTransformHasChanged]:
self.informViewBoundsChanged()
......
......@@ -5,7 +5,9 @@ from ..Point import Point
class GraphicsWidgetAnchor(object):
"""
Class used to allow GraphicsWidgets to anchor to a specific position on their
parent.
parent. The item will be automatically repositioned if the parent is resized.
This is used, for example, to anchor a LegendItem to a corner of its parent
PlotItem.
"""
......
......@@ -2,11 +2,12 @@ from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph.functions as fn
import pyqtgraph as pg
from .GraphicsWidget import GraphicsWidget
from .GraphicsWidgetAnchor import GraphicsWidgetAnchor
__all__ = ['LabelItem']
class LabelItem(GraphicsWidget):
class LabelItem(GraphicsWidget, GraphicsWidgetAnchor):
"""
GraphicsWidget displaying text.
Used mainly as axis labels, titles, etc.
......@@ -17,6 +18,7 @@ class LabelItem(GraphicsWidget):
def __init__(self, text=' ', parent=None, angle=0, **args):
GraphicsWidget.__init__(self, parent)
GraphicsWidgetAnchor.__init__(self)
self.item = QtGui.QGraphicsTextItem(self)
self.opts = {
'color': None,
......
......@@ -402,7 +402,6 @@ class PlotCurveItem(GraphicsObject):
aa = self.opts['antialias']
p.setRenderHint(p.Antialiasing, aa)
if self.opts['brush'] is not None and self.opts['fillLevel'] is not None:
if self.fillPath is None:
......
from pyqtgraph.Qt import QtGui, QtCore
from .UIGraphicsItem import *
from .GraphicsObject import *
from .GraphicsWidgetAnchor import *
from .TextItem import TextItem
import numpy as np
import pyqtgraph.functions as fn
import pyqtgraph as pg
__all__ = ['ScaleBar']
class ScaleBar(UIGraphicsItem):
class ScaleBar(GraphicsObject, GraphicsWidgetAnchor):
"""
Displays a rectangular bar with 10 divisions to indicate the relative scale of objects on the view.
Displays a rectangular bar to indicate the relative scale of objects on the view.
"""
def __init__(self, size, width=5, color=(100, 100, 255)):
UIGraphicsItem.__init__(self)
def __init__(self, size, width=5, brush=None, pen=None, suffix='m'):
GraphicsObject.__init__(self)
GraphicsWidgetAnchor.__init__(self)
self.setFlag(self.ItemHasNoContents)
self.setAcceptedMouseButtons(QtCore.Qt.NoButton)
self.brush = fn.mkBrush(color)
self.pen = fn.mkPen((0,0,0))
if brush is None:
brush = pg.getConfigOption('foreground')
self.brush = fn.mkBrush(brush)
self.pen = fn.mkPen(pen)
self._width = width
self.size = size
def paint(self, p, opt, widget):
UIGraphicsItem.paint(self, p, opt, widget)
rect = self.boundingRect()
unit = self.pixelSize()
y = rect.top() + (rect.bottom()-rect.top()) * 0.02
y1 = y + unit[1]*self._width
x = rect.right() + (rect.left()-rect.right()) * 0.02
x1 = x - self.size
p.setPen(self.pen)
p.setBrush(self.brush)
rect = QtCore.QRectF(
QtCore.QPointF(x1, y1),
QtCore.QPointF(x, y)
)
p.translate(x1, y1)
p.scale(rect.width(), rect.height())
p.drawRect(0, 0, 1, 1)
alpha = np.clip(((self.size/unit[0]) - 40.) * 255. / 80., 0, 255)
p.setPen(QtGui.QPen(QtGui.QColor(0, 0, 0, alpha)))
for i in range(1, 10):
#x2 = x + (x1-x) * 0.1 * i
x2 = 0.1 * i
p.drawLine(QtCore.QPointF(x2, 0), QtCore.QPointF(x2, 1))
def setSize(self, s):
self.size = s
self.bar = QtGui.QGraphicsRectItem()
self.bar.setPen(self.pen)
self.bar.setBrush(self.brush)
self.bar.setParentItem(self)
self.text = TextItem(text=fn.siFormat(size, suffix=suffix), anchor=(0.5,1))
self.text.setParentItem(self)
def parentChanged(self):
view = self.parentItem()
if view is None:
return
view.sigRangeChanged.connect(self.updateBar)
self.updateBar()
def updateBar(self):
view = self.parentItem()
if view is None:
return
p1 = view.mapFromViewToItem(self, QtCore.QPointF(0,0))
p2 = view.mapFromViewToItem(self, QtCore.QPointF(self.size,0))
w = (p2-p1).x()
self.bar.setRect(QtCore.QRectF(-w, 0, w, self._width))
self.text.setPos(-w/2., 0)
def boundingRect(self):
return QtCore.QRectF()