Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

lp:~luke-campagnola/pyqtgraph/dev/revision/105

  • Loading branch information...
commit 6e781030a3a315d0719cfe3d4c6f3cd0a90e3c7c 1 parent 190528c
@ibressler authored
View
11 documentation/source/graphicsItems/gradienteditoritem.rst
@@ -5,4 +5,15 @@ GradientEditorItem
:members:
.. automethod:: pyqtgraph.GradientEditorItem.__init__
+
+
+TickSliderItem
+==================
+
+.. autoclass:: pyqtgraph.TickSliderItem
+ :members:
+
+ .. automethod:: pyqtgraph.TickSliderItem.__init__
+
+
View
1  documentation/source/graphicsItems/index.rst
@@ -19,6 +19,7 @@ Contents:
graphicslayout
plotcurveitem
scatterplotitem
+ isocurveitem
axisitem
arrowitem
curvepoint
View
1  exporters/ImageExporter.py
@@ -59,6 +59,7 @@ def export(self, fileName=None):
painter = QtGui.QPainter(self.png)
try:
self.setExportMode(True, {'antialias': self.params['antialias'], 'background': self.params['background']})
+ painter.setRenderHint(QtGui.QPainter.Antialiasing, self.params['antialias'])
self.getScene().render(painter, QtCore.QRectF(targetRect), sourceRect)
finally:
self.setExportMode(False)
View
33 flowchart/Flowchart.py
@@ -84,14 +84,18 @@ def __init__(self, terminals=None, name=None, filePath=None):
self.widget()
- self.inputNode = Node('Input', allowRemove=False)
- self.outputNode = Node('Output', allowRemove=False)
+ self.inputNode = Node('Input', allowRemove=False, allowAddOutput=True)
+ self.outputNode = Node('Output', allowRemove=False, allowAddInput=True)
self.addNode(self.inputNode, 'Input', [-150, 0])
self.addNode(self.outputNode, 'Output', [300, 0])
self.outputNode.sigOutputChanged.connect(self.outputChanged)
self.outputNode.sigTerminalRenamed.connect(self.internalTerminalRenamed)
self.inputNode.sigTerminalRenamed.connect(self.internalTerminalRenamed)
+ self.outputNode.sigTerminalRemoved.connect(self.internalTerminalRemoved)
+ self.inputNode.sigTerminalRemoved.connect(self.internalTerminalRemoved)
+ self.outputNode.sigTerminalAdded.connect(self.internalTerminalAdded)
+ self.inputNode.sigTerminalAdded.connect(self.internalTerminalAdded)
self.viewBox.autoRange(padding = 0.04)
@@ -121,11 +125,20 @@ def addTerminal(self, name, **opts):
if opts['io'] == 'in': ## inputs to the flowchart become outputs on the input node
opts['io'] = 'out'
opts['multi'] = False
- term2 = self.inputNode.addTerminal(name, **opts)
+ self.inputNode.sigTerminalAdded.disconnect(self.internalTerminalAdded)
+ try:
+ term2 = self.inputNode.addTerminal(name, **opts)
+ finally:
+ self.inputNode.sigTerminalAdded.connect(self.internalTerminalAdded)
+
else:
opts['io'] = 'in'
#opts['multi'] = False
- term2 = self.outputNode.addTerminal(name, **opts)
+ self.outputNode.sigTerminalAdded.disconnect(self.internalTerminalAdded)
+ try:
+ term2 = self.outputNode.addTerminal(name, **opts)
+ finally:
+ self.outputNode.sigTerminalAdded.connect(self.internalTerminalAdded)
return term
def removeTerminal(self, name):
@@ -138,6 +151,16 @@ def removeTerminal(self, name):
def internalTerminalRenamed(self, term, oldName):
self[oldName].rename(term.name())
+ def internalTerminalAdded(self, node, term):
+ if term._io == 'in':
+ io = 'out'
+ else:
+ io = 'in'
+ Node.addTerminal(self, term.name(), io=io, renamable=term.isRenamable(), removable=term.isRemovable(), multiable=term.isMultiable())
+
+ def internalTerminalRemoved(self, node, term):
+ Node.removeTerminal(self, term.name())
+
def terminalRenamed(self, term, oldName):
newName = term.name()
#print "flowchart rename", newName, oldName
@@ -475,7 +498,7 @@ def restoreState(self, state, clear=False):
self.inputNode.restoreState(state.get('inputNode', {}))
self.outputNode.restoreState(state.get('outputNode', {}))
- #self.restoreTerminals(state['terminals'])
+ self.restoreTerminals(state['terminals'])
for n1, t1, n2, t2 in state['connects']:
try:
self.connectTerminals(self._nodes[n1][t1], self._nodes[n2][t2])
View
17 flowchart/Node.py
@@ -20,7 +20,9 @@ class Node(QtCore.QObject):
sigOutputChanged = QtCore.Signal(object) # self
sigClosed = QtCore.Signal(object)
sigRenamed = QtCore.Signal(object, object)
- sigTerminalRenamed = QtCore.Signal(object, object)
+ sigTerminalRenamed = QtCore.Signal(object, object) # term, oldName
+ sigTerminalAdded = QtCore.Signal(object, object) # self, term
+ sigTerminalRemoved = QtCore.Signal(object, object) # self, term
def __init__(self, name, terminals=None, allowAddInput=False, allowAddOutput=False, allowRemove=True):
@@ -77,6 +79,8 @@ def removeTerminal(self, term):
if name in self._outputs:
del self._outputs[name]
self.graphicsItem().updateTerminals()
+ self.sigTerminalRemoved.emit(self, term)
+
def terminalRenamed(self, term, oldName):
"""Called after a terminal has been renamed"""
@@ -107,6 +111,7 @@ def addTerminal(self, name, **opts):
elif term.isOutput():
self._outputs[name] = term
self.graphicsItem().updateTerminals()
+ self.sigTerminalAdded.emit(self, term)
return term
@@ -527,16 +532,22 @@ def raiseContextMenu(self, ev):
def buildMenu(self):
self.menu = QtGui.QMenu()
self.menu.setTitle("Node")
- a = self.menu.addAction("Add input", self.node.addInput)
+ a = self.menu.addAction("Add input", self.addInputFromMenu)
if not self.node._allowAddInput:
a.setEnabled(False)
- a = self.menu.addAction("Add output", self.node.addOutput)
+ a = self.menu.addAction("Add output", self.addOutputFromMenu)
if not self.node._allowAddOutput:
a.setEnabled(False)
a = self.menu.addAction("Remove node", self.node.close)
if not self.node._allowRemove:
a.setEnabled(False)
+ def addInputFromMenu(self): ## called when add input is clicked in context menu
+ self.node.addInput(renamable=True, removable=True, multiable=True)
+
+ def addOutputFromMenu(self): ## called when add output is clicked in context menu
+ self.node.addOutput(renamable=True, removable=True, multiable=False)
+
#def menuTriggered(self, action):
##print "node.menuTriggered called. action:", action
#act = str(action.text())
View
81 flowchart/Terminal.py
@@ -8,15 +8,23 @@
from eq import *
class Terminal:
- def __init__(self, node, name, io, optional=False, multi=False, pos=None, renamable=False, bypass=None):
- """Construct a new terminal. Optiona are:
- node - the node to which this terminal belongs
- name - string, the name of the terminal
- io - 'in' or 'out'
- optional - bool, whether the node may process without connection to this terminal
- multi - bool, for inputs: whether this terminal may make multiple connections
- for outputs: whether this terminal creates a different value for each connection
- pos - [x, y], the position of the terminal within its node's boundaries
+ def __init__(self, node, name, io, optional=False, multi=False, pos=None, renamable=False, removable=False, multiable=False, bypass=None):
+ """
+ Construct a new terminal.
+
+ ============== =================================================================================
+ **Arguments:**
+ node the node to which this terminal belongs
+ name string, the name of the terminal
+ io 'in' or 'out'
+ optional bool, whether the node may process without connection to this terminal
+ multi bool, for inputs: whether this terminal may make multiple connections
+ for outputs: whether this terminal creates a different value for each connection
+ pos [x, y], the position of the terminal within its node's boundaries
+ renamable (bool) Whether the terminal can be renamed by the user
+ removable (bool) Whether the terminal can be removed by the user
+ multiable (bool) Whether the user may toggle the *multi* option for this terminal
+ ============== =================================================================================
"""
self._io = io
#self._isOutput = opts[0] in ['out', 'io']
@@ -27,6 +35,8 @@ def __init__(self, node, name, io, optional=False, multi=False, pos=None, renama
self._node = weakref.ref(node)
self._name = name
self._renamable = renamable
+ self._removable = removable
+ self._multiable = multiable
self._connections = {}
self._graphicsItem = TerminalGraphicsItem(self, parent=self._node().graphicsItem())
self._bypass = bypass
@@ -121,6 +131,10 @@ def isInput(self):
def isMultiValue(self):
return self._multi
+
+ def setMultiValue(self, b):
+ """Set whether this is a multi-value terminal."""
+ self._multi = b
def isOutput(self):
return self._io == 'out'
@@ -128,6 +142,12 @@ def isOutput(self):
def isRenamable(self):
return self._renamable
+ def isRemovable(self):
+ return self._removable
+
+ def isMultiable(self):
+ return self._multiable
+
def name(self):
return self._name
@@ -278,7 +298,7 @@ def close(self):
item.scene().removeItem(item)
def saveState(self):
- return {'io': self._io, 'multi': self._multi, 'optional': self._optional}
+ return {'io': self._io, 'multi': self._multi, 'optional': self._optional, 'renamable': self._renamable, 'removable': self._removable, 'multiable': self._multiable}
#class TerminalGraphicsItem(QtGui.QGraphicsItem):
@@ -357,40 +377,46 @@ def updateConnections(self):
def mousePressEvent(self, ev):
#ev.accept()
- ev.ignore()
+ ev.ignore() ## necessary to allow click/drag events to process correctly
def mouseClickEvent(self, ev):
if ev.button() == QtCore.Qt.LeftButton:
ev.accept()
self.label.setFocus(QtCore.Qt.MouseFocusReason)
- if ev.button() == QtCore.Qt.RightButton:
- if self.raiseContextMenu(ev):
- ev.accept()
+ elif ev.button() == QtCore.Qt.RightButton:
+ ev.accept()
+ self.raiseContextMenu(ev)
def raiseContextMenu(self, ev):
## only raise menu if this terminal is removable
menu = self.getMenu()
- if menu is None:
- return False
menu = self.scene().addParentContextMenus(self, menu, ev)
pos = ev.screenPos()
menu.popup(QtCore.QPoint(pos.x(), pos.y()))
- return True
def getMenu(self):
if self.menu is None:
- if self.removable():
- self.menu = QtGui.QMenu()
- self.menu.setTitle("Terminal")
- self.menu.addAction("Remove terminal", self.removeSelf)
- else:
- return None
+ self.menu = QtGui.QMenu()
+ self.menu.setTitle("Terminal")
+ remAct = QtGui.QAction("Remove terminal", self.menu)
+ remAct.triggered.connect(self.removeSelf)
+ self.menu.addAction(remAct)
+ self.menu.remAct = remAct
+ if not self.term.isRemovable():
+ remAct.setEnabled(False)
+ multiAct = QtGui.QAction("Multi-value", self.menu)
+ multiAct.setCheckable(True)
+ multiAct.setChecked(self.term.isMultiValue())
+ multiAct.triggered.connect(self.toggleMulti)
+ self.menu.addAction(multiAct)
+ self.menu.multiAct = multiAct
+ if self.term.isMultiable():
+ multiAct.setEnabled = False
return self.menu
- def removable(self):
- return (
- (self.term.isOutput() and self.term.node()._allowAddOutput) or
- (self.term.isInput() and self.term.node()._allowAddInput))
+ def toggleMulti(self):
+ multi = self.menu.multiAct.isChecked()
+ self.term.setMultiValue(multi)
## probably never need this
#def getContextMenus(self, ev):
@@ -441,6 +467,7 @@ def mouseDragEvent(self, ev):
def hoverEvent(self, ev):
if not ev.isExit() and ev.acceptDrags(QtCore.Qt.LeftButton):
ev.acceptClicks(QtCore.Qt.LeftButton) ## we don't use the click, but we also don't want anyone else to use it.
+ ev.acceptClicks(QtCore.Qt.RightButton)
self.box.setBrush(fn.mkBrush('w'))
else:
self.box.setBrush(self.brush)
View
20 flowchart/library/Data.py
@@ -195,31 +195,31 @@ def __init__(self, name):
self.ui = QtGui.QWidget()
self.layout = QtGui.QGridLayout()
- self.addInBtn = QtGui.QPushButton('+Input')
- self.addOutBtn = QtGui.QPushButton('+Output')
+ #self.addInBtn = QtGui.QPushButton('+Input')
+ #self.addOutBtn = QtGui.QPushButton('+Output')
self.text = QtGui.QTextEdit()
self.text.setTabStopWidth(30)
self.text.setPlainText("# Access inputs as args['input_name']\nreturn {'output': None} ## one key per output terminal")
- self.layout.addWidget(self.addInBtn, 0, 0)
- self.layout.addWidget(self.addOutBtn, 0, 1)
+ #self.layout.addWidget(self.addInBtn, 0, 0)
+ #self.layout.addWidget(self.addOutBtn, 0, 1)
self.layout.addWidget(self.text, 1, 0, 1, 2)
self.ui.setLayout(self.layout)
#QtCore.QObject.connect(self.addInBtn, QtCore.SIGNAL('clicked()'), self.addInput)
- self.addInBtn.clicked.connect(self.addInput)
+ #self.addInBtn.clicked.connect(self.addInput)
#QtCore.QObject.connect(self.addOutBtn, QtCore.SIGNAL('clicked()'), self.addOutput)
- self.addOutBtn.clicked.connect(self.addOutput)
+ #self.addOutBtn.clicked.connect(self.addOutput)
self.text.focusOutEvent = self.focusOutEvent
self.lastText = None
def ctrlWidget(self):
return self.ui
- def addInput(self):
- Node.addInput(self, 'input', renamable=True)
+ #def addInput(self):
+ #Node.addInput(self, 'input', renamable=True)
- def addOutput(self):
- Node.addOutput(self, 'output', renamable=True)
+ #def addOutput(self):
+ #Node.addOutput(self, 'output', renamable=True)
def focusOutEvent(self, ev):
text = str(self.text.toPlainText())
View
32 flowchart/library/Display.py
@@ -5,6 +5,7 @@
from pyqtgraph.Qt import QtCore, QtGui
from pyqtgraph.graphicsItems.ScatterPlotItem import ScatterPlotItem
from pyqtgraph.graphicsItems.PlotCurveItem import PlotCurveItem
+from pyqtgraph import PlotDataItem
from common import *
import numpy as np
@@ -44,7 +45,7 @@ def process(self, In, display=True):
for val in vals:
vid = id(val)
- if vid in self.items:
+ if vid in self.items and self.items[vid].scene() is self.plot.scene():
items.add(vid)
else:
#if isinstance(val, PlotCurveItem):
@@ -66,6 +67,11 @@ def process(self, In, display=True):
self.plot.removeItem(self.items[vid])
del self.items[vid]
+ def processBypassed(self, args):
+ for item in self.items.values():
+ self.plot.removeItem(item)
+ self.items = {}
+
#def setInput(self, **args):
#for k in args:
#self.plot.plot(args[k])
@@ -117,6 +123,30 @@ def process(self, In, display=True):
del self.items[vid]
+class PlotCurve(CtrlNode):
+ """Generates a plot curve from x/y data"""
+ nodeName = 'PlotCurve'
+ uiTemplate = [
+ ('color', 'color'),
+ ]
+
+ def __init__(self, name):
+ CtrlNode.__init__(self, name, terminals={
+ 'x': {'io': 'in'},
+ 'y': {'io': 'in'},
+ 'plot': {'io': 'out'}
+ })
+ self.item = PlotDataItem()
+
+ def process(self, x, y, display=True):
+ #print "scatterplot process"
+ if not display:
+ return {'plot': None}
+
+ self.item.setData(x, y, pen=self.ctrls['color'].color())
+ return {'plot': self.item}
+
+
class ScatterPlot(CtrlNode):
View
235 graphicsItems/GradientEditorItem.py
@@ -21,8 +21,23 @@
class TickSliderItem(GraphicsWidget):
+ ## public class
+ """**Bases:** :class:`GraphicsWidget <pyqtgraph.GraphicsWidget>`
+
+ A rectangular item with tick marks along its length that can (optionally) be moved by the user."""
def __init__(self, orientation='bottom', allowAdd=True, **kargs):
+ """
+ ============= =================================================================================
+ **Arguments**
+ orientation Set the orientation of the gradient. Options are: 'left', 'right'
+ 'top', and 'bottom'.
+ allowAdd Specifies whether ticks can be added to the item by the user.
+ tickPen Default is white. Specifies the color of the outline of the ticks.
+ Can be any of the valid arguments for :func:`mkPen <pyqtgraph.mkPen>`
+ ============= =================================================================================
+ """
+ ## public
GraphicsWidget.__init__(self)
self.orientation = orientation
self.length = 100
@@ -77,10 +92,21 @@ def setMaxDim(self, mx=None):
self.setMaximumHeight(16777215)
- def setOrientation(self, ort):
- self.orientation = ort
+ def setOrientation(self, orientation):
+ ## public
+ """Set the orientation of the TickSliderItem.
+
+ ============= ===================================================================
+ **Arguments**
+ orientation Options are: 'left', 'right', 'top', 'bottom'
+ The orientation option specifies which side of the slider the
+ ticks are on, as well as whether the slider is vertical ('right'
+ and 'left') or horizontal ('top' and 'bottom').
+ ============= ===================================================================
+ """
self.setMaxDim()
self.resetTransform()
+ ort = orientation
if ort == 'top':
self.scale(1, -1)
self.translate(0, -self.height())
@@ -92,10 +118,25 @@ def setOrientation(self, ort):
self.rotate(270)
self.translate(-self.height(), 0)
#self.setPos(0, -self.height())
+ elif ort != 'bottom':
+ raise Exception("%s is not a valid orientation. Options are 'left', 'right', 'top', and 'bottom'" %str(ort))
self.translate(self.tickSize/2., 0)
def addTick(self, x, color=None, movable=True):
+ ## public
+ """
+ Add a tick to the item.
+
+ ============= ==================================================================
+ **Arguments**
+ x Position where tick should be added.
+ color Color of added tick. If color is not specified, the color will be
+ white.
+ movable Specifies whether the tick is movable with the mouse.
+ ============= ==================================================================
+ """
+
if color is None:
color = QtGui.QColor(255,255,255)
tick = Tick(self, [x*self.length, 0], color, movable, self.tickSize, pen=self.tickPen)
@@ -104,6 +145,10 @@ def addTick(self, x, color=None, movable=True):
return tick
def removeTick(self, tick):
+ ## public
+ """
+ Removes the specified tick.
+ """
del self.ticks[tick]
tick.setParentItem(None)
if self.scene() is not None:
@@ -138,6 +183,7 @@ def resizeEvent(self, ev):
#self.fitInView(bounds, QtCore.Qt.KeepAspectRatio)
def setLength(self, newLen):
+ #private
for t, x in self.ticks.items():
t.setPos(x * newLen, t.pos().y())
self.length = float(newLen)
@@ -206,12 +252,36 @@ def showMenu(self, ev):
pass
def setTickColor(self, tick, color):
+ """Set the color of the specified tick.
+
+ ============= ==================================================================
+ **Arguments**
+ tick Can be either an integer corresponding to the index of the tick
+ or a Tick object. Ex: if you had a slider with 3 ticks and you
+ wanted to change the middle tick, the index would be 1.
+ color The color to make the tick. Can be any argument that is valid for
+ :func:`mkBrush <pyqtgraph.mkBrush>`
+ ============= ==================================================================
+ """
tick = self.getTick(tick)
tick.color = color
tick.update()
#tick.setBrush(QtGui.QBrush(QtGui.QColor(tick.color)))
def setTickValue(self, tick, val):
+ ## public
+ """
+ Set the position (along the slider) of the tick.
+
+ ============= ==================================================================
+ **Arguments**
+ tick Can be either an integer corresponding to the index of the tick
+ or a Tick object. Ex: if you had a slider with 3 ticks and you
+ wanted to change the middle tick, the index would be 1.
+ val The desired position of the tick. If val is < 0, position will be
+ set to 0. If val is > 1, position will be set to 1.
+ ============= ==================================================================
+ """
tick = self.getTick(tick)
val = min(max(0.0, val), 1.0)
x = val * self.length
@@ -221,10 +291,29 @@ def setTickValue(self, tick, val):
self.ticks[tick] = val
def tickValue(self, tick):
+ ## public
+ """Return the value (from 0.0 to 1.0) of the specified tick.
+
+ ============= ==================================================================
+ **Arguments**
+ tick Can be either an integer corresponding to the index of the tick
+ or a Tick object. Ex: if you had a slider with 3 ticks and you
+ wanted the value of the middle tick, the index would be 1.
+ ============= ==================================================================
+ """
tick = self.getTick(tick)
return self.ticks[tick]
def getTick(self, tick):
+ ## public
+ """Return the Tick object at the specified index.
+
+ ============= ==================================================================
+ **Arguments**
+ tick An integer corresponding to the index of the desired tick. If the
+ argument is not an integer it will be returned unchanged.
+ ============= ==================================================================
+ """
if type(tick) is int:
tick = self.listTicks()[tick][0]
return tick
@@ -233,17 +322,46 @@ def getTick(self, tick):
#QtGui.QGraphicsView.mouseMoveEvent(self, ev)
def listTicks(self):
+ """Return a sorted list of all the Tick objects on the slider."""
+ ## public
ticks = self.ticks.items()
ticks.sort(lambda a,b: cmp(a[1], b[1]))
return ticks
class GradientEditorItem(TickSliderItem):
+ """
+ **Bases:** :class:`TickSliderItem <pyqtgraph.TickSliderItem>`
+
+ An item that can be used to define a color gradient. Implements common pre-defined gradients that are
+ customizable by the user. :class: `GradientWidget <pyqtgraph.widgets.GradientWidget>` provides a widget
+ with a GradientEditorItem that can be added to a GUI.
+
+ ======================== ===========================================================
+ **Signals**
+ sigGradientChanged(self) Signal is emitted anytime the gradient changes. The signal
+ is emitted in real time while ticks are being dragged or
+ colors are being changed.
+ ======================== ===========================================================
+
+ """
sigGradientChanged = QtCore.Signal(object)
def __init__(self, *args, **kargs):
-
+ """
+ Create a new GradientEditorItem.
+ All arguments are passed to :func:`TickSliderItem.__init__ <pyqtgraph.TickSliderItem.__init__>`
+
+ ============= =================================================================================
+ **Arguments**
+ orientation Set the orientation of the gradient. Options are: 'left', 'right'
+ 'top', and 'bottom'.
+ allowAdd Default is True. Specifies whether ticks can be added to the item.
+ tickPen Default is white. Specifies the color of the outline of the ticks.
+ Can be any of the valid arguments for :func:`mkPen <pyqtgraph.mkPen>'
+ ============= =================================================================================
+ """
self.currentTick = None
self.currentTickColor = None
self.rectSize = 15
@@ -308,22 +426,47 @@ def __init__(self, *args, **kargs):
self.setColorMode('rgb')
self.updateGradient()
- def setOrientation(self, ort):
- TickSliderItem.setOrientation(self, ort)
+ def setOrientation(self, orientation):
+ ## public
+ """
+ Set the orientation of the GradientEditorItem.
+
+ ============= ===================================================================
+ **Arguments**
+ orientation Options are: 'left', 'right', 'top', 'bottom'
+ The orientation option specifies which side of the gradient the
+ ticks are on, as well as whether the gradient is vertical ('right'
+ and 'left') or horizontal ('top' and 'bottom').
+ ============= ===================================================================
+ """
+ TickSliderItem.setOrientation(self, orientation)
self.translate(0, self.rectSize)
def showMenu(self, ev):
+ #private
self.menu.popup(ev.screenPos().toQPoint())
def contextMenuClicked(self, b=None):
- global Gradients
+ #private
+ #global Gradients
act = self.sender()
self.loadPreset(act.name)
def loadPreset(self, name):
+ """
+ Load a predefined gradient.
+
+ """ ## TODO: provide image with names of defined gradients
+ #global Gradients
self.restoreState(Gradients[name])
def setColorMode(self, cm):
+ """
+ Set the color mode for the gradient. Options are: 'hsv', 'rgb'
+
+ """
+
+ ## public
if cm not in ['rgb', 'hsv']:
raise Exception("Unknown color mode %s. Options are 'rgb' and 'hsv'." % str(cm))
@@ -339,26 +482,31 @@ def setColorMode(self, cm):
self.updateGradient()
def updateGradient(self):
+ #private
self.gradient = self.getGradient()
self.gradRect.setBrush(QtGui.QBrush(self.gradient))
self.sigGradientChanged.emit(self)
def setLength(self, newLen):
+ #private (but maybe public)
TickSliderItem.setLength(self, newLen)
self.backgroundRect.setRect(0, -self.rectSize, newLen, self.rectSize)
self.gradRect.setRect(0, -self.rectSize, newLen, self.rectSize)
self.updateGradient()
def currentColorChanged(self, color):
+ #private
if color.isValid() and self.currentTick is not None:
self.setTickColor(self.currentTick, color)
self.updateGradient()
def currentColorRejected(self):
+ #private
self.setTickColor(self.currentTick, self.currentTickColor)
self.updateGradient()
def tickClicked(self, tick, ev):
+ #private
if ev.button() == QtCore.Qt.LeftButton:
if not tick.colorChangeAllowed:
return
@@ -378,11 +526,13 @@ def tickClicked(self, tick, ev):
self.updateGradient()
def tickMoved(self, tick, pos):
+ #private
TickSliderItem.tickMoved(self, tick, pos)
self.updateGradient()
def getGradient(self):
+ """Return a QLinearGradient object."""
g = QtGui.QLinearGradient(QtCore.QPointF(0,0), QtCore.QPointF(self.length,0))
if self.colorMode == 'rgb':
ticks = self.listTicks()
@@ -403,6 +553,15 @@ def getGradient(self):
return g
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
@@ -453,8 +612,19 @@ def getColor(self, x, toQColor=True):
else:
return (c.red(), c.green(), c.blue(), c.alpha())
- def getLookupTable(self, nPts, alpha=True):
- """Return an RGB/A lookup table."""
+ def getLookupTable(self, nPts, alpha=None):
+ """
+ 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, alpha will be automatically determined.
+ ============= ============================================================================
+ """
+ if alpha is None:
+ alpha = self.usesAlpha()
if alpha:
table = np.empty((nPts,4), dtype=np.ubyte)
else:
@@ -466,9 +636,19 @@ def getLookupTable(self, nPts, alpha=True):
table[i] = color[:table.shape[1]]
return table
+
+ def usesAlpha(self):
+ """Return True if any ticks have an alpha < 255"""
+
+ ticks = self.listTicks()
+ for t in ticks:
+ if t[0].color.alpha() < 255:
+ return True
+
+ return False
def isLookupTrivial(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"""
ticks = self.listTicks()
if len(ticks) != 2:
return False
@@ -482,10 +662,24 @@ def isLookupTrivial(self):
def mouseReleaseEvent(self, ev):
+ #private
TickSliderItem.mouseReleaseEvent(self, ev)
self.updateGradient()
def addTick(self, x, color=None, movable=True):
+ """
+ Add a tick to the gradient. Return the tick.
+
+ ============= ==================================================================
+ **Arguments**
+ x Position where tick should be added.
+ color Color of added tick. If color is not specified, the color will be
+ the color of the gradient at the specified position.
+ movable Specifies whether the tick is movable with the mouse.
+ ============= ==================================================================
+ """
+
+
if color is None:
color = self.getColor(x)
t = TickSliderItem.addTick(self, x, color=color, movable=movable)
@@ -494,6 +688,13 @@ def addTick(self, x, color=None, movable=True):
return t
def saveState(self):
+ """
+ Return a dictionary with parameters for rebuilding the gradient. Keys will include:
+
+ - 'mode': hsv or rgb
+ - 'ticks': a list of tuples (pos, (r,g,b,a))
+ """
+ ## public
ticks = []
for t in self.ticks:
c = t.color
@@ -502,6 +703,21 @@ def saveState(self):
return state
def restoreState(self, state):
+ """
+ Restore the gradient specified in state.
+
+ ============= ====================================================================
+ **Arguments**
+ state A dictionary with same structure as those returned by
+ :func:`saveState <pyqtgraph.GradientEditorItem.saveState>`
+
+ Keys must include:
+
+ - 'mode': hsv or rgb
+ - 'ticks': a list of tuples (pos, (r,g,b,a))
+ ============= ====================================================================
+ """
+ ## public
self.setColorMode(state['mode'])
for t in self.ticks.keys():
self.removeTick(t)
@@ -512,6 +728,7 @@ def restoreState(self, state):
class Tick(GraphicsObject):
+ ## private class
sigMoving = QtCore.Signal(object)
sigMoved = QtCore.Signal(object)
View
2  graphicsItems/GridItem.py
@@ -6,6 +6,8 @@
__all__ = ['GridItem']
class GridItem(UIGraphicsItem):
"""
+ **Bases:** `UIGraphicsItem <pyqtgraph.UIGraphicsItem>'
+
Displays a rectangular grid of lines indicating major divisions within a coordinate system.
Automatically determines what divisions to use.
"""
View
2  graphicsItems/HistogramLUTItem.py
@@ -160,7 +160,7 @@ def gradientChanged(self):
#self.imageItem.setLookupTable(self.gradient.getLookupTable(512))
self.sigLookupTableChanged.emit(self)
- def getLookupTable(self, img=None, n=None, alpha=False):
+ def getLookupTable(self, img=None, n=None, alpha=None):
if n is None:
if img.dtype == np.uint8:
n = 256
View
2  graphicsItems/ImageItem.py
@@ -258,6 +258,8 @@ def render(self):
lut = self.lut(self.image)
else:
lut = self.lut
+ #print lut.shape
+ #print self.lut
argb, alpha = fn.makeARGB(self.image, lut=lut, levels=self.levels)
self.qimage = fn.makeQImage(argb, alpha)
View
36 graphicsItems/InfiniteLine.py
@@ -9,8 +9,17 @@
__all__ = ['InfiniteLine']
class InfiniteLine(UIGraphicsItem):
"""
+ **Bases:** :class:`UIGraphicsItem <pyqtgraph.UIGraphicsItem>`
+
Displays a line of infinite length.
This line may be dragged to indicate a position in data coordinates.
+
+ =============================== ===================================================
+ **Signals**
+ sigDragged(self)
+ sigPositionChangeFinished(self)
+ sigPositionChanged(self)
+ =============================== ===================================================
"""
sigDragged = QtCore.Signal(object)
@@ -19,12 +28,18 @@ class InfiniteLine(UIGraphicsItem):
def __init__(self, pos=None, angle=90, pen=None, movable=False, bounds=None):
"""
- Initialization options:
- pos - Position of the line. This can be a QPointF or a single value for vertical/horizontal lines.
- angle - Angle of line in degrees. 0 is horizontal, 90 is vertical.
- pen - Pen to use when drawing line
- movable - If True, the line can be dragged to a new position by the user
- bounds - Optional [min, max] bounding values. Bounds are only valid if the line is vertical or horizontal.
+ ============= ==================================================================
+ **Arguments**
+ pos Position of the line. This can be a QPointF or a single value for
+ vertical/horizontal lines.
+ angle Angle of line in degrees. 0 is horizontal, 90 is vertical.
+ pen Pen to use when drawing line. Can be any arguments that are valid
+ for :func:`mkPen <pyqtgraph.mkPen>`. Default pen is transparent
+ yellow.
+ movable If True, the line can be dragged to a new position by the user.
+ bounds Optional [min, max] bounding values. Bounds are only valid if the
+ line is vertical or horizontal.
+ ============= ==================================================================
"""
UIGraphicsItem.__init__(self)
@@ -49,6 +64,7 @@ def __init__(self, pos=None, angle=90, pen=None, movable=False, bounds=None):
#self.setFlag(self.ItemSendsScenePositionChanges)
def setMovable(self, m):
+ """Set whether the line is movable by the user."""
self.movable = m
self.setAcceptHoverEvents(m)
@@ -58,6 +74,8 @@ def setBounds(self, bounds):
self.setValue(self.value())
def setPen(self, pen):
+ """Set the pen for drawing the line. Allowable arguments are any that are valid
+ for :func:`mkPen <pyqtgraph.mkPen>`."""
self.pen = fn.mkPen(pen)
self.currentPen = self.pen
self.update()
@@ -76,6 +94,7 @@ def setAngle(self, angle):
self.update()
def setPos(self, pos):
+
if type(pos) in [list, tuple]:
newPos = pos
elif isinstance(pos, QtCore.QPointF):
@@ -116,6 +135,8 @@ def getPos(self):
return self.p
def value(self):
+ """Return the value of the line. Will be a single number for horizontal and
+ vertical lines, and a list of [x,y] values for diagonal lines."""
if self.angle%180 == 0:
return self.getYPos()
elif self.angle%180 == 90:
@@ -124,6 +145,9 @@ def value(self):
return self.getPos()
def setValue(self, v):
+ """Set the position of the line. If line is horizontal or vertical, v can be
+ a single value. Otherwise, a 2D coordinate must be specified (list, tuple and
+ QPointF are all acceptable)."""
self.setPos(v)
## broken in 4.7
View
57 graphicsItems/IsocurveItem.py
@@ -7,22 +7,51 @@
class IsocurveItem(GraphicsObject):
"""
- Item displaying an isocurve of a 2D array.
+ **Bases:** :class:`GraphicsObject <pyqtgraph.GraphicsObject>`
- To align this item correctly with an ImageItem,
- call isocurve.setParentItem(image)
+ Item displaying an isocurve of a 2D array.To align this item correctly with an
+ ImageItem,call isocurve.setParentItem(image)
"""
+
def __init__(self, data=None, level=0, pen='w'):
+ """
+ Create a new isocurve item.
+
+ ============= ===============================================================
+ **Arguments**
+ data A 2-dimensional ndarray. Can be initialized as None, and set
+ later using :func:`setData <pyqtgraph.IsocurveItem.setData>`
+ level The cutoff value at which to draw the isocurve.
+ pen The color of the curve item. Can be anything valid for
+ :func:`mkPen <pyqtgraph.mkPen>`
+ ============= ===============================================================
+ """
GraphicsObject.__init__(self)
- self.level = 0
+
+ self.level = level
self.data = None
self.path = None
- self.setData(data, level)
self.setPen(pen)
+ self.setData(data, level)
+
+
+ #if data is not None and level is not None:
+ #self.updateLines(data, level)
+
def setData(self, data, level=None):
+ """
+ Set the data/image to draw isocurves for.
+
+ ============= ================================================================
+ **Arguments**
+ data A 2-dimensional ndarray.
+ level The cutoff value at which to draw the curve. If level is not specified,
+ the previous level is used.
+ ============= ================================================================
+ """
if level is None:
level = self.level
self.level = level
@@ -31,15 +60,33 @@ def setData(self, data, level=None):
self.prepareGeometryChange()
self.update()
+
def setLevel(self, level):
+ """Set the level at which the isocurve is drawn."""
self.level = level
self.path = None
self.update()
+
def setPen(self, *args, **kwargs):
+ """Set the pen used to draw the isocurve. Arguments can be any that are valid
+ for :func:`mkPen <pyqtgraph.mkPen>`"""
self.pen = fn.mkPen(*args, **kwargs)
self.update()
+
+ def updateLines(self, data, level):
+ ##print "data:", data
+ ##print "level", level
+ #lines = fn.isocurve(data, level)
+ ##print len(lines)
+ #self.path = QtGui.QPainterPath()
+ #for line in lines:
+ #self.path.moveTo(*line[0])
+ #self.path.lineTo(*line[1])
+ #self.update()
+ self.setData(data, level)
+
def boundingRect(self):
if self.path is None:
return QtCore.QRectF()
View
38 graphicsItems/LinearRegionItem.py
@@ -28,6 +28,23 @@ class LinearRegionItem(UIGraphicsItem):
Horizontal = 1
def __init__(self, values=[0,1], orientation=None, brush=None, movable=True, bounds=None):
+ """Create a new LinearRegionItem.
+
+ ============= =====================================================================
+ **Arguments**
+ values A list of the positions of the lines in the region. These are not
+ limits; limits can be set by specifying bounds.
+ orientation Options are LinearRegionItem.Vertical or LinearRegionItem.Horizontal.
+ If not specified it will be vertical.
+ brush Defines the brush that fills the region. Can be any arguments that
+ are valid for :func:`mkBrush <pyqtgraph.mkBrush>`. Default is
+ transparent blue.
+ movable If True, the region and individual lines are movable by the user; if
+ False, they are static.
+ bounds Optional [min, max] bounding values for the region
+ ============= =====================================================================
+ """
+
UIGraphicsItem.__init__(self)
if orientation is None:
orientation = LinearRegionItem.Vertical
@@ -70,6 +87,13 @@ def getRegion(self):
return (min(r), max(r))
def setRegion(self, rgn):
+ """Set the values for the edges of the region.
+
+ ============= ==============================================
+ **Arguments**
+ rgn A list or tuple of the lower and upper values.
+ ============= ==============================================
+ """
if self.lines[0].value() == rgn[0] and self.lines[1].value() == rgn[1]:
return
self.blockLineSignal = True
@@ -80,15 +104,25 @@ def setRegion(self, rgn):
self.lineMoved()
self.lineMoveFinished()
- def setBrush(self, br):
- self.brush = fn.mkBrush(br)
+ def setBrush(self, *br, **kargs):
+ """Set the brush that fills the region. Can have any arguments that are valid
+ for :func:`mkBrush <pyqtgraph.mkBrush>`.
+ """
+ self.brush = fn.mkBrush(*br, **kargs)
self.currentBrush = self.brush
def setBounds(self, bounds):
+ """Optional [min, max] bounding values for the region. To have no bounds on the
+ region use [None, None].
+ Does not affect the current position of the region unless it is outside the new bounds.
+ See :func:`setRegion <pyqtgraph.LinearRegionItem.setRegion>` to set the position
+ of the region."""
for l in self.lines:
l.setBounds(bounds)
def setMovable(self, m):
+ """Set lines to be movable by the user, or not. If lines are movable, they will
+ also accept HoverEvents."""
for l in self.lines:
l.setMovable(m)
self.movable = m
View
2  graphicsItems/PlotDataItem.py
@@ -389,7 +389,7 @@ def getData(self):
if self.xData is None:
return (None, None)
if self.xDisp is None:
- nanMask = np.isnan(self.xData) | np.isnan(self.yData)
+ nanMask = np.isnan(self.xData) | np.isnan(self.yData) | np.isinf(self.xData) | np.isinf(self.yData)
if any(nanMask):
x = self.xData[~nanMask]
y = self.yData[~nanMask]
View
8 graphicsItems/ScatterPlotItem.py
@@ -58,7 +58,7 @@ def setData(self, *args, **kargs):
* If there is only one unnamed argument, it will be interpreted like the 'spots' argument.
* If there are two unnamed arguments, they will be interpreted as sequences of x and y values.
- ====================== =================================================================================================
+ ====================== ===============================================================================================
**Keyword Arguments:**
*spots* Optional list of dicts. Each dict specifies parameters for a single spot:
{'pos': (x,y), 'size', 'pen', 'brush', 'symbol'}. This is just an alternate method
@@ -83,7 +83,7 @@ def setData(self, *args, **kargs):
*size* The size (or list of sizes) of spots. If *pxMode* is True, this value is in pixels. Otherwise,
it is in the item's local coordinate system.
*data* a list of python objects used to uniquely identify each spot.
- ====================== =================================================================================================
+ ====================== ===============================================================================================
"""
self.clear()
@@ -488,9 +488,13 @@ def boundingRect(self):
#self.sigPointClicked.emit(self, point)
def points(self):
+ if not self.spotsValid:
+ self.generateSpots()
return self.spots[:]
def pointsAt(self, pos):
+ if not self.spotsValid:
+ self.generateSpots()
x = pos.x()
y = pos.y()
pw = self.pixelWidth()
View
29 graphicsItems/VTickGroup.py
@@ -11,12 +11,24 @@
__all__ = ['VTickGroup']
class VTickGroup(UIGraphicsItem):
"""
+ **Bases:** :class:`UIGraphicsItem <pyqtgraph.UIGraphicsItem>`
+
Draws a set of tick marks which always occupy the same vertical range of the view,
but have x coordinates relative to the data within the view.
"""
def __init__(self, xvals=None, yrange=None, pen=None):
-
+ """
+ ============= ===================================================================
+ **Arguments**
+ xvals A list of x values (in data coordinates) at which to draw ticks.
+ yrange A list of [low, high] limits for the tick. 0 is the bottom of
+ the view, 1 is the top. [0.8, 1] would draw ticks in the top
+ fifth of the view.
+ pen The pen to use for drawing ticks. Default is grey. Can be specified
+ as any argument valid for :func:`mkPen<pyqtgraph.mkPen>`
+ ============= ===================================================================
+ """
if yrange is None:
yrange = [0, 1]
if xvals is None:
@@ -42,15 +54,26 @@ def __init__(self, xvals=None, yrange=None, pen=None):
self.setXVals(xvals)
#self.valid = False
- def setPen(self, pen):
- self.pen = fn.mkPen(pen)
+ def setPen(self, *args, **kwargs):
+ """Set the pen to use for drawing ticks. Can be specified as any arguments valid
+ for :func:`mkPen<pyqtgraph.mkPen>`"""
+ self.pen = fn.mkPen(*args, **kwargs)
def setXVals(self, vals):
+ """Set the x values for the ticks.
+
+ ============= =====================================================================
+ **Arguments**
+ vals A list of x values (in data/plot coordinates) at which to draw ticks.
+ ============= =====================================================================
+ """
self.xvals = vals
self.rebuildTicks()
#self.valid = False
def setYRange(self, vals):
+ """Set the y range [low, high] that the ticks are drawn on. 0 is the bottom of
+ the view, 1 is the top."""
self.yrange = vals
#self.relative = relative
#if self.view is not None:
View
4 widgets/CheckTable.py
@@ -24,6 +24,7 @@ def __init__(self, columns):
self.rowNames = []
self.rowWidgets = []
+ self.oldRows = {} ## remember settings from removed rows; reapply if they reappear.
def updateRows(self, rows):
@@ -47,6 +48,8 @@ def addRow(self, name):
self.layout.addWidget(check, row, col)
checks.append(check)
col += 1
+ if name in self.oldRows:
+ check.setChecked(self.oldRows[name])
#QtCore.QObject.connect(check, QtCore.SIGNAL('stateChanged(int)'), self.checkChanged)
check.stateChanged.connect(self.checkChanged)
self.rowNames.append(name)
@@ -54,6 +57,7 @@ def addRow(self, name):
def removeRow(self, name):
row = self.rowNames.index(name)
+ self.oldRows[name] = self.saveState['rows'][name] ## save for later
self.rowNames.pop(row)
for w in self.rowWidgets[row]:
w.setParent(None)
View
41 widgets/ComboBox.py
@@ -0,0 +1,41 @@
+from pyqtgraph.Qt import QtGui, QtCore
+from pyqtgraph.SignalProxy import SignalProxy
+
+
+class ComboBox(QtGui.QComboBox):
+ """Extends QComboBox to add extra functionality.
+ - updateList() - updates the items in the comboBox while blocking signals, remembers and resets to the previous values if it's still in the list
+ """
+
+
+ def __init__(self, parent=None, items=None, default=None):
+ QtGui.QComboBox.__init__(self, parent)
+
+ #self.value = default
+
+ if items is not None:
+ self.addItems(items)
+ if default is not None:
+ self.setValue(default)
+
+ def setValue(self, value):
+ ind = self.findText(value)
+ if ind == -1:
+ return
+ #self.value = value
+ self.setCurrentIndex(ind)
+
+ def updateList(self, items):
+ prevVal = str(self.currentText())
+ try:
+ self.blockSignals(True)
+ self.clear()
+ self.addItems(items)
+ self.setValue(prevVal)
+
+ finally:
+ self.blockSignals(False)
+
+ if str(self.currentText()) != prevVal:
+ self.currentIndexChanged.emit(self.currentIndex())
+
Please sign in to comment.
Something went wrong with that request. Please try again.