merged many changes from acq4
This commit is contained in:
parent
f029e7893e
commit
8828892e55
@ -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))
|
||||
|
||||
|
31
examples/ScaleBar.py
Normal file
31
examples/ScaleBar.py
Normal file
@ -0,0 +1,31 @@
|
||||
# -*- 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'):
|
||||
|
38
examples/beeswarm.py
Normal file
38
examples/beeswarm.py
Normal file
@ -0,0 +1,38 @@
|
||||
# -*- 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
|
||||
|
149
pyqtgraph/graphicsItems/BarGraphItem.py
Normal file
149
pyqtgraph/graphicsItems/BarGraphItem.py
Normal file
@ -0,0 +1,149 @@
|
||||
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())
|
||||
|
||||
|
@ -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:
|
||||
|
@ -1,50 +1,104 @@
|
||||
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)
|
||||
self.bar = QtGui.QGraphicsRectItem()
|
||||
self.bar.setPen(self.pen)
|
||||
self.bar.setBrush(self.brush)
|
||||
self.bar.setParentItem(self)
|
||||
|
||||
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
|
||||
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()
|
||||
|
||||
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 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()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#class ScaleBar(UIGraphicsItem):
|
||||
#"""
|
||||
#Displays a rectangular bar with 10 divisions to indicate the relative scale of objects on the view.
|
||||
#"""
|
||||
#def __init__(self, size, width=5, color=(100, 100, 255)):
|
||||
#UIGraphicsItem.__init__(self)
|
||||
#self.setAcceptedMouseButtons(QtCore.Qt.NoButton)
|
||||
|
||||
#self.brush = fn.mkBrush(color)
|
||||
#self.pen = fn.mkPen((0,0,0))
|
||||
#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
|
||||
#def setSize(self, s):
|
||||
#self.size = s
|
||||
|
||||
|
@ -740,6 +740,7 @@ class ScatterPlotItem(GraphicsObject):
|
||||
drawSymbol(p2, *self.getSpotOpts(rec, scale))
|
||||
p2.end()
|
||||
|
||||
p.setRenderHint(p.Antialiasing, aa)
|
||||
self.picture.play(p)
|
||||
|
||||
def points(self):
|
||||
|
@ -524,12 +524,13 @@ class ViewBox(GraphicsWidget):
|
||||
if t is not None:
|
||||
t = Point(t)
|
||||
self.setRange(vr.translated(t), padding=0)
|
||||
elif x is not None:
|
||||
x1, x2 = vr.left()+x, vr.right()+x
|
||||
self.setXRange(x1, x2, padding=0)
|
||||
elif y is not None:
|
||||
y1, y2 = vr.top()+y, vr.bottom()+y
|
||||
self.setYRange(y1, y2, padding=0)
|
||||
else:
|
||||
if x is not None:
|
||||
x1, x2 = vr.left()+x, vr.right()+x
|
||||
self.setXRange(x1, x2, padding=0)
|
||||
if y is not None:
|
||||
y1, y2 = vr.top()+y, vr.bottom()+y
|
||||
self.setYRange(y1, y2, padding=0)
|
||||
|
||||
|
||||
|
||||
@ -1090,10 +1091,10 @@ class ViewBox(GraphicsWidget):
|
||||
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[0] is None and xr[1] is None) or np.isnan(xr).any() or np.isinf(xr).any():
|
||||
if xr is None or xr == (None, None) or np.isnan(xr).any() or np.isinf(xr).any():
|
||||
useX = False
|
||||
xr = (0,0)
|
||||
if yr is None or (yr[0] is None and yr[1] is None) or np.isnan(yr).any() or np.isinf(yr).any():
|
||||
if yr is None or yr == (None, None) or np.isnan(yr).any() or np.isinf(yr).any():
|
||||
useY = False
|
||||
yr = (0,0)
|
||||
|
||||
|
@ -72,7 +72,8 @@ class ColorMapParameter(ptree.types.GroupParameter):
|
||||
(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'.
|
||||
color when mode=='enum'. Optionally may specify a dict
|
||||
instead {value: name}.
|
||||
============== ============================================================
|
||||
"""
|
||||
self.fields = OrderedDict(fields)
|
||||
@ -168,12 +169,14 @@ class EnumColorMapItem(ptree.types.GroupParameter):
|
||||
def __init__(self, name, opts):
|
||||
self.fieldName = name
|
||||
vals = opts.get('values', [])
|
||||
if isinstance(vals, list):
|
||||
vals = OrderedDict([(v,str(v)) for v in vals])
|
||||
childs = [{'name': v, 'type': 'color'} for v in vals]
|
||||
|
||||
childs = []
|
||||
for v in vals:
|
||||
ch = ptree.Parameter.create(name=str(v), type='color')
|
||||
ch.maskValue = v
|
||||
for val,vname in vals.items():
|
||||
ch = ptree.Parameter.create(name=vname, type='color')
|
||||
ch.maskValue = val
|
||||
childs.append(ch)
|
||||
|
||||
ptree.types.GroupParameter.__init__(self,
|
||||
|
@ -2,6 +2,7 @@ from pyqtgraph.Qt import QtGui, QtCore
|
||||
import pyqtgraph.parametertree as ptree
|
||||
import numpy as np
|
||||
from pyqtgraph.pgcollections import OrderedDict
|
||||
import pyqtgraph as pg
|
||||
|
||||
__all__ = ['DataFilterWidget']
|
||||
|
||||
@ -22,6 +23,7 @@ class DataFilterWidget(ptree.ParameterTree):
|
||||
|
||||
self.setFields = self.params.setFields
|
||||
self.filterData = self.params.filterData
|
||||
self.describe = self.params.describe
|
||||
|
||||
def filterChanged(self):
|
||||
self.sigFilterChanged.emit(self)
|
||||
@ -70,18 +72,28 @@ class DataFilterParameter(ptree.types.GroupParameter):
|
||||
for fp in self:
|
||||
if fp.value() is False:
|
||||
continue
|
||||
mask &= fp.generateMask(data)
|
||||
mask &= fp.generateMask(data, mask.copy())
|
||||
#key, mn, mx = fp.fieldName, fp['Min'], fp['Max']
|
||||
|
||||
#vals = data[key]
|
||||
#mask &= (vals >= mn)
|
||||
#mask &= (vals < mx) ## Use inclusive minimum and non-inclusive maximum. This makes it easier to create non-overlapping selections
|
||||
return mask
|
||||
|
||||
def describe(self):
|
||||
"""Return a list of strings describing the currently enabled filters."""
|
||||
desc = []
|
||||
for fp in self:
|
||||
if fp.value() is False:
|
||||
continue
|
||||
desc.append(fp.describe())
|
||||
return desc
|
||||
|
||||
class RangeFilterItem(ptree.types.SimpleParameter):
|
||||
def __init__(self, name, opts):
|
||||
self.fieldName = name
|
||||
units = opts.get('units', '')
|
||||
self.units = units
|
||||
ptree.types.SimpleParameter.__init__(self,
|
||||
name=name, autoIncrementName=True, type='bool', value=True, removable=True, renamable=True,
|
||||
children=[
|
||||
@ -90,19 +102,24 @@ class RangeFilterItem(ptree.types.SimpleParameter):
|
||||
dict(name='Max', type='float', value=1.0, suffix=units, siPrefix=True),
|
||||
])
|
||||
|
||||
def generateMask(self, data):
|
||||
vals = data[self.fieldName]
|
||||
return (vals >= self['Min']) & (vals < self['Max']) ## Use inclusive minimum and non-inclusive maximum. This makes it easier to create non-overlapping selections
|
||||
def generateMask(self, data, mask):
|
||||
vals = data[self.fieldName][mask]
|
||||
mask[mask] = (vals >= self['Min']) & (vals < self['Max']) ## Use inclusive minimum and non-inclusive maximum. This makes it easier to create non-overlapping selections
|
||||
return mask
|
||||
|
||||
def describe(self):
|
||||
return "%s < %s < %s" % (pg.siFormat(self['Min'], suffix=self.units), self.fieldName, pg.siFormat(self['Max'], suffix=self.units))
|
||||
|
||||
class EnumFilterItem(ptree.types.SimpleParameter):
|
||||
def __init__(self, name, opts):
|
||||
self.fieldName = name
|
||||
vals = opts.get('values', [])
|
||||
childs = []
|
||||
for v in vals:
|
||||
ch = ptree.Parameter.create(name=str(v), type='bool', value=True)
|
||||
ch.maskValue = v
|
||||
if isinstance(vals, list):
|
||||
vals = OrderedDict([(v,str(v)) for v in vals])
|
||||
for val,vname in vals.items():
|
||||
ch = ptree.Parameter.create(name=vname, type='bool', value=True)
|
||||
ch.maskValue = val
|
||||
childs.append(ch)
|
||||
ch = ptree.Parameter.create(name='(other)', type='bool', value=True)
|
||||
ch.maskValue = '__other__'
|
||||
@ -112,10 +129,10 @@ class EnumFilterItem(ptree.types.SimpleParameter):
|
||||
name=name, autoIncrementName=True, type='bool', value=True, removable=True, renamable=True,
|
||||
children=childs)
|
||||
|
||||
def generateMask(self, data):
|
||||
vals = data[self.fieldName]
|
||||
mask = np.ones(len(data), dtype=bool)
|
||||
otherMask = np.ones(len(data), dtype=bool)
|
||||
def generateMask(self, data, startMask):
|
||||
vals = data[self.fieldName][startMask]
|
||||
mask = np.ones(len(vals), dtype=bool)
|
||||
otherMask = np.ones(len(vals), dtype=bool)
|
||||
for c in self:
|
||||
key = c.maskValue
|
||||
if key == '__other__':
|
||||
@ -125,4 +142,9 @@ class EnumFilterItem(ptree.types.SimpleParameter):
|
||||
otherMask &= m
|
||||
if c.value() is False:
|
||||
mask &= m
|
||||
return mask
|
||||
startMask[startMask] = mask
|
||||
return startMask
|
||||
|
||||
def describe(self):
|
||||
vals = [ch.name() for ch in self if ch.value() is True]
|
||||
return "%s: %s" % (self.fieldName, ', '.join(vals))
|
@ -6,6 +6,7 @@ import pyqtgraph.parametertree as ptree
|
||||
import pyqtgraph.functions as fn
|
||||
import numpy as np
|
||||
from pyqtgraph.pgcollections import OrderedDict
|
||||
import pyqtgraph as pg
|
||||
|
||||
__all__ = ['ScatterPlotWidget']
|
||||
|
||||
@ -47,6 +48,12 @@ class ScatterPlotWidget(QtGui.QSplitter):
|
||||
self.ctrlPanel.addWidget(self.ptree)
|
||||
self.addWidget(self.plot)
|
||||
|
||||
bg = pg.mkColor(pg.getConfigOption('background'))
|
||||
bg.setAlpha(150)
|
||||
self.filterText = pg.TextItem(border=pg.getConfigOption('foreground'), color=bg)
|
||||
self.filterText.setPos(60,20)
|
||||
self.filterText.setParentItem(self.plot.plotItem)
|
||||
|
||||
self.data = None
|
||||
self.mouseOverField = None
|
||||
self.scatterPlot = None
|
||||
@ -97,6 +104,13 @@ class ScatterPlotWidget(QtGui.QSplitter):
|
||||
def filterChanged(self, f):
|
||||
self.filtered = None
|
||||
self.updatePlot()
|
||||
desc = self.filter.describe()
|
||||
if len(desc) == 0:
|
||||
self.filterText.setVisible(False)
|
||||
else:
|
||||
self.filterText.setText('\n'.join(desc))
|
||||
self.filterText.setVisible(True)
|
||||
|
||||
|
||||
def updatePlot(self):
|
||||
self.plot.clear()
|
||||
@ -125,69 +139,69 @@ class ScatterPlotWidget(QtGui.QSplitter):
|
||||
self.plot.setLabels(left=('N', ''), bottom=(sel[0], units[0]), title='')
|
||||
if len(data) == 0:
|
||||
return
|
||||
x = data[sel[0]]
|
||||
#if x.dtype.kind == 'f':
|
||||
#mask = ~np.isnan(x)
|
||||
#else:
|
||||
#mask = np.ones(len(x), dtype=bool)
|
||||
#x = x[mask]
|
||||
#style['symbolBrush'] = colors[mask]
|
||||
y = None
|
||||
#x = data[sel[0]]
|
||||
#y = None
|
||||
xy = [data[sel[0]], None]
|
||||
elif len(sel) == 2:
|
||||
self.plot.setLabels(left=(sel[1],units[1]), bottom=(sel[0],units[0]))
|
||||
if len(data) == 0:
|
||||
return
|
||||
|
||||
xydata = []
|
||||
for ax in [0,1]:
|
||||
d = data[sel[ax]]
|
||||
## scatter catecorical values just a bit so they show up better in the scatter plot.
|
||||
#if sel[ax] in ['MorphologyBSMean', 'MorphologyTDMean', 'FIType']:
|
||||
#d += np.random.normal(size=len(cells), scale=0.1)
|
||||
xydata.append(d)
|
||||
x,y = xydata
|
||||
#mask = np.ones(len(x), dtype=bool)
|
||||
#if x.dtype.kind == 'f':
|
||||
#mask |= ~np.isnan(x)
|
||||
#if y.dtype.kind == 'f':
|
||||
#mask |= ~np.isnan(y)
|
||||
#x = x[mask]
|
||||
#y = y[mask]
|
||||
#style['symbolBrush'] = colors[mask]
|
||||
xy = [data[sel[0]], data[sel[1]]]
|
||||
#xydata = []
|
||||
#for ax in [0,1]:
|
||||
#d = data[sel[ax]]
|
||||
### scatter catecorical values just a bit so they show up better in the scatter plot.
|
||||
##if sel[ax] in ['MorphologyBSMean', 'MorphologyTDMean', 'FIType']:
|
||||
##d += np.random.normal(size=len(cells), scale=0.1)
|
||||
|
||||
#xydata.append(d)
|
||||
#x,y = xydata
|
||||
|
||||
## convert enum-type fields to float, set axis labels
|
||||
xy = [x,y]
|
||||
enum = [False, False]
|
||||
for i in [0,1]:
|
||||
axis = self.plot.getAxis(['bottom', 'left'][i])
|
||||
if xy[i] is not None and xy[i].dtype.kind in ('S', 'O'):
|
||||
if xy[i] is not None and (self.fields[sel[i]].get('mode', None) == 'enum' or xy[i].dtype.kind in ('S', 'O')):
|
||||
vals = self.fields[sel[i]].get('values', list(set(xy[i])))
|
||||
xy[i] = np.array([vals.index(x) if x in vals else len(vals) for x in xy[i]], dtype=float)
|
||||
axis.setTicks([list(enumerate(vals))])
|
||||
enum[i] = True
|
||||
else:
|
||||
axis.setTicks(None) # reset to automatic ticking
|
||||
x,y = xy
|
||||
|
||||
## mask out any nan values
|
||||
mask = np.ones(len(x), dtype=bool)
|
||||
if x.dtype.kind == 'f':
|
||||
mask &= ~np.isnan(x)
|
||||
if y is not None and y.dtype.kind == 'f':
|
||||
mask &= ~np.isnan(y)
|
||||
x = x[mask]
|
||||
mask = np.ones(len(xy[0]), dtype=bool)
|
||||
if xy[0].dtype.kind == 'f':
|
||||
mask &= ~np.isnan(xy[0])
|
||||
if xy[1] is not None and xy[1].dtype.kind == 'f':
|
||||
mask &= ~np.isnan(xy[1])
|
||||
|
||||
xy[0] = xy[0][mask]
|
||||
style['symbolBrush'] = colors[mask]
|
||||
|
||||
## Scatter y-values for a histogram-like appearance
|
||||
if y is None:
|
||||
y = fn.pseudoScatter(x)
|
||||
if xy[1] is None:
|
||||
## column scatter plot
|
||||
xy[1] = fn.pseudoScatter(xy[0])
|
||||
else:
|
||||
y = y[mask]
|
||||
|
||||
## beeswarm plots
|
||||
xy[1] = xy[1][mask]
|
||||
for ax in [0,1]:
|
||||
if not enum[ax]:
|
||||
continue
|
||||
for i in range(int(xy[ax].max())+1):
|
||||
keymask = xy[ax] == i
|
||||
scatter = pg.pseudoScatter(xy[1-ax][keymask], bidir=True)
|
||||
scatter *= 0.2 / np.abs(scatter).max()
|
||||
xy[ax][keymask] += scatter
|
||||
|
||||
if self.scatterPlot is not None:
|
||||
try:
|
||||
self.scatterPlot.sigPointsClicked.disconnect(self.plotClicked)
|
||||
except:
|
||||
pass
|
||||
self.scatterPlot = self.plot.plot(x, y, data=data[mask], **style)
|
||||
self.scatterPlot = self.plot.plot(xy[0], xy[1], data=data[mask], **style)
|
||||
self.scatterPlot.sigPointsClicked.connect(self.plotClicked)
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user