merge from luke
This commit is contained in:
commit
885d2157f1
@ -77,6 +77,11 @@ class Dock(QtGui.QWidget, DockDrop):
|
||||
return name == 'dock'
|
||||
|
||||
def setStretch(self, x=None, y=None):
|
||||
"""
|
||||
Set the 'target' size for this Dock.
|
||||
The actual size will be determined by comparing this Dock's
|
||||
stretch value to the rest of the docks it shares space with.
|
||||
"""
|
||||
#print "setStretch", self, x, y
|
||||
#self._stretch = (x, y)
|
||||
if x is None:
|
||||
@ -100,6 +105,10 @@ class Dock(QtGui.QWidget, DockDrop):
|
||||
#return self._stretch
|
||||
|
||||
def hideTitleBar(self):
|
||||
"""
|
||||
Hide the title bar for this Dock.
|
||||
This will prevent the Dock being moved by the user.
|
||||
"""
|
||||
self.label.hide()
|
||||
self.labelHidden = True
|
||||
if 'center' in self.allowedAreas:
|
||||
@ -107,12 +116,21 @@ class Dock(QtGui.QWidget, DockDrop):
|
||||
self.updateStyle()
|
||||
|
||||
def showTitleBar(self):
|
||||
"""
|
||||
Show the title bar for this Dock.
|
||||
"""
|
||||
self.label.show()
|
||||
self.labelHidden = False
|
||||
self.allowedAreas.add('center')
|
||||
self.updateStyle()
|
||||
|
||||
def setOrientation(self, o='auto', force=False):
|
||||
"""
|
||||
Sets the orientation of the title bar for this Dock.
|
||||
Must be one of 'auto', 'horizontal', or 'vertical'.
|
||||
By default ('auto'), the orientation is determined
|
||||
based on the aspect ratio of the Dock.
|
||||
"""
|
||||
#print self.name(), "setOrientation", o, force
|
||||
if o == 'auto' and self.autoOrient:
|
||||
if self.container().type() == 'tab':
|
||||
@ -127,6 +145,7 @@ class Dock(QtGui.QWidget, DockDrop):
|
||||
self.updateStyle()
|
||||
|
||||
def updateStyle(self):
|
||||
## updates orientation and appearance of title bar
|
||||
#print self.name(), "update style:", self.orientation, self.moveLabel, self.label.isVisible()
|
||||
if self.labelHidden:
|
||||
self.widgetArea.setStyleSheet(self.nStyle)
|
||||
@ -154,6 +173,10 @@ class Dock(QtGui.QWidget, DockDrop):
|
||||
return self._container
|
||||
|
||||
def addWidget(self, widget, row=None, col=0, rowspan=1, colspan=1):
|
||||
"""
|
||||
Add a new widget to the interior of this Dock.
|
||||
Each Dock uses a QGridLayout to arrange widgets within.
|
||||
"""
|
||||
if row is None:
|
||||
row = self.currentRow
|
||||
self.currentRow = max(row+1, self.currentRow)
|
||||
@ -188,7 +211,8 @@ class Dock(QtGui.QWidget, DockDrop):
|
||||
|
||||
def __repr__(self):
|
||||
return "<Dock %s %s>" % (self.name(), self.stretch())
|
||||
|
||||
|
||||
|
||||
class DockLabel(VerticalLabel):
|
||||
|
||||
sigClicked = QtCore.Signal(object, object)
|
||||
@ -287,76 +311,3 @@ class DockLabel(VerticalLabel):
|
||||
|
||||
|
||||
|
||||
#class DockLabel(QtGui.QWidget):
|
||||
#def __init__(self, text, dock):
|
||||
#QtGui.QWidget.__init__(self)
|
||||
#self._text = text
|
||||
#self.dock = dock
|
||||
#self.orientation = None
|
||||
#self.setOrientation('horizontal')
|
||||
|
||||
#def text(self):
|
||||
#return self._text
|
||||
|
||||
#def mousePressEvent(self, ev):
|
||||
#if ev.button() == QtCore.Qt.LeftButton:
|
||||
#self.pressPos = ev.pos()
|
||||
#self.startedDrag = False
|
||||
#ev.accept()
|
||||
|
||||
#def mouseMoveEvent(self, ev):
|
||||
#if not self.startedDrag and (ev.pos() - self.pressPos).manhattanLength() > QtGui.QApplication.startDragDistance():
|
||||
#self.dock.startDrag()
|
||||
#ev.accept()
|
||||
##print ev.pos()
|
||||
|
||||
#def mouseReleaseEvent(self, ev):
|
||||
#ev.accept()
|
||||
|
||||
#def mouseDoubleClickEvent(self, ev):
|
||||
#if ev.button() == QtCore.Qt.LeftButton:
|
||||
#self.dock.float()
|
||||
|
||||
#def setOrientation(self, o):
|
||||
#if self.orientation == o:
|
||||
#return
|
||||
#self.orientation = o
|
||||
#self.update()
|
||||
#self.updateGeometry()
|
||||
|
||||
#def paintEvent(self, ev):
|
||||
#p = QtGui.QPainter(self)
|
||||
#p.setBrush(QtGui.QBrush(QtGui.QColor(100, 100, 200)))
|
||||
#p.setPen(QtGui.QPen(QtGui.QColor(50, 50, 100)))
|
||||
#p.drawRect(self.rect().adjusted(0, 0, -1, -1))
|
||||
|
||||
#p.setPen(QtGui.QPen(QtGui.QColor(255, 255, 255)))
|
||||
|
||||
#if self.orientation == 'vertical':
|
||||
#p.rotate(-90)
|
||||
#rgn = QtCore.QRect(-self.height(), 0, self.height(), self.width())
|
||||
#else:
|
||||
#rgn = self.rect()
|
||||
#align = QtCore.Qt.AlignTop|QtCore.Qt.AlignHCenter
|
||||
|
||||
#self.hint = p.drawText(rgn, align, self.text())
|
||||
#p.end()
|
||||
|
||||
#if self.orientation == 'vertical':
|
||||
#self.setMaximumWidth(self.hint.height())
|
||||
#self.setMaximumHeight(16777215)
|
||||
#else:
|
||||
#self.setMaximumHeight(self.hint.height())
|
||||
#self.setMaximumWidth(16777215)
|
||||
|
||||
#def sizeHint(self):
|
||||
#if self.orientation == 'vertical':
|
||||
#if hasattr(self, 'hint'):
|
||||
#return QtCore.QSize(self.hint.height(), self.hint.width())
|
||||
#else:
|
||||
#return QtCore.QSize(19, 50)
|
||||
#else:
|
||||
#if hasattr(self, 'hint'):
|
||||
#return QtCore.QSize(self.hint.width(), self.hint.height())
|
||||
#else:
|
||||
#return QtCore.QSize(50, 19)
|
||||
|
@ -35,8 +35,18 @@ class DockArea(Container, QtGui.QWidget, DockDrop):
|
||||
|
||||
def addDock(self, dock, position='bottom', relativeTo=None):
|
||||
"""Adds a dock to this area.
|
||||
position may be: bottom, top, left, right, over, under
|
||||
If relativeTo specifies an existing dock, the new dock is added adjacent to it"""
|
||||
|
||||
=========== =================================================================
|
||||
Arguments:
|
||||
dock The new Dock object to add.
|
||||
position 'bottom', 'top', 'left', 'right', 'over', or 'under'
|
||||
relativeTo If relativeTo is None, then the new Dock is added to fill an
|
||||
entire edge of the window. If relativeTo is another Dock, then
|
||||
the new Dock is placed adjacent to it (or in a tabbed
|
||||
configuration for 'over' and 'under').
|
||||
=========== =================================================================
|
||||
|
||||
"""
|
||||
|
||||
## Determine the container to insert this dock into.
|
||||
## If there is no neighbor, then the container is the top.
|
||||
@ -90,6 +100,17 @@ class DockArea(Container, QtGui.QWidget, DockDrop):
|
||||
dock.area = self
|
||||
self.docks[dock.name()] = dock
|
||||
|
||||
def moveDock(self, dock, position, neighbor):
|
||||
"""
|
||||
Move an existing Dock to a new location.
|
||||
"""
|
||||
old = dock.container()
|
||||
## Moving to the edge of a tabbed dock causes a drop outside the tab box
|
||||
if position in ['left', 'right', 'top', 'bottom'] and neighbor is not None and neighbor.container() is not None and neighbor.container().type() == 'tab':
|
||||
neighbor = neighbor.container()
|
||||
self.addDock(dock, position, neighbor)
|
||||
old.apoptose()
|
||||
|
||||
def getContainer(self, obj):
|
||||
if obj is None:
|
||||
return self
|
||||
@ -131,13 +152,6 @@ class DockArea(Container, QtGui.QWidget, DockDrop):
|
||||
return 0
|
||||
return 1
|
||||
|
||||
def moveDock(self, dock, position, neighbor):
|
||||
old = dock.container()
|
||||
## Moving to the edge of a tabbed dock causes a drop outside the tab box
|
||||
if position in ['left', 'right', 'top', 'bottom'] and neighbor is not None and neighbor.container() is not None and neighbor.container().type() == 'tab':
|
||||
neighbor = neighbor.container()
|
||||
self.addDock(dock, position, neighbor)
|
||||
old.apoptose()
|
||||
|
||||
#def paintEvent(self, ev):
|
||||
#self.drawDockOverlay()
|
||||
@ -159,6 +173,7 @@ class DockArea(Container, QtGui.QWidget, DockDrop):
|
||||
return area
|
||||
|
||||
def floatDock(self, dock):
|
||||
"""Removes *dock* from this DockArea and places it in a new window."""
|
||||
area = self.addTempArea()
|
||||
area.win.resize(dock.size())
|
||||
area.moveDock(dock, 'top', None)
|
||||
@ -170,6 +185,9 @@ class DockArea(Container, QtGui.QWidget, DockDrop):
|
||||
area.window().close()
|
||||
|
||||
def saveState(self):
|
||||
"""
|
||||
Return a serialized (storable) representation of the state of
|
||||
all Docks in this DockArea."""
|
||||
state = {'main': self.childState(self.topContainer), 'float': []}
|
||||
for a in self.tempAreas:
|
||||
geo = a.win.geometry()
|
||||
@ -188,6 +206,10 @@ class DockArea(Container, QtGui.QWidget, DockDrop):
|
||||
|
||||
|
||||
def restoreState(self, state):
|
||||
"""
|
||||
Restore Dock configuration as generated by saveState.
|
||||
"""
|
||||
|
||||
## 1) make dict of all docks and list of existing containers
|
||||
containers, docks = self.findAll()
|
||||
oldTemps = self.tempAreas[:]
|
||||
|
8
documentation/source/3dgraphics/glimageitem.rst
Normal file
8
documentation/source/3dgraphics/glimageitem.rst
Normal file
@ -0,0 +1,8 @@
|
||||
GLImageItem
|
||||
===========
|
||||
|
||||
.. autoclass:: pyqtgraph.opengl.GLImageItem
|
||||
:members:
|
||||
|
||||
.. automethod:: pyqtgraph.opengl.GLImageItem.__init__
|
||||
|
8
documentation/source/3dgraphics/glscatterplotitem.rst
Normal file
8
documentation/source/3dgraphics/glscatterplotitem.rst
Normal file
@ -0,0 +1,8 @@
|
||||
GLScatterPlotItem
|
||||
=================
|
||||
|
||||
.. autoclass:: pyqtgraph.opengl.GLScatterPlotItem
|
||||
:members:
|
||||
|
||||
.. automethod:: pyqtgraph.opengl.GLScatterPlotItem.__init__
|
||||
|
@ -18,7 +18,9 @@ Contents:
|
||||
glgriditem
|
||||
glmeshitem
|
||||
glvolumeitem
|
||||
glimageitem
|
||||
glaxisitem
|
||||
glgraphicsitem
|
||||
glscatterplotitem
|
||||
meshdata
|
||||
|
||||
|
@ -29,6 +29,7 @@ Contents:
|
||||
scalebar
|
||||
labelitem
|
||||
vtickgroup
|
||||
legenditem
|
||||
gradienteditoritem
|
||||
histogramlutitem
|
||||
gradientlegend
|
||||
|
8
documentation/source/graphicsItems/legenditem.rst
Normal file
8
documentation/source/graphicsItems/legenditem.rst
Normal file
@ -0,0 +1,8 @@
|
||||
LegendItem
|
||||
==========
|
||||
|
||||
.. autoclass:: pyqtgraph.LegendItem
|
||||
:members:
|
||||
|
||||
.. automethod:: pyqtgraph.LegendItem.__init__
|
||||
|
47
examples/GLImageItem.py
Normal file
47
examples/GLImageItem.py
Normal file
@ -0,0 +1,47 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
## Add path to library (just for examples; you do not need this)
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
|
||||
from pyqtgraph.Qt import QtCore, QtGui
|
||||
import pyqtgraph.opengl as gl
|
||||
import pyqtgraph as pg
|
||||
import numpy as np
|
||||
import scipy.ndimage as ndi
|
||||
|
||||
app = QtGui.QApplication([])
|
||||
w = gl.GLViewWidget()
|
||||
w.opts['distance'] = 200
|
||||
w.show()
|
||||
|
||||
## create volume data set to slice three images from
|
||||
shape = (100,100,70)
|
||||
data = ndi.gaussian_filter(np.random.normal(size=shape), (4,4,4))
|
||||
data += ndi.gaussian_filter(np.random.normal(size=shape), (15,15,15))*15
|
||||
|
||||
## slice out three planes, convert to ARGB for OpenGL texture
|
||||
levels = (-0.08, 0.08)
|
||||
tex1 = pg.makeRGBA(data[shape[0]/2], levels=levels)[0] # yz plane
|
||||
tex2 = pg.makeRGBA(data[:,shape[1]/2], levels=levels)[0] # xz plane
|
||||
tex3 = pg.makeRGBA(data[:,:,shape[2]/2], levels=levels)[0] # xy plane
|
||||
|
||||
## Create three image items from textures, add to view
|
||||
v1 = gl.GLImageItem(tex1)
|
||||
v1.translate(-shape[1]/2, -shape[2]/2, 0)
|
||||
v1.rotate(90, 0,0,1)
|
||||
v1.rotate(-90, 0,1,0)
|
||||
w.addItem(v1)
|
||||
v2 = gl.GLImageItem(tex2)
|
||||
v2.translate(-shape[0]/2, -shape[2]/2, 0)
|
||||
v2.rotate(-90, 1,0,0)
|
||||
w.addItem(v2)
|
||||
v3 = gl.GLImageItem(tex3)
|
||||
v3.translate(-shape[0]/2, -shape[1]/2, 0)
|
||||
w.addItem(v3)
|
||||
|
||||
ax = gl.GLAxisItem()
|
||||
w.addItem(ax)
|
||||
|
||||
## Start Qt event loop unless running in interactive mode.
|
||||
if sys.flags.interactive != 1:
|
||||
app.exec_()
|
@ -5,6 +5,7 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
|
||||
from pyqtgraph.Qt import QtCore, QtGui
|
||||
import pyqtgraph.opengl as gl
|
||||
import numpy as np
|
||||
|
||||
app = QtGui.QApplication([])
|
||||
w = gl.GLViewWidget()
|
||||
@ -14,18 +15,47 @@ w.show()
|
||||
g = gl.GLGridItem()
|
||||
w.addItem(g)
|
||||
|
||||
pts = [
|
||||
{'pos': (1,0,0), 'size':0.5, 'color':(1.0, 0.0, 0.0, 0.5)},
|
||||
{'pos': (0,1,0), 'size':0.2, 'color':(0.0, 0.0, 1.0, 0.5)},
|
||||
{'pos': (0,0,1), 'size':2./3., 'color':(0.0, 1.0, 0.0, 0.5)},
|
||||
]
|
||||
z = 0.5
|
||||
d = 6.0
|
||||
for i in range(50):
|
||||
pts.append({'pos': (0,0,z), 'size':2./d, 'color':(0.0, 1.0, 0.0, 0.5)})
|
||||
z *= 0.5
|
||||
d *= 2.0
|
||||
sp = gl.GLScatterPlotItem(pts)
|
||||
#pos = np.empty((53, 3))
|
||||
#size = np.empty((53))
|
||||
#color = np.empty((53, 4))
|
||||
#pos[0] = (1,0,0); size[0] = 0.5; color[0] = (1.0, 0.0, 0.0, 0.5)
|
||||
#pos[1] = (0,1,0); size[1] = 0.2; color[1] = (0.0, 0.0, 1.0, 0.5)
|
||||
#pos[2] = (0,0,1); size[2] = 2./3.; color[2] = (0.0, 1.0, 0.0, 0.5)
|
||||
|
||||
#z = 0.5
|
||||
#d = 6.0
|
||||
#for i in range(3,53):
|
||||
#pos[i] = (0,0,z)
|
||||
#size[i] = 2./d
|
||||
#color[i] = (0.0, 1.0, 0.0, 0.5)
|
||||
#z *= 0.5
|
||||
#d *= 2.0
|
||||
|
||||
#sp = gl.GLScatterPlotItem(pos=pos, sizes=size, colors=color, pxMode=False)
|
||||
|
||||
|
||||
pos = (np.random.random(size=(100000,3)) * 10) - 5
|
||||
color = np.ones((pos.shape[0], 4))
|
||||
d = (pos**2).sum(axis=1)**0.5
|
||||
color[:,3] = np.clip(-np.cos(d*2) * 0.2, 0, 1)
|
||||
sp = gl.GLScatterPlotItem(pos=pos, color=color, size=5)
|
||||
phase = 0.
|
||||
|
||||
def update():
|
||||
global phase, color, sp, d
|
||||
s = -np.cos(d*2+phase)
|
||||
color[:,3] = np.clip(s * 0.2, 0, 1)
|
||||
color[:,0] = np.clip(s * 3.0, 0, 1)
|
||||
color[:,1] = np.clip(s * 1.0, 0, 1)
|
||||
color[:,2] = np.clip(s ** 3, 0, 1)
|
||||
|
||||
sp.setData(color=color)
|
||||
phase -= 0.1
|
||||
|
||||
t = QtCore.QTimer()
|
||||
t.timeout.connect(update)
|
||||
t.start(50)
|
||||
|
||||
w.addItem(sp)
|
||||
|
||||
## Start Qt event loop unless running in interactive mode.
|
||||
|
21
examples/Legend.py
Normal file
21
examples/Legend.py
Normal file
@ -0,0 +1,21 @@
|
||||
# -*- 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
|
||||
|
||||
plt = pg.plot()
|
||||
|
||||
l = pg.LegendItem((100,60), (60,10)) # args are (size, position)
|
||||
l.setParentItem(plt.graphicsItem()) # Note we do NOT call plt.addItem in this case
|
||||
|
||||
c1 = plt.plot([1,3,2,4], pen='r')
|
||||
c2 = plt.plot([2,1,4,3], pen='g')
|
||||
l.addItem(c1, 'red plot')
|
||||
l.addItem(c2, 'green plot')
|
||||
|
||||
|
||||
## Start Qt event loop unless running in interactive mode or using pyside.
|
||||
import sys
|
||||
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
|
||||
QtGui.QApplication.instance().exec_()
|
@ -26,7 +26,7 @@ win.show()
|
||||
p = ui.plot
|
||||
|
||||
data = np.random.normal(size=(50,500), scale=100)
|
||||
sizeArray = np.random.random(500) * 20.
|
||||
sizeArray = (np.random.random(500) * 20.).astype(int)
|
||||
ptr = 0
|
||||
lastTime = time()
|
||||
fps = None
|
||||
@ -49,7 +49,8 @@ def update():
|
||||
s = np.clip(dt*3., 0, 1)
|
||||
fps = fps * (1-s) + (1.0/dt) * s
|
||||
p.setTitle('%0.2f fps' % fps)
|
||||
app.processEvents() ## force complete redraw for every plot
|
||||
p.repaint()
|
||||
#app.processEvents() ## force complete redraw for every plot
|
||||
timer = QtCore.QTimer()
|
||||
timer.timeout.connect(update)
|
||||
timer.start(0)
|
||||
|
@ -17,12 +17,11 @@ examples = OrderedDict([
|
||||
('ImageView', 'ImageView.py'),
|
||||
('ParameterTree', 'parametertree.py'),
|
||||
('Crosshair / Mouse interaction', 'crosshair.py'),
|
||||
('Video speed test', 'VideoSpeedTest.py'),
|
||||
('Plot speed test', 'PlotSpeedTest.py'),
|
||||
('Data Slicing', 'DataSlicing.py'),
|
||||
('Plot Customization', 'customPlot.py'),
|
||||
('Dock widgets', 'dockarea.py'),
|
||||
('Console', 'ConsoleWidget.py'),
|
||||
('Histograms', 'histogram.py'),
|
||||
('GraphicsItems', OrderedDict([
|
||||
('Scatter Plot', 'ScatterPlot.py'),
|
||||
#('PlotItem', 'PlotItem.py'),
|
||||
@ -31,14 +30,22 @@ examples = OrderedDict([
|
||||
('ImageItem - draw', 'Draw.py'),
|
||||
('Region-of-Interest', 'ROIExamples.py'),
|
||||
('GraphicsLayout', 'GraphicsLayout.py'),
|
||||
('LegendItem', 'Legend.py'),
|
||||
('Text Item', 'text.py'),
|
||||
('Linked Views', 'linkedViews.py'),
|
||||
('Arrow', 'Arrow.py'),
|
||||
('ViewBox', 'ViewBox.py'),
|
||||
])),
|
||||
('Benchmarks', OrderedDict([
|
||||
('Video speed test', 'VideoSpeedTest.py'),
|
||||
('Line Plot update', 'PlotSpeedTest.py'),
|
||||
('Scatter Plot update', 'ScatterPlotSpeedTest.py'),
|
||||
])),
|
||||
('3D Graphics', OrderedDict([
|
||||
('Volumetric', 'GLVolumeItem.py'),
|
||||
('Isosurface', 'GLMeshItem.py'),
|
||||
('Image', 'GLImageItem.py'),
|
||||
('Scatter Plot', 'GLScatterPlotItem.py'),
|
||||
])),
|
||||
('Widgets', OrderedDict([
|
||||
('PlotWidget', 'PlotWidget.py'),
|
||||
@ -80,7 +87,17 @@ class ExampleLoader(QtGui.QMainWindow):
|
||||
self.ui.loadBtn.clicked.connect(self.loadFile)
|
||||
self.ui.exampleTree.currentItemChanged.connect(self.showFile)
|
||||
self.ui.exampleTree.itemDoubleClicked.connect(self.loadFile)
|
||||
self.ui.pyqtCheck.toggled.connect(self.pyqtToggled)
|
||||
self.ui.pysideCheck.toggled.connect(self.pysideToggled)
|
||||
|
||||
def pyqtToggled(self, b):
|
||||
if b:
|
||||
self.ui.pysideCheck.setChecked(False)
|
||||
|
||||
def pysideToggled(self, b):
|
||||
if b:
|
||||
self.ui.pyqtCheck.setChecked(False)
|
||||
|
||||
|
||||
def populateTree(self, root, examples):
|
||||
for key, val in examples.items():
|
||||
@ -101,12 +118,19 @@ class ExampleLoader(QtGui.QMainWindow):
|
||||
|
||||
def loadFile(self):
|
||||
fn = self.currentFile()
|
||||
extra = []
|
||||
if self.ui.pyqtCheck.isChecked():
|
||||
extra.append('pyqt')
|
||||
elif self.ui.pysideCheck.isChecked():
|
||||
extra.append('pyside')
|
||||
|
||||
if fn is None:
|
||||
return
|
||||
if sys.platform.startswith('win'):
|
||||
os.spawnl(os.P_NOWAIT, sys.executable, sys.executable, '"' + fn + '"')
|
||||
os.spawnl(os.P_NOWAIT, sys.executable, sys.executable, '"' + fn + '"', *extra)
|
||||
else:
|
||||
os.spawnl(os.P_NOWAIT, sys.executable, sys.executable, fn)
|
||||
|
||||
os.spawnl(os.P_NOWAIT, sys.executable, sys.executable, fn, *extra)
|
||||
|
||||
|
||||
def showFile(self):
|
||||
|
@ -39,6 +39,24 @@
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="pyqtCheck">
|
||||
<property name="text">
|
||||
<string>Force PyQt</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="pysideCheck">
|
||||
<property name="text">
|
||||
<string>Force PySide</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="loadBtn">
|
||||
<property name="text">
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
# Form implementation generated from reading ui file './examples/exampleLoaderTemplate.ui'
|
||||
#
|
||||
# Created: Sun Sep 9 14:41:31 2012
|
||||
# Created: Fri Oct 26 07:53:55 2012
|
||||
# by: PyQt4 UI code generator 4.9.1
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
@ -35,6 +35,15 @@ class Ui_Form(object):
|
||||
self.exampleTree.headerItem().setText(0, _fromUtf8("1"))
|
||||
self.exampleTree.header().setVisible(False)
|
||||
self.verticalLayout.addWidget(self.exampleTree)
|
||||
self.horizontalLayout = QtGui.QHBoxLayout()
|
||||
self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout"))
|
||||
self.pyqtCheck = QtGui.QCheckBox(self.layoutWidget)
|
||||
self.pyqtCheck.setObjectName(_fromUtf8("pyqtCheck"))
|
||||
self.horizontalLayout.addWidget(self.pyqtCheck)
|
||||
self.pysideCheck = QtGui.QCheckBox(self.layoutWidget)
|
||||
self.pysideCheck.setObjectName(_fromUtf8("pysideCheck"))
|
||||
self.horizontalLayout.addWidget(self.pysideCheck)
|
||||
self.verticalLayout.addLayout(self.horizontalLayout)
|
||||
self.loadBtn = QtGui.QPushButton(self.layoutWidget)
|
||||
self.loadBtn.setObjectName(_fromUtf8("loadBtn"))
|
||||
self.verticalLayout.addWidget(self.loadBtn)
|
||||
@ -51,5 +60,7 @@ class Ui_Form(object):
|
||||
|
||||
def retranslateUi(self, Form):
|
||||
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.pyqtCheck.setText(QtGui.QApplication.translate("Form", "Force PyQt", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.pysideCheck.setText(QtGui.QApplication.translate("Form", "Force PySide", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.loadBtn.setText(QtGui.QApplication.translate("Form", "Load Example", None, QtGui.QApplication.UnicodeUTF8))
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
# Form implementation generated from reading ui file './examples/exampleLoaderTemplate.ui'
|
||||
#
|
||||
# Created: Sun Sep 9 14:41:31 2012
|
||||
# Created: Fri Oct 26 07:53:57 2012
|
||||
# by: pyside-uic 0.2.13 running on PySide 1.1.0
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
@ -30,6 +30,15 @@ class Ui_Form(object):
|
||||
self.exampleTree.headerItem().setText(0, "1")
|
||||
self.exampleTree.header().setVisible(False)
|
||||
self.verticalLayout.addWidget(self.exampleTree)
|
||||
self.horizontalLayout = QtGui.QHBoxLayout()
|
||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||
self.pyqtCheck = QtGui.QCheckBox(self.layoutWidget)
|
||||
self.pyqtCheck.setObjectName("pyqtCheck")
|
||||
self.horizontalLayout.addWidget(self.pyqtCheck)
|
||||
self.pysideCheck = QtGui.QCheckBox(self.layoutWidget)
|
||||
self.pysideCheck.setObjectName("pysideCheck")
|
||||
self.horizontalLayout.addWidget(self.pysideCheck)
|
||||
self.verticalLayout.addLayout(self.horizontalLayout)
|
||||
self.loadBtn = QtGui.QPushButton(self.layoutWidget)
|
||||
self.loadBtn.setObjectName("loadBtn")
|
||||
self.verticalLayout.addWidget(self.loadBtn)
|
||||
@ -46,5 +55,7 @@ class Ui_Form(object):
|
||||
|
||||
def retranslateUi(self, Form):
|
||||
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.pyqtCheck.setText(QtGui.QApplication.translate("Form", "Force PyQt", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.pysideCheck.setText(QtGui.QApplication.translate("Form", "Force PySide", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.loadBtn.setText(QtGui.QApplication.translate("Form", "Load Example", None, QtGui.QApplication.UnicodeUTF8))
|
||||
|
||||
|
38
examples/histogram.py
Normal file
38
examples/histogram.py
Normal file
@ -0,0 +1,38 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
In this example we draw two different kinds of histogram.
|
||||
"""
|
||||
|
||||
|
||||
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.GraphicsWindow()
|
||||
win.resize(800,350)
|
||||
plt1 = win.addPlot()
|
||||
plt2 = win.addPlot()
|
||||
|
||||
## make interesting distribution of values
|
||||
vals = np.hstack([np.random.normal(size=500), np.random.normal(size=260, loc=4)])
|
||||
|
||||
## draw standard histogram
|
||||
y,x = np.histogram(vals, bins=np.linspace(-3, 8, 40))
|
||||
|
||||
## notice that len(x) == len(y)+1
|
||||
## We are required to use stepMode=True so that PlotCurveItem will interpret this data correctly.
|
||||
curve = pg.PlotCurveItem(x, y, stepMode=True, fillLevel=0, brush=(0, 0, 255, 80))
|
||||
plt1.addItem(curve)
|
||||
|
||||
|
||||
## Now draw all points as a nicely-spaced scatter plot
|
||||
y = pg.pseudoScatter(vals, spacing=0.15)
|
||||
#plt2.plot(vals, y, pen=None, symbol='o', symbolSize=5)
|
||||
plt2.plot(vals, y, pen=None, symbol='o', symbolSize=5, symbolPen=(255,255,255,200), symbolBrush=(0,0,255,150))
|
||||
|
||||
## Start Qt event loop unless running in interactive mode or using pyside.
|
||||
import sys
|
||||
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
|
||||
QtGui.QApplication.instance().exec_()
|
@ -1,3 +1,8 @@
|
||||
## make this version of pyqtgraph importable before any others
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')))
|
||||
|
||||
if 'pyside' in sys.argv: ## should force example to use PySide instead of PyQt
|
||||
import PySide
|
||||
elif 'pyqt' in sys.argv:
|
||||
import PyQt4
|
||||
|
@ -22,7 +22,7 @@ class CSVExporter(Exporter):
|
||||
def export(self, fileName=None):
|
||||
|
||||
if not isinstance(self.item, pg.PlotItem):
|
||||
raise Exception("Matplotlib export currently only works with plot items")
|
||||
raise Exception("Must have a PlotItem selected for CSV export.")
|
||||
|
||||
if fileName is None:
|
||||
self.fileSaveDialog(filter=["*.csv", "*.tsv"])
|
||||
|
@ -54,8 +54,13 @@ class Exporter(object):
|
||||
fileName = str(fileName)
|
||||
global LastExportDirectory
|
||||
LastExportDirectory = os.path.split(fileName)[0]
|
||||
self.export(fileName=fileName, **self.fileDialog.opts)
|
||||
|
||||
ext = os.path.splitext(fileName)[1].lower()
|
||||
selectedExt = str(self.fileDialog.selectedNameFilter()).lstrip('*').lower()
|
||||
if ext != selectedExt:
|
||||
fileName = fileName + selectedExt
|
||||
|
||||
self.export(fileName=fileName, **self.fileDialog.opts)
|
||||
|
||||
def getScene(self):
|
||||
if isinstance(self.item, pg.GraphicsScene):
|
||||
|
@ -63,7 +63,7 @@ class ImageExporter(Exporter):
|
||||
self.getScene().render(painter, QtCore.QRectF(targetRect), sourceRect)
|
||||
finally:
|
||||
self.setExportMode(False)
|
||||
self.png.save(fileName)
|
||||
painter.end()
|
||||
self.png.save(fileName)
|
||||
|
||||
|
180
functions.py
180
functions.py
@ -22,9 +22,10 @@ SI_PREFIXES_ASCII = 'yzafpnum kMGTPEZY'
|
||||
|
||||
|
||||
|
||||
from .Qt import QtGui, QtCore
|
||||
from .Qt import QtGui, QtCore, USE_PYSIDE
|
||||
import numpy as np
|
||||
import decimal, re
|
||||
import ctypes
|
||||
|
||||
try:
|
||||
import scipy.ndimage
|
||||
@ -223,13 +224,15 @@ def mkColor(*args):
|
||||
return QtGui.QColor(*args)
|
||||
|
||||
|
||||
def mkBrush(*args):
|
||||
def mkBrush(*args, **kwds):
|
||||
"""
|
||||
| Convenience function for constructing Brush.
|
||||
| This function always constructs a solid brush and accepts the same arguments as :func:`mkColor() <pyqtgraph.mkColor>`
|
||||
| Calling mkBrush(None) returns an invisible brush.
|
||||
"""
|
||||
if len(args) == 1:
|
||||
if 'color' in kwds:
|
||||
color = kwds['color']
|
||||
elif len(args) == 1:
|
||||
arg = args[0]
|
||||
if arg is None:
|
||||
return QtGui.QBrush(QtCore.Qt.NoBrush)
|
||||
@ -237,7 +240,7 @@ def mkBrush(*args):
|
||||
return QtGui.QBrush(arg)
|
||||
else:
|
||||
color = arg
|
||||
if len(args) > 1:
|
||||
elif len(args) > 1:
|
||||
color = args
|
||||
return QtGui.QBrush(mkColor(color))
|
||||
|
||||
@ -579,7 +582,10 @@ def solveBilinearTransform(points1, points2):
|
||||
|
||||
|
||||
|
||||
|
||||
def makeRGBA(*args, **kwds):
|
||||
"""Equivalent to makeARGB(..., useRGBA=True)"""
|
||||
kwds['useRGBA'] = True
|
||||
return makeARGB(*args, **kwds)
|
||||
|
||||
def makeARGB(data, lut=None, levels=None, useRGBA=False):
|
||||
"""
|
||||
@ -605,7 +611,7 @@ def makeARGB(data, lut=None, levels=None, useRGBA=False):
|
||||
Lookup tables can be built using GradientWidget.
|
||||
levels - List [min, max]; optionally rescale data before converting through the
|
||||
lookup table. rescaled = (data-min) * len(lut) / (max-min)
|
||||
useRGBA - If True, the data is returned in RGBA order. The default is
|
||||
useRGBA - If True, the data is returned in RGBA order (useful for building OpenGL textures). The default is
|
||||
False, which returns in BGRA order for use with QImage.
|
||||
|
||||
"""
|
||||
@ -779,30 +785,117 @@ def makeARGB(data, lut=None, levels=None, useRGBA=False):
|
||||
return imgData, alpha
|
||||
|
||||
|
||||
def makeQImage(imgData, alpha):
|
||||
"""Turn an ARGB array into QImage"""
|
||||
def makeQImage(imgData, alpha=None, copy=True, transpose=True):
|
||||
"""
|
||||
Turn an ARGB array into QImage.
|
||||
By default, the data is copied; changes to the array will not
|
||||
be reflected in the image. The image will be given a 'data' attribute
|
||||
pointing to the array which shares its data to prevent python
|
||||
freeing that memory while the image is in use.
|
||||
|
||||
=========== ===================================================================
|
||||
Arguments:
|
||||
imgData Array of data to convert. Must have shape (width, height, 3 or 4)
|
||||
and dtype=ubyte. The order of values in the 3rd axis must be
|
||||
(b, g, r, a).
|
||||
alpha If True, the QImage returned will have format ARGB32. If False,
|
||||
the format will be RGB32. By default, _alpha_ is True if
|
||||
array.shape[2] == 4.
|
||||
copy If True, the data is copied before converting to QImage.
|
||||
If False, the new QImage points directly to the data in the array.
|
||||
Note that the array must be contiguous for this to work.
|
||||
transpose If True (the default), the array x/y axes are transposed before
|
||||
creating the image. Note that Qt expects the axes to be in
|
||||
(height, width) order whereas pyqtgraph usually prefers the
|
||||
opposite.
|
||||
=========== ===================================================================
|
||||
"""
|
||||
## create QImage from buffer
|
||||
prof = debug.Profiler('functions.makeQImage', disabled=True)
|
||||
|
||||
## If we didn't explicitly specify alpha, check the array shape.
|
||||
if alpha is None:
|
||||
alpha = (imgData.shape[2] == 4)
|
||||
|
||||
copied = False
|
||||
if imgData.shape[2] == 3: ## need to make alpha channel (even if alpha==False; QImage requires 32 bpp)
|
||||
if copy is True:
|
||||
d2 = np.empty(imgData.shape[:2] + (4,), dtype=imgData.dtype)
|
||||
d2[:,:,:3] = imgData
|
||||
d2[:,:,3] = 255
|
||||
imgData = d2
|
||||
copied = True
|
||||
else:
|
||||
raise Exception('Array has only 3 channels; cannot make QImage without copying.')
|
||||
|
||||
if alpha:
|
||||
imgFormat = QtGui.QImage.Format_ARGB32
|
||||
else:
|
||||
imgFormat = QtGui.QImage.Format_RGB32
|
||||
|
||||
imgData = imgData.transpose((1, 0, 2)) ## QImage expects the row/column order to be opposite
|
||||
try:
|
||||
buf = imgData.data
|
||||
except AttributeError: ## happens when image data is non-contiguous
|
||||
if transpose:
|
||||
imgData = imgData.transpose((1, 0, 2)) ## QImage expects the row/column order to be opposite
|
||||
|
||||
if not imgData.flags['C_CONTIGUOUS']:
|
||||
if copy is False:
|
||||
extra = ' (try setting transpose=False)' if transpose else ''
|
||||
raise Exception('Array is not contiguous; cannot make QImage without copying.'+extra)
|
||||
imgData = np.ascontiguousarray(imgData)
|
||||
buf = imgData.data
|
||||
copied = True
|
||||
|
||||
prof.mark('1')
|
||||
qimage = QtGui.QImage(buf, imgData.shape[1], imgData.shape[0], imgFormat)
|
||||
prof.mark('2')
|
||||
qimage.data = imgData
|
||||
prof.finish()
|
||||
return qimage
|
||||
if copy is True and copied is False:
|
||||
imgData = imgData.copy()
|
||||
|
||||
if USE_PYSIDE:
|
||||
ch = ctypes.c_char.from_buffer(imgData, 0)
|
||||
img = QtGui.QImage(ch, imgData.shape[1], imgData.shape[0], imgFormat)
|
||||
else:
|
||||
addr = ctypes.addressof(ctypes.c_char.from_buffer(imgData, 0))
|
||||
img = QtGui.QImage(addr, imgData.shape[1], imgData.shape[0], imgFormat)
|
||||
img.data = imgData
|
||||
return img
|
||||
#try:
|
||||
#buf = imgData.data
|
||||
#except AttributeError: ## happens when image data is non-contiguous
|
||||
#buf = imgData.data
|
||||
|
||||
#prof.mark('1')
|
||||
#qimage = QtGui.QImage(buf, imgData.shape[1], imgData.shape[0], imgFormat)
|
||||
#prof.mark('2')
|
||||
#qimage.data = imgData
|
||||
#prof.finish()
|
||||
#return qimage
|
||||
|
||||
def imageToArray(img, copy=False, transpose=True):
|
||||
"""
|
||||
Convert a QImage into numpy array. The image must have format RGB32, ARGB32, or ARGB32_Premultiplied.
|
||||
By default, the image is not copied; changes made to the array will appear in the QImage as well (beware: if
|
||||
the QImage is collected before the array, there may be trouble).
|
||||
The array will have shape (width, height, (b,g,r,a)).
|
||||
"""
|
||||
fmt = img.format()
|
||||
ptr = img.bits()
|
||||
if USE_PYSIDE:
|
||||
arr = np.frombuffer(ptr, dtype=np.ubyte)
|
||||
else:
|
||||
ptr.setsize(img.byteCount())
|
||||
arr = np.asarray(ptr)
|
||||
|
||||
if fmt == img.Format_RGB32:
|
||||
arr = arr.reshape(img.height(), img.width(), 3)
|
||||
elif fmt == img.Format_ARGB32 or fmt == img.Format_ARGB32_Premultiplied:
|
||||
arr = arr.reshape(img.height(), img.width(), 4)
|
||||
|
||||
if copy:
|
||||
arr = arr.copy()
|
||||
|
||||
if transpose:
|
||||
return arr.transpose((1,0,2))
|
||||
else:
|
||||
return arr
|
||||
|
||||
|
||||
|
||||
|
||||
def rescaleData(data, scale, offset):
|
||||
newData = np.empty((data.size,), dtype=np.int)
|
||||
@ -1386,3 +1479,52 @@ 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):
|
||||
"""
|
||||
Used for examining the distribution of values in a set.
|
||||
|
||||
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).
|
||||
"""
|
||||
inds = np.arange(len(data))
|
||||
if shuffle:
|
||||
np.random.shuffle(inds)
|
||||
|
||||
data = data[inds]
|
||||
|
||||
if spacing is None:
|
||||
spacing = 2.*np.std(data)/len(data)**0.5
|
||||
s2 = spacing**2
|
||||
|
||||
yvals = np.empty(len(data))
|
||||
yvals[0] = 0
|
||||
for i in range(1,len(data)):
|
||||
x = data[i] # current x value to be placed
|
||||
x0 = data[:i] # all x values already placed
|
||||
y0 = yvals[:i] # all y values already placed
|
||||
y = 0
|
||||
|
||||
dx = (x0-x)**2 # x-distance to each previous point
|
||||
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()
|
||||
|
||||
yvals[i] = y
|
||||
|
||||
return yvals[np.argsort(inds)] ## un-shuffle values before returning
|
@ -172,6 +172,8 @@ class AxisItem(GraphicsWidget):
|
||||
return asUnicode("<span style='%s'>%s</span>") % (style, s)
|
||||
|
||||
def setHeight(self, h=None):
|
||||
"""Set the height of this axis reserved for ticks and tick labels.
|
||||
The height of the axis label is automatically added."""
|
||||
if h is None:
|
||||
h = self.textHeight + max(0, self.tickLength)
|
||||
if self.label.isVisible():
|
||||
@ -182,6 +184,8 @@ class AxisItem(GraphicsWidget):
|
||||
|
||||
|
||||
def setWidth(self, w=None):
|
||||
"""Set the width of this axis reserved for ticks and tick labels.
|
||||
The width of the axis label is automatically added."""
|
||||
if w is None:
|
||||
w = max(0, self.tickLength) + 40
|
||||
if self.label.isVisible():
|
||||
|
@ -394,14 +394,17 @@ class GraphicsItem(object):
|
||||
if oldView is not None:
|
||||
#print "disconnect:", self, oldView
|
||||
oldView.sigRangeChanged.disconnect(self.viewRangeChanged)
|
||||
oldView.sigTransformChanged.disconnect(self.viewTransformChanged)
|
||||
self._connectedView = None
|
||||
|
||||
## connect to new view
|
||||
if view is not None:
|
||||
#print "connect:", self, view
|
||||
view.sigRangeChanged.connect(self.viewRangeChanged)
|
||||
view.sigTransformChanged.connect(self.viewTransformChanged)
|
||||
self._connectedView = weakref.ref(view)
|
||||
self.viewRangeChanged()
|
||||
self.viewTransformChanged()
|
||||
|
||||
## inform children that their view might have changed
|
||||
self._replaceView(oldView)
|
||||
@ -425,3 +428,9 @@ class GraphicsItem(object):
|
||||
Called whenever the view coordinates of the ViewBox containing this item have changed.
|
||||
"""
|
||||
pass
|
||||
|
||||
def viewTransformChanged(self):
|
||||
"""
|
||||
Called whenever the transformation matrix of the view has changed.
|
||||
"""
|
||||
pass
|
68
graphicsItems/LegendItem.py
Normal file
68
graphicsItems/LegendItem.py
Normal file
@ -0,0 +1,68 @@
|
||||
from .GraphicsWidget import GraphicsWidget
|
||||
from .LabelItem import LabelItem
|
||||
from ..Qt import QtGui, QtCore
|
||||
from .. import functions as fn
|
||||
|
||||
__all__ = ['LegendItem']
|
||||
|
||||
class LegendItem(GraphicsWidget):
|
||||
"""
|
||||
Displays a legend used for describing the contents of a plot.
|
||||
|
||||
Note that this item should not be added directly to a PlotItem. Instead,
|
||||
Make it a direct descendant of the PlotItem::
|
||||
|
||||
legend.setParentItem(plotItem)
|
||||
|
||||
"""
|
||||
def __init__(self, size, offset):
|
||||
GraphicsWidget.__init__(self)
|
||||
self.setFlag(self.ItemIgnoresTransformations)
|
||||
self.layout = QtGui.QGraphicsGridLayout()
|
||||
self.setLayout(self.layout)
|
||||
self.items = []
|
||||
self.size = size
|
||||
self.offset = offset
|
||||
self.setGeometry(QtCore.QRectF(self.offset[0], self.offset[1], self.size[0], self.size[1]))
|
||||
|
||||
def addItem(self, item, title):
|
||||
"""
|
||||
Add a new entry to the legend.
|
||||
|
||||
=========== ========================================================
|
||||
Arguments
|
||||
item A PlotDataItem from which the line and point style
|
||||
of the item will be determined
|
||||
title The title to display for this item. Simple HTML allowed.
|
||||
=========== ========================================================
|
||||
"""
|
||||
label = LabelItem(title)
|
||||
sample = ItemSample(item)
|
||||
row = len(self.items)
|
||||
self.items.append((sample, label))
|
||||
self.layout.addItem(sample, row, 0)
|
||||
self.layout.addItem(label, row, 1)
|
||||
|
||||
def boundingRect(self):
|
||||
return QtCore.QRectF(0, 0, self.size[0], self.size[1])
|
||||
|
||||
def paint(self, p, *args):
|
||||
p.setPen(fn.mkPen(255,255,255,100))
|
||||
p.setBrush(fn.mkBrush(100,100,100,50))
|
||||
p.drawRect(self.boundingRect())
|
||||
|
||||
|
||||
class ItemSample(GraphicsWidget):
|
||||
def __init__(self, item):
|
||||
GraphicsWidget.__init__(self)
|
||||
self.item = item
|
||||
|
||||
def boundingRect(self):
|
||||
return QtCore.QRectF(0, 0, 20, 20)
|
||||
|
||||
def paint(self, p, *args):
|
||||
p.setPen(fn.mkPen(self.item.opts['pen']))
|
||||
p.drawLine(2, 18, 18, 2)
|
||||
|
||||
|
||||
|
@ -64,6 +64,7 @@ class PlotCurveItem(GraphicsObject):
|
||||
'shadowPen': None,
|
||||
'fillLevel': None,
|
||||
'brush': None,
|
||||
'stepMode': False,
|
||||
}
|
||||
self.setClickable(kargs.get('clickable', False))
|
||||
self.setData(*args, **kargs)
|
||||
@ -223,8 +224,15 @@ class PlotCurveItem(GraphicsObject):
|
||||
|
||||
prof.mark('copy')
|
||||
|
||||
if self.xData.shape != self.yData.shape:
|
||||
raise Exception("X and Y arrays must be the same shape--got %s and %s." % (str(x.shape), str(y.shape)))
|
||||
if 'stepMode' in kargs:
|
||||
self.opts['stepMode'] = kargs['stepMode']
|
||||
|
||||
if self.opts['stepMode'] is True:
|
||||
if len(self.xData) != len(self.yData)+1: ## allow difference of 1 for step mode plots
|
||||
raise Exception("len(X) must be len(Y)+1 since stepMode=True (got %s and %s)" % (str(x.shape), str(y.shape)))
|
||||
else:
|
||||
if self.xData.shape != self.yData.shape: ## allow difference of 1 for step mode plots
|
||||
raise Exception("X and Y arrays must be the same shape--got %s and %s." % (str(x.shape), str(y.shape)))
|
||||
|
||||
self.path = None
|
||||
self.fillPath = None
|
||||
@ -267,6 +275,29 @@ class PlotCurveItem(GraphicsObject):
|
||||
## 0(i4)
|
||||
##
|
||||
## All values are big endian--pack using struct.pack('>d') or struct.pack('>i')
|
||||
|
||||
if self.opts['stepMode']:
|
||||
## each value in the x/y arrays generates 2 points.
|
||||
x2 = np.empty((len(x),2), dtype=x.dtype)
|
||||
x2[:] = x[:,np.newaxis]
|
||||
if self.opts['fillLevel'] is None:
|
||||
x = x2.reshape(x2.size)[1:-1]
|
||||
y2 = np.empty((len(y),2), dtype=y.dtype)
|
||||
y2[:] = y[:,np.newaxis]
|
||||
y = y2.reshape(y2.size)
|
||||
else:
|
||||
## If we have a fill level, add two extra points at either end
|
||||
x = x2.reshape(x2.size)
|
||||
y2 = np.empty((len(y)+2,2), dtype=y.dtype)
|
||||
y2[1:-1] = y[:,np.newaxis]
|
||||
y = y2.reshape(y2.size)[1:-1]
|
||||
y[0] = self.opts['fillLevel']
|
||||
y[-1] = self.opts['fillLevel']
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if sys.version_info[0] == 2: ## So this is disabled for python 3... why??
|
||||
n = x.shape[0]
|
||||
# create empty array, pad with extra space on either end
|
||||
@ -324,12 +355,21 @@ class PlotCurveItem(GraphicsObject):
|
||||
pixels = self.pixelVectors()
|
||||
if pixels == (None, None):
|
||||
pixels = [Point(0,0), Point(0,0)]
|
||||
xmin = x.min() - pixels[0].x() * lineWidth
|
||||
xmax = x.max() + pixels[0].x() * lineWidth
|
||||
ymin = y.min() - abs(pixels[1].y()) * lineWidth
|
||||
ymax = y.max() + abs(pixels[1].y()) * lineWidth
|
||||
|
||||
|
||||
xmin = x.min()
|
||||
xmax = x.max()
|
||||
ymin = y.min()
|
||||
ymax = y.max()
|
||||
|
||||
if self.opts['fillLevel'] is not None:
|
||||
ymin = min(ymin, self.opts['fillLevel'])
|
||||
ymax = max(ymax, self.opts['fillLevel'])
|
||||
|
||||
xmin -= pixels[0].x() * lineWidth
|
||||
xmax += pixels[0].x() * lineWidth
|
||||
ymin -= abs(pixels[1].y()) * lineWidth
|
||||
ymax += abs(pixels[1].y()) * lineWidth
|
||||
|
||||
return QtCore.QRectF(xmin, ymin, xmax-xmin, ymax-ymin)
|
||||
|
||||
def paint(self, p, opt, widget):
|
||||
|
@ -130,6 +130,8 @@ class PlotDataItem(GraphicsObject):
|
||||
'symbolBrush': (50, 50, 150),
|
||||
'pxMode': True,
|
||||
|
||||
'pointMode': None,
|
||||
|
||||
'data': None,
|
||||
}
|
||||
self.setData(*args, **kargs)
|
||||
@ -144,22 +146,30 @@ class PlotDataItem(GraphicsObject):
|
||||
return QtCore.QRectF() ## let child items handle this
|
||||
|
||||
def setAlpha(self, alpha, auto):
|
||||
if self.opts['alphaHint'] == alpha and self.opts['alphaMode'] == auto:
|
||||
return
|
||||
self.opts['alphaHint'] = alpha
|
||||
self.opts['alphaMode'] = auto
|
||||
self.setOpacity(alpha)
|
||||
#self.update()
|
||||
|
||||
def setFftMode(self, mode):
|
||||
if self.opts['fftMode'] == mode:
|
||||
return
|
||||
self.opts['fftMode'] = mode
|
||||
self.xDisp = self.yDisp = None
|
||||
self.updateItems()
|
||||
|
||||
def setLogMode(self, xMode, yMode):
|
||||
self.opts['logMode'] = (xMode, yMode)
|
||||
if self.opts['logMode'] == [xMode, yMode]:
|
||||
return
|
||||
self.opts['logMode'] = [xMode, yMode]
|
||||
self.xDisp = self.yDisp = None
|
||||
self.updateItems()
|
||||
|
||||
def setPointMode(self, mode):
|
||||
if self.opts['pointMode'] == mode:
|
||||
return
|
||||
self.opts['pointMode'] = mode
|
||||
self.update()
|
||||
|
||||
@ -193,6 +203,8 @@ class PlotDataItem(GraphicsObject):
|
||||
|
||||
def setFillBrush(self, *args, **kargs):
|
||||
brush = fn.mkBrush(*args, **kargs)
|
||||
if self.opts['fillBrush'] == brush:
|
||||
return
|
||||
self.opts['fillBrush'] = brush
|
||||
self.updateItems()
|
||||
|
||||
@ -200,16 +212,22 @@ class PlotDataItem(GraphicsObject):
|
||||
return self.setFillBrush(*args, **kargs)
|
||||
|
||||
def setFillLevel(self, level):
|
||||
if self.opts['fillLevel'] == level:
|
||||
return
|
||||
self.opts['fillLevel'] = level
|
||||
self.updateItems()
|
||||
|
||||
def setSymbol(self, symbol):
|
||||
if self.opts['symbol'] == symbol:
|
||||
return
|
||||
self.opts['symbol'] = symbol
|
||||
#self.scatter.setSymbol(symbol)
|
||||
self.updateItems()
|
||||
|
||||
def setSymbolPen(self, *args, **kargs):
|
||||
pen = fn.mkPen(*args, **kargs)
|
||||
if self.opts['symbolPen'] == pen:
|
||||
return
|
||||
self.opts['symbolPen'] = pen
|
||||
#self.scatter.setSymbolPen(pen)
|
||||
self.updateItems()
|
||||
@ -218,21 +236,26 @@ class PlotDataItem(GraphicsObject):
|
||||
|
||||
def setSymbolBrush(self, *args, **kargs):
|
||||
brush = fn.mkBrush(*args, **kargs)
|
||||
if self.opts['symbolBrush'] == brush:
|
||||
return
|
||||
self.opts['symbolBrush'] = brush
|
||||
#self.scatter.setSymbolBrush(brush)
|
||||
self.updateItems()
|
||||
|
||||
|
||||
def setSymbolSize(self, size):
|
||||
if self.opts['symbolSize'] == size:
|
||||
return
|
||||
self.opts['symbolSize'] = size
|
||||
#self.scatter.setSymbolSize(symbolSize)
|
||||
self.updateItems()
|
||||
|
||||
def setDownsampling(self, ds):
|
||||
if self.opts['downsample'] != ds:
|
||||
self.opts['downsample'] = ds
|
||||
self.xDisp = self.yDisp = None
|
||||
self.updateItems()
|
||||
if self.opts['downsample'] == ds:
|
||||
return
|
||||
self.opts['downsample'] = ds
|
||||
self.xDisp = self.yDisp = None
|
||||
self.updateItems()
|
||||
|
||||
def setData(self, *args, **kargs):
|
||||
"""
|
||||
@ -436,9 +459,12 @@ class PlotDataItem(GraphicsObject):
|
||||
and max)
|
||||
=============== =============================================================
|
||||
"""
|
||||
if frac <= 0.0:
|
||||
raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac))
|
||||
|
||||
(x, y) = self.getData()
|
||||
if x is None or len(x) == 0:
|
||||
return (0, 0)
|
||||
return None
|
||||
|
||||
if ax == 0:
|
||||
d = x
|
||||
@ -450,14 +476,15 @@ class PlotDataItem(GraphicsObject):
|
||||
if orthoRange is not None:
|
||||
mask = (d2 >= orthoRange[0]) * (d2 <= orthoRange[1])
|
||||
d = d[mask]
|
||||
d2 = d2[mask]
|
||||
#d2 = d2[mask]
|
||||
|
||||
if frac >= 1.0:
|
||||
return (np.min(d), np.max(d))
|
||||
elif frac <= 0.0:
|
||||
raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac))
|
||||
if len(d) > 0:
|
||||
if frac >= 1.0:
|
||||
return (np.min(d), np.max(d))
|
||||
else:
|
||||
return (scipy.stats.scoreatpercentile(d, 50 - (frac * 50)), scipy.stats.scoreatpercentile(d, 50 + (frac * 50)))
|
||||
else:
|
||||
return (scipy.stats.scoreatpercentile(d, 50 - (frac * 50)), scipy.stats.scoreatpercentile(d, 50 + (frac * 50)))
|
||||
return None
|
||||
|
||||
|
||||
def clear(self):
|
||||
|
@ -1,4 +1,4 @@
|
||||
from pyqtgraph.Qt import QtGui, QtCore
|
||||
from pyqtgraph.Qt import QtGui, QtCore, USE_PYSIDE
|
||||
from pyqtgraph.Point import Point
|
||||
import pyqtgraph.functions as fn
|
||||
from .GraphicsItem import GraphicsItem
|
||||
@ -32,26 +32,171 @@ for k, c in coords.items():
|
||||
Symbols[k].lineTo(x, y)
|
||||
Symbols[k].closeSubpath()
|
||||
|
||||
|
||||
def drawSymbol(painter, symbol, size, pen, brush):
|
||||
painter.scale(size, size)
|
||||
painter.setPen(pen)
|
||||
painter.setBrush(brush)
|
||||
if isinstance(symbol, basestring):
|
||||
symbol = Symbols[symbol]
|
||||
if np.isscalar(symbol):
|
||||
symbol = Symbols.values()[symbol % len(Symbols)]
|
||||
painter.drawPath(symbol)
|
||||
|
||||
def makeSymbolPixmap(size, pen, brush, symbol):
|
||||
|
||||
def renderSymbol(symbol, size, pen, brush, device=None):
|
||||
"""
|
||||
Render a symbol specification to QImage.
|
||||
Symbol may be either a QPainterPath or one of the keys in the Symbols dict.
|
||||
If *device* is None, a new QPixmap will be returned. Otherwise,
|
||||
the symbol will be rendered into the device specified (See QPainter documentation
|
||||
for more information).
|
||||
"""
|
||||
## see if this pixmap is already cached
|
||||
#global SymbolPixmapCache
|
||||
#key = (symbol, size, fn.colorTuple(pen.color()), pen.width(), pen.style(), fn.colorTuple(brush.color()))
|
||||
#if key in SymbolPixmapCache:
|
||||
#return SymbolPixmapCache[key]
|
||||
|
||||
## Render a spot with the given parameters to a pixmap
|
||||
penPxWidth = max(np.ceil(pen.width()), 1)
|
||||
image = QtGui.QImage(int(size+penPxWidth), int(size+penPxWidth), QtGui.QImage.Format_ARGB32_Premultiplied)
|
||||
image = QtGui.QImage(int(size+penPxWidth), int(size+penPxWidth), QtGui.QImage.Format_ARGB32)
|
||||
image.fill(0)
|
||||
p = QtGui.QPainter(image)
|
||||
p.setRenderHint(p.Antialiasing)
|
||||
p.translate(image.width()*0.5, image.height()*0.5)
|
||||
p.scale(size, size)
|
||||
p.setPen(pen)
|
||||
p.setBrush(brush)
|
||||
if isinstance(symbol, basestring):
|
||||
symbol = Symbols[symbol]
|
||||
p.drawPath(symbol)
|
||||
drawSymbol(p, symbol, size, pen, brush)
|
||||
p.end()
|
||||
return QtGui.QPixmap(image)
|
||||
return image
|
||||
#pixmap = QtGui.QPixmap(image)
|
||||
#SymbolPixmapCache[key] = pixmap
|
||||
#return pixmap
|
||||
|
||||
def makeSymbolPixmap(size, pen, brush, symbol):
|
||||
## deprecated
|
||||
img = renderSymbol(symbol, size, pen, brush)
|
||||
return QtGui.QPixmap(img)
|
||||
|
||||
class SymbolAtlas:
|
||||
"""
|
||||
Used to efficiently construct a single QPixmap containing all rendered symbols
|
||||
for a ScatterPlotItem. This is required for fragment rendering.
|
||||
|
||||
Use example:
|
||||
atlas = SymbolAtlas()
|
||||
sc1 = atlas.getSymbolCoords('o', 5, QPen(..), QBrush(..))
|
||||
sc2 = atlas.getSymbolCoords('t', 10, QPen(..), QBrush(..))
|
||||
pm = atlas.getAtlas()
|
||||
|
||||
"""
|
||||
class SymbolCoords(list): ## needed because lists are not allowed in weak references.
|
||||
pass
|
||||
|
||||
def __init__(self):
|
||||
# symbol key : [x, y, w, h] atlas coordinates
|
||||
# note that the coordinate list will always be the same list object as
|
||||
# long as the symbol is in the atlas, but the coordinates may
|
||||
# change if the atlas is rebuilt.
|
||||
# weak value; if all external refs to this list disappear,
|
||||
# the symbol will be forgotten.
|
||||
self.symbolMap = weakref.WeakValueDictionary()
|
||||
|
||||
self.atlasData = None # numpy array of atlas image
|
||||
self.atlas = None # atlas as QPixmap
|
||||
self.atlasValid = False
|
||||
|
||||
def getSymbolCoords(self, opts):
|
||||
"""
|
||||
Given a list of spot records, return an object representing the coordinates of that symbol within the atlas
|
||||
"""
|
||||
coords = np.empty(len(opts), dtype=object)
|
||||
for i, rec in enumerate(opts):
|
||||
symbol, size, pen, brush = rec['symbol'], rec['size'], rec['pen'], rec['brush']
|
||||
pen = fn.mkPen(pen) if not isinstance(pen, QtGui.QPen) else pen
|
||||
brush = fn.mkBrush(brush) if not isinstance(pen, QtGui.QBrush) else brush
|
||||
key = (symbol, size, fn.colorTuple(pen.color()), pen.width(), pen.style(), fn.colorTuple(brush.color()))
|
||||
if key not in self.symbolMap:
|
||||
newCoords = SymbolAtlas.SymbolCoords()
|
||||
self.symbolMap[key] = newCoords
|
||||
self.atlasValid = False
|
||||
#try:
|
||||
#self.addToAtlas(key) ## squeeze this into the atlas if there is room
|
||||
#except:
|
||||
#self.buildAtlas() ## otherwise, we need to rebuild
|
||||
|
||||
coords[i] = self.symbolMap[key]
|
||||
return coords
|
||||
|
||||
def buildAtlas(self):
|
||||
# get rendered array for all symbols, keep track of avg/max width
|
||||
rendered = {}
|
||||
avgWidth = 0.0
|
||||
maxWidth = 0
|
||||
images = []
|
||||
for key, coords in self.symbolMap.items():
|
||||
if len(coords) == 0:
|
||||
pen = fn.mkPen(color=key[2], width=key[3], style=key[4])
|
||||
brush = fn.mkBrush(color=key[5])
|
||||
img = renderSymbol(key[0], key[1], pen, brush)
|
||||
images.append(img) ## we only need this to prevent the images being garbage collected immediately
|
||||
arr = fn.imageToArray(img, copy=False, transpose=False)
|
||||
else:
|
||||
(x,y,w,h) = self.symbolMap[key]
|
||||
arr = self.atlasData[x:x+w, y:y+w]
|
||||
rendered[key] = arr
|
||||
w = arr.shape[0]
|
||||
avgWidth += w
|
||||
maxWidth = max(maxWidth, w)
|
||||
|
||||
nSymbols = len(rendered)
|
||||
if nSymbols > 0:
|
||||
avgWidth /= nSymbols
|
||||
width = max(maxWidth, avgWidth * (nSymbols**0.5))
|
||||
else:
|
||||
avgWidth = 0
|
||||
width = 0
|
||||
|
||||
# sort symbols by height
|
||||
symbols = sorted(rendered.keys(), key=lambda x: rendered[x].shape[1], reverse=True)
|
||||
|
||||
self.atlasRows = []
|
||||
|
||||
x = width
|
||||
y = 0
|
||||
rowheight = 0
|
||||
for key in symbols:
|
||||
arr = rendered[key]
|
||||
w,h = arr.shape[:2]
|
||||
if x+w > width:
|
||||
y += rowheight
|
||||
x = 0
|
||||
rowheight = h
|
||||
self.atlasRows.append([y, rowheight, 0])
|
||||
self.symbolMap[key][:] = x, y, w, h
|
||||
x += w
|
||||
self.atlasRows[-1][2] = x
|
||||
height = y + rowheight
|
||||
|
||||
self.atlasData = np.zeros((width, height, 4), dtype=np.ubyte)
|
||||
for key in symbols:
|
||||
x, y, w, h = self.symbolMap[key]
|
||||
self.atlasData[x:x+w, y:y+h] = rendered[key]
|
||||
self.atlas = None
|
||||
self.atlasValid = True
|
||||
|
||||
def getAtlas(self):
|
||||
if not self.atlasValid:
|
||||
self.buildAtlas()
|
||||
if self.atlas is None:
|
||||
if len(self.atlasData) == 0:
|
||||
return QtGui.QPixmap(0,0)
|
||||
img = fn.makeQImage(self.atlasData, copy=False, transpose=False)
|
||||
self.atlas = QtGui.QPixmap(img)
|
||||
return self.atlas
|
||||
|
||||
|
||||
|
||||
|
||||
class ScatterPlotItem(GraphicsObject):
|
||||
"""
|
||||
Displays a set of x/y points. Instances of this class are created
|
||||
@ -79,13 +224,16 @@ class ScatterPlotItem(GraphicsObject):
|
||||
"""
|
||||
prof = debug.Profiler('ScatterPlotItem.__init__', disabled=True)
|
||||
GraphicsObject.__init__(self)
|
||||
self.setFlag(self.ItemHasNoContents, True)
|
||||
self.data = np.empty(0, dtype=[('x', float), ('y', float), ('size', float), ('symbol', object), ('pen', object), ('brush', object), ('item', object), ('data', object)])
|
||||
|
||||
self.picture = None # QPicture used for rendering when pxmode==False
|
||||
self.fragments = None # fragment specification for pxmode; updated every time the view changes.
|
||||
self.fragmentAtlas = SymbolAtlas()
|
||||
|
||||
self.data = np.empty(0, dtype=[('x', float), ('y', float), ('size', float), ('symbol', object), ('pen', object), ('brush', object), ('data', object), ('fragCoords', object), ('item', object)])
|
||||
self.bounds = [None, None] ## caches data bounds
|
||||
self._maxSpotWidth = 0 ## maximum size of the scale-variant portion of all spots
|
||||
self._maxSpotPxWidth = 0 ## maximum size of the scale-invariant portion of all spots
|
||||
self._spotPixmap = None
|
||||
self.opts = {'pxMode': True}
|
||||
self.opts = {'pxMode': True, 'useCache': True} ## If useCache is False, symbols are re-drawn on every paint.
|
||||
|
||||
self.setPen(200,200,200, update=False)
|
||||
self.setBrush(100,100,150, update=False)
|
||||
@ -96,6 +244,8 @@ class ScatterPlotItem(GraphicsObject):
|
||||
prof.mark('setData')
|
||||
prof.finish()
|
||||
|
||||
#self.setCacheMode(self.DeviceCoordinateCache)
|
||||
|
||||
def setData(self, *args, **kargs):
|
||||
"""
|
||||
**Ordered Arguments:**
|
||||
@ -130,6 +280,7 @@ class ScatterPlotItem(GraphicsObject):
|
||||
*identical* *Deprecated*. This functionality is handled automatically now.
|
||||
====================== ===============================================================================================
|
||||
"""
|
||||
oldData = self.data ## this causes cached pixmaps to be preserved while new data is registered.
|
||||
self.clear() ## clear out all old data
|
||||
self.addPoints(*args, **kargs)
|
||||
|
||||
@ -183,8 +334,8 @@ class ScatterPlotItem(GraphicsObject):
|
||||
## note that np.empty initializes object fields to None and string fields to ''
|
||||
|
||||
self.data[:len(oldData)] = oldData
|
||||
for i in range(len(oldData)):
|
||||
oldData[i]['item']._data = self.data[i] ## Make sure items have proper reference to new array
|
||||
#for i in range(len(oldData)):
|
||||
#oldData[i]['item']._data = self.data[i] ## Make sure items have proper reference to new array
|
||||
|
||||
newData = self.data[len(oldData):]
|
||||
newData['size'] = -1 ## indicates to use default size
|
||||
@ -217,7 +368,7 @@ class ScatterPlotItem(GraphicsObject):
|
||||
newData['y'] = kargs['y']
|
||||
|
||||
if 'pxMode' in kargs:
|
||||
self.setPxMode(kargs['pxMode'], update=False)
|
||||
self.setPxMode(kargs['pxMode'])
|
||||
|
||||
## Set any extra parameters provided in keyword arguments
|
||||
for k in ['pen', 'brush', 'symbol', 'size']:
|
||||
@ -228,12 +379,18 @@ class ScatterPlotItem(GraphicsObject):
|
||||
if 'data' in kargs:
|
||||
self.setPointData(kargs['data'], dataSet=newData)
|
||||
|
||||
#self.updateSpots()
|
||||
self.prepareGeometryChange()
|
||||
self.bounds = [None, None]
|
||||
self.generateSpotItems()
|
||||
self.invalidate()
|
||||
self.updateSpots(newData)
|
||||
self.sigPlotChanged.emit(self)
|
||||
|
||||
def invalidate(self):
|
||||
## clear any cached drawing state
|
||||
self.picture = None
|
||||
self.fragments = None
|
||||
self.update()
|
||||
|
||||
def getData(self):
|
||||
return self.data['x'], self.data['y']
|
||||
|
||||
@ -263,8 +420,8 @@ class ScatterPlotItem(GraphicsObject):
|
||||
dataSet['pen'] = pens
|
||||
else:
|
||||
self.opts['pen'] = fn.mkPen(*args, **kargs)
|
||||
self._spotPixmap = None
|
||||
|
||||
dataSet['fragCoords'] = None
|
||||
if update:
|
||||
self.updateSpots(dataSet)
|
||||
|
||||
@ -285,8 +442,9 @@ class ScatterPlotItem(GraphicsObject):
|
||||
dataSet['brush'] = brushes
|
||||
else:
|
||||
self.opts['brush'] = fn.mkBrush(*args, **kargs)
|
||||
self._spotPixmap = None
|
||||
#self._spotPixmap = None
|
||||
|
||||
dataSet['fragCoords'] = None
|
||||
if update:
|
||||
self.updateSpots(dataSet)
|
||||
|
||||
@ -307,6 +465,7 @@ class ScatterPlotItem(GraphicsObject):
|
||||
self.opts['symbol'] = symbol
|
||||
self._spotPixmap = None
|
||||
|
||||
dataSet['fragCoords'] = None
|
||||
if update:
|
||||
self.updateSpots(dataSet)
|
||||
|
||||
@ -327,6 +486,7 @@ class ScatterPlotItem(GraphicsObject):
|
||||
self.opts['size'] = size
|
||||
self._spotPixmap = None
|
||||
|
||||
dataSet['fragCoords'] = None
|
||||
if update:
|
||||
self.updateSpots(dataSet)
|
||||
|
||||
@ -346,34 +506,71 @@ class ScatterPlotItem(GraphicsObject):
|
||||
else:
|
||||
dataSet['data'] = data
|
||||
|
||||
def setPxMode(self, mode, update=True):
|
||||
def setPxMode(self, mode):
|
||||
if self.opts['pxMode'] == mode:
|
||||
return
|
||||
|
||||
self.opts['pxMode'] = mode
|
||||
self.clearItems()
|
||||
if update:
|
||||
self.generateSpotItems()
|
||||
self.invalidate()
|
||||
|
||||
def updateSpots(self, dataSet=None):
|
||||
if dataSet is None:
|
||||
dataSet = self.data
|
||||
self._maxSpotWidth = 0
|
||||
self._maxSpotPxWidth = 0
|
||||
for spot in dataSet['item']:
|
||||
spot.updateItem()
|
||||
invalidate = False
|
||||
self.measureSpotSizes(dataSet)
|
||||
if self.opts['pxMode']:
|
||||
mask = np.equal(dataSet['fragCoords'], None)
|
||||
if np.any(mask):
|
||||
invalidate = True
|
||||
opts = self.getSpotOpts(dataSet[mask])
|
||||
coords = self.fragmentAtlas.getSymbolCoords(opts)
|
||||
dataSet['fragCoords'][mask] = coords
|
||||
|
||||
#for rec in dataSet:
|
||||
#if rec['fragCoords'] is None:
|
||||
#invalidate = True
|
||||
#rec['fragCoords'] = self.fragmentAtlas.getSymbolCoords(*self.getSpotOpts(rec))
|
||||
if invalidate:
|
||||
self.invalidate()
|
||||
|
||||
def getSpotOpts(self, recs):
|
||||
if recs.ndim == 0:
|
||||
rec = recs
|
||||
symbol = rec['symbol']
|
||||
if symbol is None:
|
||||
symbol = self.opts['symbol']
|
||||
size = rec['size']
|
||||
if size < 0:
|
||||
size = self.opts['size']
|
||||
pen = rec['pen']
|
||||
if pen is None:
|
||||
pen = self.opts['pen']
|
||||
brush = rec['brush']
|
||||
if brush is None:
|
||||
brush = self.opts['brush']
|
||||
return (symbol, size, fn.mkPen(pen), fn.mkBrush(brush))
|
||||
else:
|
||||
recs = recs.copy()
|
||||
recs['symbol'][np.equal(recs['symbol'], None)] = self.opts['symbol']
|
||||
recs['size'][np.equal(recs['size'], -1)] = self.opts['size']
|
||||
recs['pen'][np.equal(recs['pen'], None)] = fn.mkPen(self.opts['pen'])
|
||||
recs['brush'][np.equal(recs['brush'], None)] = fn.mkBrush(self.opts['brush'])
|
||||
return recs
|
||||
|
||||
|
||||
|
||||
def measureSpotSizes(self, dataSet):
|
||||
for spot in dataSet['item']:
|
||||
for rec in dataSet:
|
||||
## keep track of the maximum spot size and pixel size
|
||||
symbol, size, pen, brush = self.getSpotOpts(rec)
|
||||
width = 0
|
||||
pxWidth = 0
|
||||
pen = spot.pen()
|
||||
if self.opts['pxMode']:
|
||||
pxWidth = spot.size() + pen.width()
|
||||
pxWidth = size + pen.width()
|
||||
else:
|
||||
width = spot.size()
|
||||
width = size
|
||||
if pen.isCosmetic():
|
||||
pxWidth += pen.width()
|
||||
else:
|
||||
@ -385,20 +582,11 @@ class ScatterPlotItem(GraphicsObject):
|
||||
|
||||
def clear(self):
|
||||
"""Remove all spots from the scatter plot"""
|
||||
self.clearItems()
|
||||
#self.clearItems()
|
||||
self.data = np.empty(0, dtype=self.data.dtype)
|
||||
self.bounds = [None, None]
|
||||
self.invalidate()
|
||||
|
||||
def clearItems(self):
|
||||
for i in self.data['item']:
|
||||
if i is None:
|
||||
continue
|
||||
i.setParentItem(None)
|
||||
s = i.scene()
|
||||
if s is not None:
|
||||
s.removeItem(i)
|
||||
self.data['item'] = None
|
||||
|
||||
def dataBounds(self, ax, frac=1.0, orthoRange=None):
|
||||
if frac >= 1.0 and self.bounds[ax] is not None:
|
||||
return self.bounds[ax]
|
||||
@ -436,28 +624,12 @@ class ScatterPlotItem(GraphicsObject):
|
||||
else:
|
||||
return (scipy.stats.scoreatpercentile(d, 50 - (frac * 50)), scipy.stats.scoreatpercentile(d, 50 + (frac * 50)))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def generateSpotItems(self):
|
||||
if self.opts['pxMode']:
|
||||
for rec in self.data:
|
||||
if rec['item'] is None:
|
||||
rec['item'] = PixmapSpotItem(rec, self)
|
||||
else:
|
||||
for rec in self.data:
|
||||
if rec['item'] is None:
|
||||
rec['item'] = PathSpotItem(rec, self)
|
||||
self.measureSpotSizes(self.data)
|
||||
self.sigPlotChanged.emit(self)
|
||||
|
||||
def defaultSpotPixmap(self):
|
||||
## Return the default spot pixmap
|
||||
if self._spotPixmap is None:
|
||||
self._spotPixmap = makeSymbolPixmap(size=self.opts['size'], brush=self.opts['brush'], pen=self.opts['pen'], symbol=self.opts['symbol'])
|
||||
return self._spotPixmap
|
||||
#def defaultSpotPixmap(self):
|
||||
### Return the default spot pixmap
|
||||
#if self._spotPixmap is None:
|
||||
#self._spotPixmap = makeSymbolPixmap(size=self.opts['size'], brush=self.opts['brush'], pen=self.opts['pen'], symbol=self.opts['symbol'])
|
||||
#return self._spotPixmap
|
||||
|
||||
def boundingRect(self):
|
||||
(xmn, xmx) = self.dataBounds(ax=0)
|
||||
@ -470,19 +642,72 @@ class ScatterPlotItem(GraphicsObject):
|
||||
ymx = 0
|
||||
return QtCore.QRectF(xmn, ymn, xmx-xmn, ymx-ymn)
|
||||
|
||||
def viewRangeChanged(self):
|
||||
def viewTransformChanged(self):
|
||||
self.prepareGeometryChange()
|
||||
GraphicsObject.viewRangeChanged(self)
|
||||
GraphicsObject.viewTransformChanged(self)
|
||||
self.bounds = [None, None]
|
||||
self.fragments = None
|
||||
|
||||
def generateFragments(self):
|
||||
tr = self.deviceTransform()
|
||||
if tr is None:
|
||||
return
|
||||
pts = np.empty((2,len(self.data['x'])))
|
||||
pts[0] = self.data['x']
|
||||
pts[1] = self.data['y']
|
||||
pts = fn.transformCoordinates(tr, pts)
|
||||
self.fragments = []
|
||||
for i in xrange(len(self.data)):
|
||||
rec = self.data[i]
|
||||
pos = QtCore.QPointF(pts[0,i], pts[1,i])
|
||||
x,y,w,h = rec['fragCoords']
|
||||
rect = QtCore.QRectF(y, x, h, w)
|
||||
self.fragments.append(QtGui.QPainter.PixmapFragment.create(pos, rect))
|
||||
|
||||
def paint(self, p, *args):
|
||||
## NOTE: self.paint is disabled by this line in __init__:
|
||||
## self.setFlag(self.ItemHasNoContents, True)
|
||||
p.setPen(fn.mkPen('r'))
|
||||
p.drawRect(self.boundingRect())
|
||||
#p.setPen(fn.mkPen('r'))
|
||||
#p.drawRect(self.boundingRect())
|
||||
if self.opts['pxMode']:
|
||||
atlas = self.fragmentAtlas.getAtlas()
|
||||
#arr = fn.imageToArray(atlas.toImage(), copy=True)
|
||||
#if hasattr(self, 'lastAtlas'):
|
||||
#if np.any(self.lastAtlas != arr):
|
||||
#print "Atlas changed:", arr
|
||||
#self.lastAtlas = arr
|
||||
|
||||
if self.fragments is None:
|
||||
self.updateSpots()
|
||||
self.generateFragments()
|
||||
|
||||
p.resetTransform()
|
||||
|
||||
if not USE_PYSIDE and self.opts['useCache']:
|
||||
p.drawPixmapFragments(self.fragments, atlas)
|
||||
else:
|
||||
for i in range(len(self.data)):
|
||||
rec = self.data[i]
|
||||
frag = self.fragments[i]
|
||||
p.resetTransform()
|
||||
p.translate(frag.x, frag.y)
|
||||
drawSymbol(p, *self.getSpotOpts(rec))
|
||||
else:
|
||||
if self.picture is None:
|
||||
self.picture = QtGui.QPicture()
|
||||
p2 = QtGui.QPainter(self.picture)
|
||||
for rec in self.data:
|
||||
|
||||
p2.resetTransform()
|
||||
p2.translate(rec['x'], rec['y'])
|
||||
drawSymbol(p2, *self.getSpotOpts(rec))
|
||||
p2.end()
|
||||
|
||||
self.picture.play(p)
|
||||
|
||||
|
||||
def points(self):
|
||||
for rec in self.data:
|
||||
if rec['item'] is None:
|
||||
rec['item'] = SpotItem(rec, self)
|
||||
return self.data['item']
|
||||
|
||||
def pointsAt(self, pos):
|
||||
@ -506,8 +731,8 @@ class ScatterPlotItem(GraphicsObject):
|
||||
#else:
|
||||
#print "No hit:", (x, y), (sx, sy)
|
||||
#print " ", (sx-s2x, sy-s2y), (sx+s2x, sy+s2y)
|
||||
pts.sort(lambda a,b: cmp(b.zValue(), a.zValue()))
|
||||
return pts
|
||||
#pts.sort(lambda a,b: cmp(b.zValue(), a.zValue()))
|
||||
return pts[::-1]
|
||||
|
||||
|
||||
def mouseClickEvent(self, ev):
|
||||
@ -524,7 +749,7 @@ class ScatterPlotItem(GraphicsObject):
|
||||
ev.ignore()
|
||||
|
||||
|
||||
class SpotItem(GraphicsItem):
|
||||
class SpotItem(object):
|
||||
"""
|
||||
Class referring to individual spots in a scatter plot.
|
||||
These can be retrieved by calling ScatterPlotItem.points() or
|
||||
@ -532,14 +757,12 @@ class SpotItem(GraphicsItem):
|
||||
"""
|
||||
|
||||
def __init__(self, data, plot):
|
||||
GraphicsItem.__init__(self, register=False)
|
||||
#GraphicsItem.__init__(self, register=False)
|
||||
self._data = data
|
||||
self._plot = plot
|
||||
#self._viewBox = None
|
||||
#self._viewWidget = None
|
||||
self.setParentItem(plot)
|
||||
self.setPos(QtCore.QPointF(data['x'], data['y']))
|
||||
self.updateItem()
|
||||
#self.setParentItem(plot)
|
||||
#self.setPos(QtCore.QPointF(data['x'], data['y']))
|
||||
#self.updateItem()
|
||||
|
||||
def data(self):
|
||||
"""Return the user data associated with this spot."""
|
||||
@ -553,6 +776,12 @@ class SpotItem(GraphicsItem):
|
||||
else:
|
||||
return self._data['size']
|
||||
|
||||
def pos(self):
|
||||
return Point(self._data['x'], self._data['y'])
|
||||
|
||||
def viewPos(self):
|
||||
return self._plot.mapToView(self.pos())
|
||||
|
||||
def setSize(self, size):
|
||||
"""Set the size of this spot.
|
||||
If the size is set to -1, then the ScatterPlotItem's default size
|
||||
@ -618,37 +847,41 @@ class SpotItem(GraphicsItem):
|
||||
"""Set the user-data associated with this spot"""
|
||||
self._data['data'] = data
|
||||
|
||||
|
||||
class PixmapSpotItem(SpotItem, QtGui.QGraphicsPixmapItem):
|
||||
def __init__(self, data, plot):
|
||||
QtGui.QGraphicsPixmapItem.__init__(self)
|
||||
self.setFlags(self.flags() | self.ItemIgnoresTransformations)
|
||||
SpotItem.__init__(self, data, plot)
|
||||
|
||||
def setPixmap(self, pixmap):
|
||||
QtGui.QGraphicsPixmapItem.setPixmap(self, pixmap)
|
||||
self.setOffset(-pixmap.width()/2.+0.5, -pixmap.height()/2.)
|
||||
|
||||
def updateItem(self):
|
||||
symbolOpts = (self._data['pen'], self._data['brush'], self._data['size'], self._data['symbol'])
|
||||
self._data['fragCoords'] = None
|
||||
self._plot.updateSpots([self._data])
|
||||
self._plot.invalidate()
|
||||
|
||||
#class PixmapSpotItem(SpotItem, QtGui.QGraphicsPixmapItem):
|
||||
#def __init__(self, data, plot):
|
||||
#QtGui.QGraphicsPixmapItem.__init__(self)
|
||||
#self.setFlags(self.flags() | self.ItemIgnoresTransformations)
|
||||
#SpotItem.__init__(self, data, plot)
|
||||
|
||||
#def setPixmap(self, pixmap):
|
||||
#QtGui.QGraphicsPixmapItem.setPixmap(self, pixmap)
|
||||
#self.setOffset(-pixmap.width()/2.+0.5, -pixmap.height()/2.)
|
||||
|
||||
#def updateItem(self):
|
||||
#symbolOpts = (self._data['pen'], self._data['brush'], self._data['size'], self._data['symbol'])
|
||||
|
||||
## If all symbol options are default, use default pixmap
|
||||
if symbolOpts == (None, None, -1, ''):
|
||||
pixmap = self._plot.defaultSpotPixmap()
|
||||
else:
|
||||
pixmap = makeSymbolPixmap(size=self.size(), pen=self.pen(), brush=self.brush(), symbol=self.symbol())
|
||||
self.setPixmap(pixmap)
|
||||
### If all symbol options are default, use default pixmap
|
||||
#if symbolOpts == (None, None, -1, ''):
|
||||
#pixmap = self._plot.defaultSpotPixmap()
|
||||
#else:
|
||||
#pixmap = makeSymbolPixmap(size=self.size(), pen=self.pen(), brush=self.brush(), symbol=self.symbol())
|
||||
#self.setPixmap(pixmap)
|
||||
|
||||
|
||||
class PathSpotItem(SpotItem, QtGui.QGraphicsPathItem):
|
||||
def __init__(self, data, plot):
|
||||
QtGui.QGraphicsPathItem.__init__(self)
|
||||
SpotItem.__init__(self, data, plot)
|
||||
#class PathSpotItem(SpotItem, QtGui.QGraphicsPathItem):
|
||||
#def __init__(self, data, plot):
|
||||
#QtGui.QGraphicsPathItem.__init__(self)
|
||||
#SpotItem.__init__(self, data, plot)
|
||||
|
||||
def updateItem(self):
|
||||
QtGui.QGraphicsPathItem.setPath(self, Symbols[self.symbol()])
|
||||
QtGui.QGraphicsPathItem.setPen(self, self.pen())
|
||||
QtGui.QGraphicsPathItem.setBrush(self, self.brush())
|
||||
size = self.size()
|
||||
self.resetTransform()
|
||||
self.scale(size, size)
|
||||
#def updateItem(self):
|
||||
#QtGui.QGraphicsPathItem.setPath(self, Symbols[self.symbol()])
|
||||
#QtGui.QGraphicsPathItem.setPen(self, self.pen())
|
||||
#QtGui.QGraphicsPathItem.setBrush(self, self.brush())
|
||||
#size = self.size()
|
||||
#self.resetTransform()
|
||||
#self.scale(size, size)
|
||||
|
@ -48,6 +48,7 @@ class ViewBox(GraphicsWidget):
|
||||
sigRangeChanged = QtCore.Signal(object, object)
|
||||
#sigActionPositionChanged = QtCore.Signal(object)
|
||||
sigStateChanged = QtCore.Signal(object)
|
||||
sigTransformChanged = QtCore.Signal(object)
|
||||
|
||||
## mouse modes
|
||||
PanMode = 3
|
||||
@ -307,10 +308,6 @@ class ViewBox(GraphicsWidget):
|
||||
print("make qrectf failed:", self.state['viewRange'])
|
||||
raise
|
||||
|
||||
#def viewportTransform(self):
|
||||
##return self.itemTransform(self.childGroup)[0]
|
||||
#return self.childGroup.itemTransform(self)[0]
|
||||
|
||||
def targetRange(self):
|
||||
return [x[:] for x in self.state['targetRange']] ## return copy
|
||||
|
||||
@ -554,10 +551,10 @@ class ViewBox(GraphicsWidget):
|
||||
## Make corrections to range
|
||||
xr = childRange[ax]
|
||||
if xr is not None:
|
||||
if self.state['autoPan'][0]:
|
||||
if self.state['autoPan'][ax]:
|
||||
x = sum(xr) * 0.5
|
||||
#x = childRect.center().x()
|
||||
w2 = (targetRect[0][1]-targetRect[0][0]) / 2.
|
||||
w2 = (targetRect[ax][1]-targetRect[ax][0]) / 2.
|
||||
#childRect.setLeft(x-w2)
|
||||
#childRect.setRight(x+w2)
|
||||
childRange[ax] = [x-w2, x+w2]
|
||||
@ -1127,29 +1124,15 @@ class ViewBox(GraphicsWidget):
|
||||
m = QtGui.QTransform()
|
||||
|
||||
## First center the viewport at 0
|
||||
#self.childGroup.resetTransform()
|
||||
#self.resetTransform()
|
||||
#center = self.transform().inverted()[0].map(bounds.center())
|
||||
center = bounds.center()
|
||||
#print " transform to center:", center
|
||||
#if self.state['yInverted']:
|
||||
#m.translate(center.x(), -center.y())
|
||||
#print " inverted; translate", center.x(), center.y()
|
||||
#else:
|
||||
m.translate(center.x(), center.y())
|
||||
#print " not inverted; translate", center.x(), -center.y()
|
||||
|
||||
## Now scale and translate properly
|
||||
m.scale(scale[0], scale[1])
|
||||
st = Point(vr.center())
|
||||
#st = translate
|
||||
m.translate(-st[0], -st[1])
|
||||
|
||||
self.childGroup.setTransform(m)
|
||||
#self.setTransform(m)
|
||||
#self.prepareGeometryChange()
|
||||
|
||||
#self.currentScale = scale
|
||||
|
||||
if changed[0]:
|
||||
self.sigXRangeChanged.emit(self, tuple(self.state['viewRange'][0]))
|
||||
@ -1157,6 +1140,8 @@ class ViewBox(GraphicsWidget):
|
||||
self.sigYRangeChanged.emit(self, tuple(self.state['viewRange'][1]))
|
||||
if any(changed):
|
||||
self.sigRangeChanged.emit(self, self.state['viewRange'])
|
||||
|
||||
self.sigTransformChanged.emit(self)
|
||||
|
||||
def paint(self, p, opt, widget):
|
||||
if self.border is not None:
|
||||
@ -1165,20 +1150,6 @@ class ViewBox(GraphicsWidget):
|
||||
#p.fillRect(bounds, QtGui.QColor(0, 0, 0))
|
||||
p.drawPath(bounds)
|
||||
|
||||
#def saveSvg(self):
|
||||
#pass
|
||||
|
||||
#def saveImage(self):
|
||||
#pass
|
||||
|
||||
#def savePrint(self):
|
||||
#printer = QtGui.QPrinter()
|
||||
#if QtGui.QPrintDialog(printer).exec_() == QtGui.QDialog.Accepted:
|
||||
#p = QtGui.QPainter(printer)
|
||||
#p.setRenderHint(p.Antialiasing)
|
||||
#self.scene().render(p)
|
||||
#p.end()
|
||||
|
||||
def updateBackground(self):
|
||||
bg = self.state['background']
|
||||
if bg is None:
|
||||
|
@ -21,13 +21,13 @@ def mkQApp():
|
||||
class GraphicsWindow(GraphicsLayoutWidget):
|
||||
def __init__(self, title=None, size=(800,600), **kargs):
|
||||
mkQApp()
|
||||
self.win = QtGui.QMainWindow()
|
||||
#self.win = QtGui.QMainWindow()
|
||||
GraphicsLayoutWidget.__init__(self, **kargs)
|
||||
self.win.setCentralWidget(self)
|
||||
self.win.resize(*size)
|
||||
#self.win.setCentralWidget(self)
|
||||
self.resize(*size)
|
||||
if title is not None:
|
||||
self.win.setWindowTitle(title)
|
||||
self.win.show()
|
||||
self.setWindowTitle(title)
|
||||
self.show()
|
||||
|
||||
|
||||
class TabWindow(QtGui.QMainWindow):
|
||||
|
87
opengl/items/GLImageItem.py
Normal file
87
opengl/items/GLImageItem.py
Normal file
@ -0,0 +1,87 @@
|
||||
from OpenGL.GL import *
|
||||
from .. GLGraphicsItem import GLGraphicsItem
|
||||
from pyqtgraph.Qt import QtGui
|
||||
import numpy as np
|
||||
|
||||
__all__ = ['GLImageItem']
|
||||
|
||||
class GLImageItem(GLGraphicsItem):
|
||||
"""
|
||||
**Bases:** :class:`GLGraphicsItem <pyqtgraph.opengl.GLGraphicsItem>`
|
||||
|
||||
Displays image data as a textured quad.
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self, data, smooth=False):
|
||||
"""
|
||||
|
||||
============== =======================================================================================
|
||||
**Arguments:**
|
||||
data Volume data to be rendered. *Must* be 3D numpy array (x, y, RGBA) with dtype=ubyte.
|
||||
(See functions.makeRGBA)
|
||||
smooth (bool) If True, the volume slices are rendered with linear interpolation
|
||||
============== =======================================================================================
|
||||
"""
|
||||
|
||||
self.smooth = smooth
|
||||
self.data = data
|
||||
GLGraphicsItem.__init__(self)
|
||||
|
||||
def initializeGL(self):
|
||||
glEnable(GL_TEXTURE_2D)
|
||||
self.texture = glGenTextures(1)
|
||||
glBindTexture(GL_TEXTURE_2D, self.texture)
|
||||
if self.smooth:
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
|
||||
else:
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER)
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER)
|
||||
#glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER)
|
||||
shape = self.data.shape
|
||||
|
||||
## Test texture dimensions first
|
||||
glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA, shape[0], shape[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, None)
|
||||
if glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH) == 0:
|
||||
raise Exception("OpenGL failed to create 2D texture (%dx%d); too large for this hardware." % shape[:2])
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, shape[0], shape[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, self.data.transpose((1,0,2)))
|
||||
glDisable(GL_TEXTURE_2D)
|
||||
|
||||
#self.lists = {}
|
||||
#for ax in [0,1,2]:
|
||||
#for d in [-1, 1]:
|
||||
#l = glGenLists(1)
|
||||
#self.lists[(ax,d)] = l
|
||||
#glNewList(l, GL_COMPILE)
|
||||
#self.drawVolume(ax, d)
|
||||
#glEndList()
|
||||
|
||||
|
||||
def paint(self):
|
||||
|
||||
glEnable(GL_TEXTURE_2D)
|
||||
glBindTexture(GL_TEXTURE_2D, self.texture)
|
||||
|
||||
glEnable(GL_DEPTH_TEST)
|
||||
#glDisable(GL_CULL_FACE)
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
||||
glEnable( GL_BLEND )
|
||||
glEnable( GL_ALPHA_TEST )
|
||||
glColor4f(1,1,1,1)
|
||||
|
||||
glBegin(GL_QUADS)
|
||||
glTexCoord2f(0,0)
|
||||
glVertex3f(0,0,0)
|
||||
glTexCoord2f(1,0)
|
||||
glVertex3f(self.data.shape[0], 0, 0)
|
||||
glTexCoord2f(1,1)
|
||||
glVertex3f(self.data.shape[0], self.data.shape[1], 0)
|
||||
glTexCoord2f(0,1)
|
||||
glVertex3f(0, self.data.shape[1], 0)
|
||||
glEnd()
|
||||
glDisable(GL_TEXTURE_3D)
|
||||
|
@ -8,31 +8,47 @@ __all__ = ['GLScatterPlotItem']
|
||||
class GLScatterPlotItem(GLGraphicsItem):
|
||||
"""Draws points at a list of 3D positions."""
|
||||
|
||||
def __init__(self, data=None):
|
||||
def __init__(self, **kwds):
|
||||
GLGraphicsItem.__init__(self)
|
||||
self.data = []
|
||||
if data is not None:
|
||||
self.setData(data)
|
||||
self.pos = []
|
||||
self.size = 10
|
||||
self.color = [1.0,1.0,1.0,0.5]
|
||||
self.pxMode = True
|
||||
self.setData(**kwds)
|
||||
|
||||
def setData(self, data):
|
||||
def setData(self, **kwds):
|
||||
"""
|
||||
Data may be either a list of dicts (one dict per point) or a numpy record array.
|
||||
Update the data displayed by this item. All arguments are optional;
|
||||
for example it is allowed to update spot positions while leaving
|
||||
colors unchanged, etc.
|
||||
|
||||
==================== ==================================================
|
||||
Allowed fields are:
|
||||
Arguments:
|
||||
------------------------------------------------------------------------
|
||||
pos (x,y,z) tuple of coordinate values or QVector3D
|
||||
color (r,g,b,a) tuple of floats (0.0-1.0) or QColor
|
||||
size (float) diameter of spot in pixels
|
||||
pos (N,3) array of floats specifying point locations.
|
||||
color (N,4) array of floats (0.0-1.0) specifying
|
||||
spot colors OR a tuple of floats specifying
|
||||
a single color for all spots.
|
||||
size (N,) array of floats specifying spot sizes or
|
||||
a single value to apply to all spots.
|
||||
pxMode If True, spot sizes are expressed in pixels.
|
||||
Otherwise, they are expressed in item coordinates.
|
||||
==================== ==================================================
|
||||
"""
|
||||
|
||||
|
||||
self.data = data
|
||||
args = ['pos', 'color', 'size', 'pxMode']
|
||||
for k in kwds.keys():
|
||||
if k not in args:
|
||||
raise Exception('Invalid keyword argument: %s (allowed arguments are %s)' % (k, str(args)))
|
||||
self.pos = kwds.get('pos', self.pos)
|
||||
self.color = kwds.get('color', self.color)
|
||||
self.size = kwds.get('size', self.size)
|
||||
self.pxMode = kwds.get('pxMode', self.pxMode)
|
||||
self.update()
|
||||
|
||||
|
||||
def initializeGL(self):
|
||||
|
||||
## Generate texture for rendering points
|
||||
w = 64
|
||||
def fn(x,y):
|
||||
r = ((x-w/2.)**2 + (y-w/2.)**2) ** 0.5
|
||||
@ -73,28 +89,49 @@ class GLScatterPlotItem(GLGraphicsItem):
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
|
||||
|
||||
for pt in self.data:
|
||||
pos = pt['pos']
|
||||
try:
|
||||
color = pt['color']
|
||||
except KeyError:
|
||||
color = (1,1,1,1)
|
||||
try:
|
||||
size = pt['size']
|
||||
except KeyError:
|
||||
size = 10
|
||||
|
||||
if isinstance(color, QtGui.QColor):
|
||||
color = fn.glColor(color)
|
||||
|
||||
pxSize = self.view().pixelSize(QtGui.QVector3D(*pos))
|
||||
if self.pxMode:
|
||||
glVertexPointerf(self.pos)
|
||||
if isinstance(self.color, np.ndarray):
|
||||
glColorPointerf(self.color)
|
||||
else:
|
||||
if isinstance(self.color, QtGui.QColor):
|
||||
glColor4f(*fn.glColor(self.color))
|
||||
else:
|
||||
glColor4f(*self.color)
|
||||
|
||||
glPointSize(size / pxSize)
|
||||
glBegin( GL_POINTS )
|
||||
glColor4f(*color) # x is blue
|
||||
#glNormal3f(size, 0, 0)
|
||||
glVertex3f(*pos)
|
||||
glEnd()
|
||||
if isinstance(self.size, np.ndarray):
|
||||
raise Exception('Array size not yet supported in pxMode (hopefully soon)')
|
||||
|
||||
glPointSize(self.size)
|
||||
glEnableClientState(GL_VERTEX_ARRAY)
|
||||
glEnableClientState(GL_COLOR_ARRAY)
|
||||
glDrawArrays(GL_POINTS, 0, len(self.pos))
|
||||
else:
|
||||
|
||||
|
||||
for i in range(len(self.pos)):
|
||||
pos = self.pos[i]
|
||||
|
||||
if isinstance(self.color, np.ndarray):
|
||||
color = self.color[i]
|
||||
else:
|
||||
color = self.color
|
||||
if isinstance(self.color, QtGui.QColor):
|
||||
color = fn.glColor(self.color)
|
||||
|
||||
if isinstance(self.size, np.ndarray):
|
||||
size = self.size[i]
|
||||
else:
|
||||
size = self.size
|
||||
|
||||
pxSize = self.view().pixelSize(QtGui.QVector3D(*pos))
|
||||
|
||||
glPointSize(size / pxSize)
|
||||
glBegin( GL_POINTS )
|
||||
glColor4f(*color) # x is blue
|
||||
#glNormal3f(size, 0, 0)
|
||||
glVertex3f(*pos)
|
||||
glEnd()
|
||||
|
||||
|
||||
|
||||
|
@ -42,6 +42,7 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
enabled via enableMouse() (but ordinarily, we use ViewBox for this functionality)."""
|
||||
|
||||
sigRangeChanged = QtCore.Signal(object, object)
|
||||
sigTransformChanged = QtCore.Signal(object)
|
||||
sigMouseReleased = QtCore.Signal(object)
|
||||
sigSceneMouseMoved = QtCore.Signal(object)
|
||||
#sigRegionChanged = QtCore.Signal(object)
|
||||
@ -212,6 +213,7 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
self.fitInView(self.range, QtCore.Qt.IgnoreAspectRatio)
|
||||
|
||||
self.sigRangeChanged.emit(self, self.range)
|
||||
self.sigTransformChanged.emit(self)
|
||||
|
||||
if propagate:
|
||||
for v in self.lockedViewports:
|
||||
|
Loading…
x
Reference in New Issue
Block a user