merge from luke
This commit is contained in:
commit
885d2157f1
@ -77,6 +77,11 @@ class Dock(QtGui.QWidget, DockDrop):
|
|||||||
return name == 'dock'
|
return name == 'dock'
|
||||||
|
|
||||||
def setStretch(self, x=None, y=None):
|
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
|
#print "setStretch", self, x, y
|
||||||
#self._stretch = (x, y)
|
#self._stretch = (x, y)
|
||||||
if x is None:
|
if x is None:
|
||||||
@ -100,6 +105,10 @@ class Dock(QtGui.QWidget, DockDrop):
|
|||||||
#return self._stretch
|
#return self._stretch
|
||||||
|
|
||||||
def hideTitleBar(self):
|
def hideTitleBar(self):
|
||||||
|
"""
|
||||||
|
Hide the title bar for this Dock.
|
||||||
|
This will prevent the Dock being moved by the user.
|
||||||
|
"""
|
||||||
self.label.hide()
|
self.label.hide()
|
||||||
self.labelHidden = True
|
self.labelHidden = True
|
||||||
if 'center' in self.allowedAreas:
|
if 'center' in self.allowedAreas:
|
||||||
@ -107,12 +116,21 @@ class Dock(QtGui.QWidget, DockDrop):
|
|||||||
self.updateStyle()
|
self.updateStyle()
|
||||||
|
|
||||||
def showTitleBar(self):
|
def showTitleBar(self):
|
||||||
|
"""
|
||||||
|
Show the title bar for this Dock.
|
||||||
|
"""
|
||||||
self.label.show()
|
self.label.show()
|
||||||
self.labelHidden = False
|
self.labelHidden = False
|
||||||
self.allowedAreas.add('center')
|
self.allowedAreas.add('center')
|
||||||
self.updateStyle()
|
self.updateStyle()
|
||||||
|
|
||||||
def setOrientation(self, o='auto', force=False):
|
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
|
#print self.name(), "setOrientation", o, force
|
||||||
if o == 'auto' and self.autoOrient:
|
if o == 'auto' and self.autoOrient:
|
||||||
if self.container().type() == 'tab':
|
if self.container().type() == 'tab':
|
||||||
@ -127,6 +145,7 @@ class Dock(QtGui.QWidget, DockDrop):
|
|||||||
self.updateStyle()
|
self.updateStyle()
|
||||||
|
|
||||||
def updateStyle(self):
|
def updateStyle(self):
|
||||||
|
## updates orientation and appearance of title bar
|
||||||
#print self.name(), "update style:", self.orientation, self.moveLabel, self.label.isVisible()
|
#print self.name(), "update style:", self.orientation, self.moveLabel, self.label.isVisible()
|
||||||
if self.labelHidden:
|
if self.labelHidden:
|
||||||
self.widgetArea.setStyleSheet(self.nStyle)
|
self.widgetArea.setStyleSheet(self.nStyle)
|
||||||
@ -154,6 +173,10 @@ class Dock(QtGui.QWidget, DockDrop):
|
|||||||
return self._container
|
return self._container
|
||||||
|
|
||||||
def addWidget(self, widget, row=None, col=0, rowspan=1, colspan=1):
|
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:
|
if row is None:
|
||||||
row = self.currentRow
|
row = self.currentRow
|
||||||
self.currentRow = max(row+1, self.currentRow)
|
self.currentRow = max(row+1, self.currentRow)
|
||||||
@ -188,7 +211,8 @@ class Dock(QtGui.QWidget, DockDrop):
|
|||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<Dock %s %s>" % (self.name(), self.stretch())
|
return "<Dock %s %s>" % (self.name(), self.stretch())
|
||||||
|
|
||||||
|
|
||||||
class DockLabel(VerticalLabel):
|
class DockLabel(VerticalLabel):
|
||||||
|
|
||||||
sigClicked = QtCore.Signal(object, object)
|
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):
|
def addDock(self, dock, position='bottom', relativeTo=None):
|
||||||
"""Adds a dock to this area.
|
"""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.
|
## Determine the container to insert this dock into.
|
||||||
## If there is no neighbor, then the container is the top.
|
## If there is no neighbor, then the container is the top.
|
||||||
@ -90,6 +100,17 @@ class DockArea(Container, QtGui.QWidget, DockDrop):
|
|||||||
dock.area = self
|
dock.area = self
|
||||||
self.docks[dock.name()] = dock
|
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):
|
def getContainer(self, obj):
|
||||||
if obj is None:
|
if obj is None:
|
||||||
return self
|
return self
|
||||||
@ -131,13 +152,6 @@ class DockArea(Container, QtGui.QWidget, DockDrop):
|
|||||||
return 0
|
return 0
|
||||||
return 1
|
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):
|
#def paintEvent(self, ev):
|
||||||
#self.drawDockOverlay()
|
#self.drawDockOverlay()
|
||||||
@ -159,6 +173,7 @@ class DockArea(Container, QtGui.QWidget, DockDrop):
|
|||||||
return area
|
return area
|
||||||
|
|
||||||
def floatDock(self, dock):
|
def floatDock(self, dock):
|
||||||
|
"""Removes *dock* from this DockArea and places it in a new window."""
|
||||||
area = self.addTempArea()
|
area = self.addTempArea()
|
||||||
area.win.resize(dock.size())
|
area.win.resize(dock.size())
|
||||||
area.moveDock(dock, 'top', None)
|
area.moveDock(dock, 'top', None)
|
||||||
@ -170,6 +185,9 @@ class DockArea(Container, QtGui.QWidget, DockDrop):
|
|||||||
area.window().close()
|
area.window().close()
|
||||||
|
|
||||||
def saveState(self):
|
def saveState(self):
|
||||||
|
"""
|
||||||
|
Return a serialized (storable) representation of the state of
|
||||||
|
all Docks in this DockArea."""
|
||||||
state = {'main': self.childState(self.topContainer), 'float': []}
|
state = {'main': self.childState(self.topContainer), 'float': []}
|
||||||
for a in self.tempAreas:
|
for a in self.tempAreas:
|
||||||
geo = a.win.geometry()
|
geo = a.win.geometry()
|
||||||
@ -188,6 +206,10 @@ class DockArea(Container, QtGui.QWidget, DockDrop):
|
|||||||
|
|
||||||
|
|
||||||
def restoreState(self, state):
|
def restoreState(self, state):
|
||||||
|
"""
|
||||||
|
Restore Dock configuration as generated by saveState.
|
||||||
|
"""
|
||||||
|
|
||||||
## 1) make dict of all docks and list of existing containers
|
## 1) make dict of all docks and list of existing containers
|
||||||
containers, docks = self.findAll()
|
containers, docks = self.findAll()
|
||||||
oldTemps = self.tempAreas[:]
|
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
|
glgriditem
|
||||||
glmeshitem
|
glmeshitem
|
||||||
glvolumeitem
|
glvolumeitem
|
||||||
|
glimageitem
|
||||||
glaxisitem
|
glaxisitem
|
||||||
glgraphicsitem
|
glgraphicsitem
|
||||||
|
glscatterplotitem
|
||||||
meshdata
|
meshdata
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ Contents:
|
|||||||
scalebar
|
scalebar
|
||||||
labelitem
|
labelitem
|
||||||
vtickgroup
|
vtickgroup
|
||||||
|
legenditem
|
||||||
gradienteditoritem
|
gradienteditoritem
|
||||||
histogramlutitem
|
histogramlutitem
|
||||||
gradientlegend
|
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
|
from pyqtgraph.Qt import QtCore, QtGui
|
||||||
import pyqtgraph.opengl as gl
|
import pyqtgraph.opengl as gl
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
app = QtGui.QApplication([])
|
app = QtGui.QApplication([])
|
||||||
w = gl.GLViewWidget()
|
w = gl.GLViewWidget()
|
||||||
@ -14,18 +15,47 @@ w.show()
|
|||||||
g = gl.GLGridItem()
|
g = gl.GLGridItem()
|
||||||
w.addItem(g)
|
w.addItem(g)
|
||||||
|
|
||||||
pts = [
|
#pos = np.empty((53, 3))
|
||||||
{'pos': (1,0,0), 'size':0.5, 'color':(1.0, 0.0, 0.0, 0.5)},
|
#size = np.empty((53))
|
||||||
{'pos': (0,1,0), 'size':0.2, 'color':(0.0, 0.0, 1.0, 0.5)},
|
#color = np.empty((53, 4))
|
||||||
{'pos': (0,0,1), 'size':2./3., 'color':(0.0, 1.0, 0.0, 0.5)},
|
#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)
|
||||||
z = 0.5
|
#pos[2] = (0,0,1); size[2] = 2./3.; color[2] = (0.0, 1.0, 0.0, 0.5)
|
||||||
d = 6.0
|
|
||||||
for i in range(50):
|
#z = 0.5
|
||||||
pts.append({'pos': (0,0,z), 'size':2./d, 'color':(0.0, 1.0, 0.0, 0.5)})
|
#d = 6.0
|
||||||
z *= 0.5
|
#for i in range(3,53):
|
||||||
d *= 2.0
|
#pos[i] = (0,0,z)
|
||||||
sp = gl.GLScatterPlotItem(pts)
|
#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)
|
w.addItem(sp)
|
||||||
|
|
||||||
## Start Qt event loop unless running in interactive mode.
|
## 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
|
p = ui.plot
|
||||||
|
|
||||||
data = np.random.normal(size=(50,500), scale=100)
|
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
|
ptr = 0
|
||||||
lastTime = time()
|
lastTime = time()
|
||||||
fps = None
|
fps = None
|
||||||
@ -49,7 +49,8 @@ def update():
|
|||||||
s = np.clip(dt*3., 0, 1)
|
s = np.clip(dt*3., 0, 1)
|
||||||
fps = fps * (1-s) + (1.0/dt) * s
|
fps = fps * (1-s) + (1.0/dt) * s
|
||||||
p.setTitle('%0.2f fps' % fps)
|
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 = QtCore.QTimer()
|
||||||
timer.timeout.connect(update)
|
timer.timeout.connect(update)
|
||||||
timer.start(0)
|
timer.start(0)
|
||||||
|
@ -17,12 +17,11 @@ examples = OrderedDict([
|
|||||||
('ImageView', 'ImageView.py'),
|
('ImageView', 'ImageView.py'),
|
||||||
('ParameterTree', 'parametertree.py'),
|
('ParameterTree', 'parametertree.py'),
|
||||||
('Crosshair / Mouse interaction', 'crosshair.py'),
|
('Crosshair / Mouse interaction', 'crosshair.py'),
|
||||||
('Video speed test', 'VideoSpeedTest.py'),
|
|
||||||
('Plot speed test', 'PlotSpeedTest.py'),
|
|
||||||
('Data Slicing', 'DataSlicing.py'),
|
('Data Slicing', 'DataSlicing.py'),
|
||||||
('Plot Customization', 'customPlot.py'),
|
('Plot Customization', 'customPlot.py'),
|
||||||
('Dock widgets', 'dockarea.py'),
|
('Dock widgets', 'dockarea.py'),
|
||||||
('Console', 'ConsoleWidget.py'),
|
('Console', 'ConsoleWidget.py'),
|
||||||
|
('Histograms', 'histogram.py'),
|
||||||
('GraphicsItems', OrderedDict([
|
('GraphicsItems', OrderedDict([
|
||||||
('Scatter Plot', 'ScatterPlot.py'),
|
('Scatter Plot', 'ScatterPlot.py'),
|
||||||
#('PlotItem', 'PlotItem.py'),
|
#('PlotItem', 'PlotItem.py'),
|
||||||
@ -31,14 +30,22 @@ examples = OrderedDict([
|
|||||||
('ImageItem - draw', 'Draw.py'),
|
('ImageItem - draw', 'Draw.py'),
|
||||||
('Region-of-Interest', 'ROIExamples.py'),
|
('Region-of-Interest', 'ROIExamples.py'),
|
||||||
('GraphicsLayout', 'GraphicsLayout.py'),
|
('GraphicsLayout', 'GraphicsLayout.py'),
|
||||||
|
('LegendItem', 'Legend.py'),
|
||||||
('Text Item', 'text.py'),
|
('Text Item', 'text.py'),
|
||||||
('Linked Views', 'linkedViews.py'),
|
('Linked Views', 'linkedViews.py'),
|
||||||
('Arrow', 'Arrow.py'),
|
('Arrow', 'Arrow.py'),
|
||||||
('ViewBox', 'ViewBox.py'),
|
('ViewBox', 'ViewBox.py'),
|
||||||
])),
|
])),
|
||||||
|
('Benchmarks', OrderedDict([
|
||||||
|
('Video speed test', 'VideoSpeedTest.py'),
|
||||||
|
('Line Plot update', 'PlotSpeedTest.py'),
|
||||||
|
('Scatter Plot update', 'ScatterPlotSpeedTest.py'),
|
||||||
|
])),
|
||||||
('3D Graphics', OrderedDict([
|
('3D Graphics', OrderedDict([
|
||||||
('Volumetric', 'GLVolumeItem.py'),
|
('Volumetric', 'GLVolumeItem.py'),
|
||||||
('Isosurface', 'GLMeshItem.py'),
|
('Isosurface', 'GLMeshItem.py'),
|
||||||
|
('Image', 'GLImageItem.py'),
|
||||||
|
('Scatter Plot', 'GLScatterPlotItem.py'),
|
||||||
])),
|
])),
|
||||||
('Widgets', OrderedDict([
|
('Widgets', OrderedDict([
|
||||||
('PlotWidget', 'PlotWidget.py'),
|
('PlotWidget', 'PlotWidget.py'),
|
||||||
@ -80,7 +87,17 @@ class ExampleLoader(QtGui.QMainWindow):
|
|||||||
self.ui.loadBtn.clicked.connect(self.loadFile)
|
self.ui.loadBtn.clicked.connect(self.loadFile)
|
||||||
self.ui.exampleTree.currentItemChanged.connect(self.showFile)
|
self.ui.exampleTree.currentItemChanged.connect(self.showFile)
|
||||||
self.ui.exampleTree.itemDoubleClicked.connect(self.loadFile)
|
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):
|
def populateTree(self, root, examples):
|
||||||
for key, val in examples.items():
|
for key, val in examples.items():
|
||||||
@ -101,12 +118,19 @@ class ExampleLoader(QtGui.QMainWindow):
|
|||||||
|
|
||||||
def loadFile(self):
|
def loadFile(self):
|
||||||
fn = self.currentFile()
|
fn = self.currentFile()
|
||||||
|
extra = []
|
||||||
|
if self.ui.pyqtCheck.isChecked():
|
||||||
|
extra.append('pyqt')
|
||||||
|
elif self.ui.pysideCheck.isChecked():
|
||||||
|
extra.append('pyside')
|
||||||
|
|
||||||
if fn is None:
|
if fn is None:
|
||||||
return
|
return
|
||||||
if sys.platform.startswith('win'):
|
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:
|
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):
|
def showFile(self):
|
||||||
|
@ -39,6 +39,24 @@
|
|||||||
</column>
|
</column>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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>
|
<item>
|
||||||
<widget class="QPushButton" name="loadBtn">
|
<widget class="QPushButton" name="loadBtn">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# Form implementation generated from reading ui file './examples/exampleLoaderTemplate.ui'
|
# 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
|
# by: PyQt4 UI code generator 4.9.1
|
||||||
#
|
#
|
||||||
# WARNING! All changes made in this file will be lost!
|
# 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.headerItem().setText(0, _fromUtf8("1"))
|
||||||
self.exampleTree.header().setVisible(False)
|
self.exampleTree.header().setVisible(False)
|
||||||
self.verticalLayout.addWidget(self.exampleTree)
|
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 = QtGui.QPushButton(self.layoutWidget)
|
||||||
self.loadBtn.setObjectName(_fromUtf8("loadBtn"))
|
self.loadBtn.setObjectName(_fromUtf8("loadBtn"))
|
||||||
self.verticalLayout.addWidget(self.loadBtn)
|
self.verticalLayout.addWidget(self.loadBtn)
|
||||||
@ -51,5 +60,7 @@ class Ui_Form(object):
|
|||||||
|
|
||||||
def retranslateUi(self, Form):
|
def retranslateUi(self, Form):
|
||||||
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8))
|
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))
|
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'
|
# 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
|
# by: pyside-uic 0.2.13 running on PySide 1.1.0
|
||||||
#
|
#
|
||||||
# WARNING! All changes made in this file will be lost!
|
# 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.headerItem().setText(0, "1")
|
||||||
self.exampleTree.header().setVisible(False)
|
self.exampleTree.header().setVisible(False)
|
||||||
self.verticalLayout.addWidget(self.exampleTree)
|
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 = QtGui.QPushButton(self.layoutWidget)
|
||||||
self.loadBtn.setObjectName("loadBtn")
|
self.loadBtn.setObjectName("loadBtn")
|
||||||
self.verticalLayout.addWidget(self.loadBtn)
|
self.verticalLayout.addWidget(self.loadBtn)
|
||||||
@ -46,5 +55,7 @@ class Ui_Form(object):
|
|||||||
|
|
||||||
def retranslateUi(self, Form):
|
def retranslateUi(self, Form):
|
||||||
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8))
|
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))
|
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
|
## make this version of pyqtgraph importable before any others
|
||||||
import sys, os
|
import sys, os
|
||||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')))
|
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):
|
def export(self, fileName=None):
|
||||||
|
|
||||||
if not isinstance(self.item, pg.PlotItem):
|
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:
|
if fileName is None:
|
||||||
self.fileSaveDialog(filter=["*.csv", "*.tsv"])
|
self.fileSaveDialog(filter=["*.csv", "*.tsv"])
|
||||||
|
@ -54,8 +54,13 @@ class Exporter(object):
|
|||||||
fileName = str(fileName)
|
fileName = str(fileName)
|
||||||
global LastExportDirectory
|
global LastExportDirectory
|
||||||
LastExportDirectory = os.path.split(fileName)[0]
|
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):
|
def getScene(self):
|
||||||
if isinstance(self.item, pg.GraphicsScene):
|
if isinstance(self.item, pg.GraphicsScene):
|
||||||
|
@ -63,7 +63,7 @@ class ImageExporter(Exporter):
|
|||||||
self.getScene().render(painter, QtCore.QRectF(targetRect), sourceRect)
|
self.getScene().render(painter, QtCore.QRectF(targetRect), sourceRect)
|
||||||
finally:
|
finally:
|
||||||
self.setExportMode(False)
|
self.setExportMode(False)
|
||||||
self.png.save(fileName)
|
|
||||||
painter.end()
|
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 numpy as np
|
||||||
import decimal, re
|
import decimal, re
|
||||||
|
import ctypes
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import scipy.ndimage
|
import scipy.ndimage
|
||||||
@ -223,13 +224,15 @@ def mkColor(*args):
|
|||||||
return QtGui.QColor(*args)
|
return QtGui.QColor(*args)
|
||||||
|
|
||||||
|
|
||||||
def mkBrush(*args):
|
def mkBrush(*args, **kwds):
|
||||||
"""
|
"""
|
||||||
| Convenience function for constructing Brush.
|
| Convenience function for constructing Brush.
|
||||||
| This function always constructs a solid brush and accepts the same arguments as :func:`mkColor() <pyqtgraph.mkColor>`
|
| This function always constructs a solid brush and accepts the same arguments as :func:`mkColor() <pyqtgraph.mkColor>`
|
||||||
| Calling mkBrush(None) returns an invisible brush.
|
| 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]
|
arg = args[0]
|
||||||
if arg is None:
|
if arg is None:
|
||||||
return QtGui.QBrush(QtCore.Qt.NoBrush)
|
return QtGui.QBrush(QtCore.Qt.NoBrush)
|
||||||
@ -237,7 +240,7 @@ def mkBrush(*args):
|
|||||||
return QtGui.QBrush(arg)
|
return QtGui.QBrush(arg)
|
||||||
else:
|
else:
|
||||||
color = arg
|
color = arg
|
||||||
if len(args) > 1:
|
elif len(args) > 1:
|
||||||
color = args
|
color = args
|
||||||
return QtGui.QBrush(mkColor(color))
|
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):
|
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.
|
Lookup tables can be built using GradientWidget.
|
||||||
levels - List [min, max]; optionally rescale data before converting through the
|
levels - List [min, max]; optionally rescale data before converting through the
|
||||||
lookup table. rescaled = (data-min) * len(lut) / (max-min)
|
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.
|
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
|
return imgData, alpha
|
||||||
|
|
||||||
|
|
||||||
def makeQImage(imgData, alpha):
|
def makeQImage(imgData, alpha=None, copy=True, transpose=True):
|
||||||
"""Turn an ARGB array into QImage"""
|
"""
|
||||||
|
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
|
## create QImage from buffer
|
||||||
prof = debug.Profiler('functions.makeQImage', disabled=True)
|
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:
|
if alpha:
|
||||||
imgFormat = QtGui.QImage.Format_ARGB32
|
imgFormat = QtGui.QImage.Format_ARGB32
|
||||||
else:
|
else:
|
||||||
imgFormat = QtGui.QImage.Format_RGB32
|
imgFormat = QtGui.QImage.Format_RGB32
|
||||||
|
|
||||||
imgData = imgData.transpose((1, 0, 2)) ## QImage expects the row/column order to be opposite
|
if transpose:
|
||||||
try:
|
imgData = imgData.transpose((1, 0, 2)) ## QImage expects the row/column order to be opposite
|
||||||
buf = imgData.data
|
|
||||||
except AttributeError: ## happens when image data is non-contiguous
|
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)
|
imgData = np.ascontiguousarray(imgData)
|
||||||
buf = imgData.data
|
copied = True
|
||||||
|
|
||||||
prof.mark('1')
|
if copy is True and copied is False:
|
||||||
qimage = QtGui.QImage(buf, imgData.shape[1], imgData.shape[0], imgFormat)
|
imgData = imgData.copy()
|
||||||
prof.mark('2')
|
|
||||||
qimage.data = imgData
|
if USE_PYSIDE:
|
||||||
prof.finish()
|
ch = ctypes.c_char.from_buffer(imgData, 0)
|
||||||
return qimage
|
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):
|
def rescaleData(data, scale, offset):
|
||||||
newData = np.empty((data.size,), dtype=np.int)
|
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])
|
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)
|
return asUnicode("<span style='%s'>%s</span>") % (style, s)
|
||||||
|
|
||||||
def setHeight(self, h=None):
|
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:
|
if h is None:
|
||||||
h = self.textHeight + max(0, self.tickLength)
|
h = self.textHeight + max(0, self.tickLength)
|
||||||
if self.label.isVisible():
|
if self.label.isVisible():
|
||||||
@ -182,6 +184,8 @@ class AxisItem(GraphicsWidget):
|
|||||||
|
|
||||||
|
|
||||||
def setWidth(self, w=None):
|
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:
|
if w is None:
|
||||||
w = max(0, self.tickLength) + 40
|
w = max(0, self.tickLength) + 40
|
||||||
if self.label.isVisible():
|
if self.label.isVisible():
|
||||||
|
@ -394,14 +394,17 @@ class GraphicsItem(object):
|
|||||||
if oldView is not None:
|
if oldView is not None:
|
||||||
#print "disconnect:", self, oldView
|
#print "disconnect:", self, oldView
|
||||||
oldView.sigRangeChanged.disconnect(self.viewRangeChanged)
|
oldView.sigRangeChanged.disconnect(self.viewRangeChanged)
|
||||||
|
oldView.sigTransformChanged.disconnect(self.viewTransformChanged)
|
||||||
self._connectedView = None
|
self._connectedView = None
|
||||||
|
|
||||||
## connect to new view
|
## connect to new view
|
||||||
if view is not None:
|
if view is not None:
|
||||||
#print "connect:", self, view
|
#print "connect:", self, view
|
||||||
view.sigRangeChanged.connect(self.viewRangeChanged)
|
view.sigRangeChanged.connect(self.viewRangeChanged)
|
||||||
|
view.sigTransformChanged.connect(self.viewTransformChanged)
|
||||||
self._connectedView = weakref.ref(view)
|
self._connectedView = weakref.ref(view)
|
||||||
self.viewRangeChanged()
|
self.viewRangeChanged()
|
||||||
|
self.viewTransformChanged()
|
||||||
|
|
||||||
## inform children that their view might have changed
|
## inform children that their view might have changed
|
||||||
self._replaceView(oldView)
|
self._replaceView(oldView)
|
||||||
@ -425,3 +428,9 @@ class GraphicsItem(object):
|
|||||||
Called whenever the view coordinates of the ViewBox containing this item have changed.
|
Called whenever the view coordinates of the ViewBox containing this item have changed.
|
||||||
"""
|
"""
|
||||||
pass
|
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,
|
'shadowPen': None,
|
||||||
'fillLevel': None,
|
'fillLevel': None,
|
||||||
'brush': None,
|
'brush': None,
|
||||||
|
'stepMode': False,
|
||||||
}
|
}
|
||||||
self.setClickable(kargs.get('clickable', False))
|
self.setClickable(kargs.get('clickable', False))
|
||||||
self.setData(*args, **kargs)
|
self.setData(*args, **kargs)
|
||||||
@ -223,8 +224,15 @@ class PlotCurveItem(GraphicsObject):
|
|||||||
|
|
||||||
prof.mark('copy')
|
prof.mark('copy')
|
||||||
|
|
||||||
if self.xData.shape != self.yData.shape:
|
if 'stepMode' in kargs:
|
||||||
raise Exception("X and Y arrays must be the same shape--got %s and %s." % (str(x.shape), str(y.shape)))
|
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.path = None
|
||||||
self.fillPath = None
|
self.fillPath = None
|
||||||
@ -267,6 +275,29 @@ class PlotCurveItem(GraphicsObject):
|
|||||||
## 0(i4)
|
## 0(i4)
|
||||||
##
|
##
|
||||||
## All values are big endian--pack using struct.pack('>d') or struct.pack('>i')
|
## 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??
|
if sys.version_info[0] == 2: ## So this is disabled for python 3... why??
|
||||||
n = x.shape[0]
|
n = x.shape[0]
|
||||||
# create empty array, pad with extra space on either end
|
# create empty array, pad with extra space on either end
|
||||||
@ -324,12 +355,21 @@ class PlotCurveItem(GraphicsObject):
|
|||||||
pixels = self.pixelVectors()
|
pixels = self.pixelVectors()
|
||||||
if pixels == (None, None):
|
if pixels == (None, None):
|
||||||
pixels = [Point(0,0), Point(0,0)]
|
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)
|
return QtCore.QRectF(xmin, ymin, xmax-xmin, ymax-ymin)
|
||||||
|
|
||||||
def paint(self, p, opt, widget):
|
def paint(self, p, opt, widget):
|
||||||
|
@ -130,6 +130,8 @@ class PlotDataItem(GraphicsObject):
|
|||||||
'symbolBrush': (50, 50, 150),
|
'symbolBrush': (50, 50, 150),
|
||||||
'pxMode': True,
|
'pxMode': True,
|
||||||
|
|
||||||
|
'pointMode': None,
|
||||||
|
|
||||||
'data': None,
|
'data': None,
|
||||||
}
|
}
|
||||||
self.setData(*args, **kargs)
|
self.setData(*args, **kargs)
|
||||||
@ -144,22 +146,30 @@ class PlotDataItem(GraphicsObject):
|
|||||||
return QtCore.QRectF() ## let child items handle this
|
return QtCore.QRectF() ## let child items handle this
|
||||||
|
|
||||||
def setAlpha(self, alpha, auto):
|
def setAlpha(self, alpha, auto):
|
||||||
|
if self.opts['alphaHint'] == alpha and self.opts['alphaMode'] == auto:
|
||||||
|
return
|
||||||
self.opts['alphaHint'] = alpha
|
self.opts['alphaHint'] = alpha
|
||||||
self.opts['alphaMode'] = auto
|
self.opts['alphaMode'] = auto
|
||||||
self.setOpacity(alpha)
|
self.setOpacity(alpha)
|
||||||
#self.update()
|
#self.update()
|
||||||
|
|
||||||
def setFftMode(self, mode):
|
def setFftMode(self, mode):
|
||||||
|
if self.opts['fftMode'] == mode:
|
||||||
|
return
|
||||||
self.opts['fftMode'] = mode
|
self.opts['fftMode'] = mode
|
||||||
self.xDisp = self.yDisp = None
|
self.xDisp = self.yDisp = None
|
||||||
self.updateItems()
|
self.updateItems()
|
||||||
|
|
||||||
def setLogMode(self, xMode, yMode):
|
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.xDisp = self.yDisp = None
|
||||||
self.updateItems()
|
self.updateItems()
|
||||||
|
|
||||||
def setPointMode(self, mode):
|
def setPointMode(self, mode):
|
||||||
|
if self.opts['pointMode'] == mode:
|
||||||
|
return
|
||||||
self.opts['pointMode'] = mode
|
self.opts['pointMode'] = mode
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
@ -193,6 +203,8 @@ class PlotDataItem(GraphicsObject):
|
|||||||
|
|
||||||
def setFillBrush(self, *args, **kargs):
|
def setFillBrush(self, *args, **kargs):
|
||||||
brush = fn.mkBrush(*args, **kargs)
|
brush = fn.mkBrush(*args, **kargs)
|
||||||
|
if self.opts['fillBrush'] == brush:
|
||||||
|
return
|
||||||
self.opts['fillBrush'] = brush
|
self.opts['fillBrush'] = brush
|
||||||
self.updateItems()
|
self.updateItems()
|
||||||
|
|
||||||
@ -200,16 +212,22 @@ class PlotDataItem(GraphicsObject):
|
|||||||
return self.setFillBrush(*args, **kargs)
|
return self.setFillBrush(*args, **kargs)
|
||||||
|
|
||||||
def setFillLevel(self, level):
|
def setFillLevel(self, level):
|
||||||
|
if self.opts['fillLevel'] == level:
|
||||||
|
return
|
||||||
self.opts['fillLevel'] = level
|
self.opts['fillLevel'] = level
|
||||||
self.updateItems()
|
self.updateItems()
|
||||||
|
|
||||||
def setSymbol(self, symbol):
|
def setSymbol(self, symbol):
|
||||||
|
if self.opts['symbol'] == symbol:
|
||||||
|
return
|
||||||
self.opts['symbol'] = symbol
|
self.opts['symbol'] = symbol
|
||||||
#self.scatter.setSymbol(symbol)
|
#self.scatter.setSymbol(symbol)
|
||||||
self.updateItems()
|
self.updateItems()
|
||||||
|
|
||||||
def setSymbolPen(self, *args, **kargs):
|
def setSymbolPen(self, *args, **kargs):
|
||||||
pen = fn.mkPen(*args, **kargs)
|
pen = fn.mkPen(*args, **kargs)
|
||||||
|
if self.opts['symbolPen'] == pen:
|
||||||
|
return
|
||||||
self.opts['symbolPen'] = pen
|
self.opts['symbolPen'] = pen
|
||||||
#self.scatter.setSymbolPen(pen)
|
#self.scatter.setSymbolPen(pen)
|
||||||
self.updateItems()
|
self.updateItems()
|
||||||
@ -218,21 +236,26 @@ class PlotDataItem(GraphicsObject):
|
|||||||
|
|
||||||
def setSymbolBrush(self, *args, **kargs):
|
def setSymbolBrush(self, *args, **kargs):
|
||||||
brush = fn.mkBrush(*args, **kargs)
|
brush = fn.mkBrush(*args, **kargs)
|
||||||
|
if self.opts['symbolBrush'] == brush:
|
||||||
|
return
|
||||||
self.opts['symbolBrush'] = brush
|
self.opts['symbolBrush'] = brush
|
||||||
#self.scatter.setSymbolBrush(brush)
|
#self.scatter.setSymbolBrush(brush)
|
||||||
self.updateItems()
|
self.updateItems()
|
||||||
|
|
||||||
|
|
||||||
def setSymbolSize(self, size):
|
def setSymbolSize(self, size):
|
||||||
|
if self.opts['symbolSize'] == size:
|
||||||
|
return
|
||||||
self.opts['symbolSize'] = size
|
self.opts['symbolSize'] = size
|
||||||
#self.scatter.setSymbolSize(symbolSize)
|
#self.scatter.setSymbolSize(symbolSize)
|
||||||
self.updateItems()
|
self.updateItems()
|
||||||
|
|
||||||
def setDownsampling(self, ds):
|
def setDownsampling(self, ds):
|
||||||
if self.opts['downsample'] != ds:
|
if self.opts['downsample'] == ds:
|
||||||
self.opts['downsample'] = ds
|
return
|
||||||
self.xDisp = self.yDisp = None
|
self.opts['downsample'] = ds
|
||||||
self.updateItems()
|
self.xDisp = self.yDisp = None
|
||||||
|
self.updateItems()
|
||||||
|
|
||||||
def setData(self, *args, **kargs):
|
def setData(self, *args, **kargs):
|
||||||
"""
|
"""
|
||||||
@ -436,9 +459,12 @@ class PlotDataItem(GraphicsObject):
|
|||||||
and max)
|
and max)
|
||||||
=============== =============================================================
|
=============== =============================================================
|
||||||
"""
|
"""
|
||||||
|
if frac <= 0.0:
|
||||||
|
raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac))
|
||||||
|
|
||||||
(x, y) = self.getData()
|
(x, y) = self.getData()
|
||||||
if x is None or len(x) == 0:
|
if x is None or len(x) == 0:
|
||||||
return (0, 0)
|
return None
|
||||||
|
|
||||||
if ax == 0:
|
if ax == 0:
|
||||||
d = x
|
d = x
|
||||||
@ -450,14 +476,15 @@ class PlotDataItem(GraphicsObject):
|
|||||||
if orthoRange is not None:
|
if orthoRange is not None:
|
||||||
mask = (d2 >= orthoRange[0]) * (d2 <= orthoRange[1])
|
mask = (d2 >= orthoRange[0]) * (d2 <= orthoRange[1])
|
||||||
d = d[mask]
|
d = d[mask]
|
||||||
d2 = d2[mask]
|
#d2 = d2[mask]
|
||||||
|
|
||||||
if frac >= 1.0:
|
if len(d) > 0:
|
||||||
return (np.min(d), np.max(d))
|
if frac >= 1.0:
|
||||||
elif frac <= 0.0:
|
return (np.min(d), np.max(d))
|
||||||
raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac))
|
else:
|
||||||
|
return (scipy.stats.scoreatpercentile(d, 50 - (frac * 50)), scipy.stats.scoreatpercentile(d, 50 + (frac * 50)))
|
||||||
else:
|
else:
|
||||||
return (scipy.stats.scoreatpercentile(d, 50 - (frac * 50)), scipy.stats.scoreatpercentile(d, 50 + (frac * 50)))
|
return None
|
||||||
|
|
||||||
|
|
||||||
def clear(self):
|
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
|
from pyqtgraph.Point import Point
|
||||||
import pyqtgraph.functions as fn
|
import pyqtgraph.functions as fn
|
||||||
from .GraphicsItem import GraphicsItem
|
from .GraphicsItem import GraphicsItem
|
||||||
@ -32,26 +32,171 @@ for k, c in coords.items():
|
|||||||
Symbols[k].lineTo(x, y)
|
Symbols[k].lineTo(x, y)
|
||||||
Symbols[k].closeSubpath()
|
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
|
## Render a spot with the given parameters to a pixmap
|
||||||
penPxWidth = max(np.ceil(pen.width()), 1)
|
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)
|
image.fill(0)
|
||||||
p = QtGui.QPainter(image)
|
p = QtGui.QPainter(image)
|
||||||
p.setRenderHint(p.Antialiasing)
|
p.setRenderHint(p.Antialiasing)
|
||||||
p.translate(image.width()*0.5, image.height()*0.5)
|
p.translate(image.width()*0.5, image.height()*0.5)
|
||||||
p.scale(size, size)
|
drawSymbol(p, symbol, size, pen, brush)
|
||||||
p.setPen(pen)
|
|
||||||
p.setBrush(brush)
|
|
||||||
if isinstance(symbol, basestring):
|
|
||||||
symbol = Symbols[symbol]
|
|
||||||
p.drawPath(symbol)
|
|
||||||
p.end()
|
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):
|
class ScatterPlotItem(GraphicsObject):
|
||||||
"""
|
"""
|
||||||
Displays a set of x/y points. Instances of this class are created
|
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)
|
prof = debug.Profiler('ScatterPlotItem.__init__', disabled=True)
|
||||||
GraphicsObject.__init__(self)
|
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.bounds = [None, None] ## caches data bounds
|
||||||
self._maxSpotWidth = 0 ## maximum size of the scale-variant portion of all spots
|
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._maxSpotPxWidth = 0 ## maximum size of the scale-invariant portion of all spots
|
||||||
self._spotPixmap = None
|
self.opts = {'pxMode': True, 'useCache': True} ## If useCache is False, symbols are re-drawn on every paint.
|
||||||
self.opts = {'pxMode': True}
|
|
||||||
|
|
||||||
self.setPen(200,200,200, update=False)
|
self.setPen(200,200,200, update=False)
|
||||||
self.setBrush(100,100,150, update=False)
|
self.setBrush(100,100,150, update=False)
|
||||||
@ -96,6 +244,8 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
prof.mark('setData')
|
prof.mark('setData')
|
||||||
prof.finish()
|
prof.finish()
|
||||||
|
|
||||||
|
#self.setCacheMode(self.DeviceCoordinateCache)
|
||||||
|
|
||||||
def setData(self, *args, **kargs):
|
def setData(self, *args, **kargs):
|
||||||
"""
|
"""
|
||||||
**Ordered Arguments:**
|
**Ordered Arguments:**
|
||||||
@ -130,6 +280,7 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
*identical* *Deprecated*. This functionality is handled automatically now.
|
*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.clear() ## clear out all old data
|
||||||
self.addPoints(*args, **kargs)
|
self.addPoints(*args, **kargs)
|
||||||
|
|
||||||
@ -183,8 +334,8 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
## note that np.empty initializes object fields to None and string fields to ''
|
## note that np.empty initializes object fields to None and string fields to ''
|
||||||
|
|
||||||
self.data[:len(oldData)] = oldData
|
self.data[:len(oldData)] = oldData
|
||||||
for i in range(len(oldData)):
|
#for i in range(len(oldData)):
|
||||||
oldData[i]['item']._data = self.data[i] ## Make sure items have proper reference to new array
|
#oldData[i]['item']._data = self.data[i] ## Make sure items have proper reference to new array
|
||||||
|
|
||||||
newData = self.data[len(oldData):]
|
newData = self.data[len(oldData):]
|
||||||
newData['size'] = -1 ## indicates to use default size
|
newData['size'] = -1 ## indicates to use default size
|
||||||
@ -217,7 +368,7 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
newData['y'] = kargs['y']
|
newData['y'] = kargs['y']
|
||||||
|
|
||||||
if 'pxMode' in kargs:
|
if 'pxMode' in kargs:
|
||||||
self.setPxMode(kargs['pxMode'], update=False)
|
self.setPxMode(kargs['pxMode'])
|
||||||
|
|
||||||
## Set any extra parameters provided in keyword arguments
|
## Set any extra parameters provided in keyword arguments
|
||||||
for k in ['pen', 'brush', 'symbol', 'size']:
|
for k in ['pen', 'brush', 'symbol', 'size']:
|
||||||
@ -228,12 +379,18 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
if 'data' in kargs:
|
if 'data' in kargs:
|
||||||
self.setPointData(kargs['data'], dataSet=newData)
|
self.setPointData(kargs['data'], dataSet=newData)
|
||||||
|
|
||||||
#self.updateSpots()
|
|
||||||
self.prepareGeometryChange()
|
self.prepareGeometryChange()
|
||||||
self.bounds = [None, None]
|
self.bounds = [None, None]
|
||||||
self.generateSpotItems()
|
self.invalidate()
|
||||||
|
self.updateSpots(newData)
|
||||||
self.sigPlotChanged.emit(self)
|
self.sigPlotChanged.emit(self)
|
||||||
|
|
||||||
|
def invalidate(self):
|
||||||
|
## clear any cached drawing state
|
||||||
|
self.picture = None
|
||||||
|
self.fragments = None
|
||||||
|
self.update()
|
||||||
|
|
||||||
def getData(self):
|
def getData(self):
|
||||||
return self.data['x'], self.data['y']
|
return self.data['x'], self.data['y']
|
||||||
|
|
||||||
@ -263,8 +420,8 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
dataSet['pen'] = pens
|
dataSet['pen'] = pens
|
||||||
else:
|
else:
|
||||||
self.opts['pen'] = fn.mkPen(*args, **kargs)
|
self.opts['pen'] = fn.mkPen(*args, **kargs)
|
||||||
self._spotPixmap = None
|
|
||||||
|
|
||||||
|
dataSet['fragCoords'] = None
|
||||||
if update:
|
if update:
|
||||||
self.updateSpots(dataSet)
|
self.updateSpots(dataSet)
|
||||||
|
|
||||||
@ -285,8 +442,9 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
dataSet['brush'] = brushes
|
dataSet['brush'] = brushes
|
||||||
else:
|
else:
|
||||||
self.opts['brush'] = fn.mkBrush(*args, **kargs)
|
self.opts['brush'] = fn.mkBrush(*args, **kargs)
|
||||||
self._spotPixmap = None
|
#self._spotPixmap = None
|
||||||
|
|
||||||
|
dataSet['fragCoords'] = None
|
||||||
if update:
|
if update:
|
||||||
self.updateSpots(dataSet)
|
self.updateSpots(dataSet)
|
||||||
|
|
||||||
@ -307,6 +465,7 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
self.opts['symbol'] = symbol
|
self.opts['symbol'] = symbol
|
||||||
self._spotPixmap = None
|
self._spotPixmap = None
|
||||||
|
|
||||||
|
dataSet['fragCoords'] = None
|
||||||
if update:
|
if update:
|
||||||
self.updateSpots(dataSet)
|
self.updateSpots(dataSet)
|
||||||
|
|
||||||
@ -327,6 +486,7 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
self.opts['size'] = size
|
self.opts['size'] = size
|
||||||
self._spotPixmap = None
|
self._spotPixmap = None
|
||||||
|
|
||||||
|
dataSet['fragCoords'] = None
|
||||||
if update:
|
if update:
|
||||||
self.updateSpots(dataSet)
|
self.updateSpots(dataSet)
|
||||||
|
|
||||||
@ -346,34 +506,71 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
else:
|
else:
|
||||||
dataSet['data'] = data
|
dataSet['data'] = data
|
||||||
|
|
||||||
def setPxMode(self, mode, update=True):
|
def setPxMode(self, mode):
|
||||||
if self.opts['pxMode'] == mode:
|
if self.opts['pxMode'] == mode:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.opts['pxMode'] = mode
|
self.opts['pxMode'] = mode
|
||||||
self.clearItems()
|
self.invalidate()
|
||||||
if update:
|
|
||||||
self.generateSpotItems()
|
|
||||||
|
|
||||||
def updateSpots(self, dataSet=None):
|
def updateSpots(self, dataSet=None):
|
||||||
if dataSet is None:
|
if dataSet is None:
|
||||||
dataSet = self.data
|
dataSet = self.data
|
||||||
self._maxSpotWidth = 0
|
self._maxSpotWidth = 0
|
||||||
self._maxSpotPxWidth = 0
|
self._maxSpotPxWidth = 0
|
||||||
for spot in dataSet['item']:
|
invalidate = False
|
||||||
spot.updateItem()
|
|
||||||
self.measureSpotSizes(dataSet)
|
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):
|
def measureSpotSizes(self, dataSet):
|
||||||
for spot in dataSet['item']:
|
for rec in dataSet:
|
||||||
## keep track of the maximum spot size and pixel size
|
## keep track of the maximum spot size and pixel size
|
||||||
|
symbol, size, pen, brush = self.getSpotOpts(rec)
|
||||||
width = 0
|
width = 0
|
||||||
pxWidth = 0
|
pxWidth = 0
|
||||||
pen = spot.pen()
|
|
||||||
if self.opts['pxMode']:
|
if self.opts['pxMode']:
|
||||||
pxWidth = spot.size() + pen.width()
|
pxWidth = size + pen.width()
|
||||||
else:
|
else:
|
||||||
width = spot.size()
|
width = size
|
||||||
if pen.isCosmetic():
|
if pen.isCosmetic():
|
||||||
pxWidth += pen.width()
|
pxWidth += pen.width()
|
||||||
else:
|
else:
|
||||||
@ -385,20 +582,11 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
"""Remove all spots from the scatter plot"""
|
"""Remove all spots from the scatter plot"""
|
||||||
self.clearItems()
|
#self.clearItems()
|
||||||
self.data = np.empty(0, dtype=self.data.dtype)
|
self.data = np.empty(0, dtype=self.data.dtype)
|
||||||
self.bounds = [None, None]
|
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):
|
def dataBounds(self, ax, frac=1.0, orthoRange=None):
|
||||||
if frac >= 1.0 and self.bounds[ax] is not None:
|
if frac >= 1.0 and self.bounds[ax] is not None:
|
||||||
return self.bounds[ax]
|
return self.bounds[ax]
|
||||||
@ -436,28 +624,12 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
else:
|
else:
|
||||||
return (scipy.stats.scoreatpercentile(d, 50 - (frac * 50)), scipy.stats.scoreatpercentile(d, 50 + (frac * 50)))
|
return (scipy.stats.scoreatpercentile(d, 50 - (frac * 50)), scipy.stats.scoreatpercentile(d, 50 + (frac * 50)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#def defaultSpotPixmap(self):
|
||||||
|
### Return the default spot pixmap
|
||||||
|
#if self._spotPixmap is None:
|
||||||
def generateSpotItems(self):
|
#self._spotPixmap = makeSymbolPixmap(size=self.opts['size'], brush=self.opts['brush'], pen=self.opts['pen'], symbol=self.opts['symbol'])
|
||||||
if self.opts['pxMode']:
|
#return self._spotPixmap
|
||||||
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 boundingRect(self):
|
def boundingRect(self):
|
||||||
(xmn, xmx) = self.dataBounds(ax=0)
|
(xmn, xmx) = self.dataBounds(ax=0)
|
||||||
@ -470,19 +642,72 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
ymx = 0
|
ymx = 0
|
||||||
return QtCore.QRectF(xmn, ymn, xmx-xmn, ymx-ymn)
|
return QtCore.QRectF(xmn, ymn, xmx-xmn, ymx-ymn)
|
||||||
|
|
||||||
def viewRangeChanged(self):
|
def viewTransformChanged(self):
|
||||||
self.prepareGeometryChange()
|
self.prepareGeometryChange()
|
||||||
GraphicsObject.viewRangeChanged(self)
|
GraphicsObject.viewTransformChanged(self)
|
||||||
self.bounds = [None, None]
|
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):
|
def paint(self, p, *args):
|
||||||
## NOTE: self.paint is disabled by this line in __init__:
|
#p.setPen(fn.mkPen('r'))
|
||||||
## self.setFlag(self.ItemHasNoContents, True)
|
#p.drawRect(self.boundingRect())
|
||||||
p.setPen(fn.mkPen('r'))
|
if self.opts['pxMode']:
|
||||||
p.drawRect(self.boundingRect())
|
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):
|
def points(self):
|
||||||
|
for rec in self.data:
|
||||||
|
if rec['item'] is None:
|
||||||
|
rec['item'] = SpotItem(rec, self)
|
||||||
return self.data['item']
|
return self.data['item']
|
||||||
|
|
||||||
def pointsAt(self, pos):
|
def pointsAt(self, pos):
|
||||||
@ -506,8 +731,8 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
#else:
|
#else:
|
||||||
#print "No hit:", (x, y), (sx, sy)
|
#print "No hit:", (x, y), (sx, sy)
|
||||||
#print " ", (sx-s2x, sy-s2y), (sx+s2x, sy+s2y)
|
#print " ", (sx-s2x, sy-s2y), (sx+s2x, sy+s2y)
|
||||||
pts.sort(lambda a,b: cmp(b.zValue(), a.zValue()))
|
#pts.sort(lambda a,b: cmp(b.zValue(), a.zValue()))
|
||||||
return pts
|
return pts[::-1]
|
||||||
|
|
||||||
|
|
||||||
def mouseClickEvent(self, ev):
|
def mouseClickEvent(self, ev):
|
||||||
@ -524,7 +749,7 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
ev.ignore()
|
ev.ignore()
|
||||||
|
|
||||||
|
|
||||||
class SpotItem(GraphicsItem):
|
class SpotItem(object):
|
||||||
"""
|
"""
|
||||||
Class referring to individual spots in a scatter plot.
|
Class referring to individual spots in a scatter plot.
|
||||||
These can be retrieved by calling ScatterPlotItem.points() or
|
These can be retrieved by calling ScatterPlotItem.points() or
|
||||||
@ -532,14 +757,12 @@ class SpotItem(GraphicsItem):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, data, plot):
|
def __init__(self, data, plot):
|
||||||
GraphicsItem.__init__(self, register=False)
|
#GraphicsItem.__init__(self, register=False)
|
||||||
self._data = data
|
self._data = data
|
||||||
self._plot = plot
|
self._plot = plot
|
||||||
#self._viewBox = None
|
#self.setParentItem(plot)
|
||||||
#self._viewWidget = None
|
#self.setPos(QtCore.QPointF(data['x'], data['y']))
|
||||||
self.setParentItem(plot)
|
#self.updateItem()
|
||||||
self.setPos(QtCore.QPointF(data['x'], data['y']))
|
|
||||||
self.updateItem()
|
|
||||||
|
|
||||||
def data(self):
|
def data(self):
|
||||||
"""Return the user data associated with this spot."""
|
"""Return the user data associated with this spot."""
|
||||||
@ -553,6 +776,12 @@ class SpotItem(GraphicsItem):
|
|||||||
else:
|
else:
|
||||||
return self._data['size']
|
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):
|
def setSize(self, size):
|
||||||
"""Set the size of this spot.
|
"""Set the size of this spot.
|
||||||
If the size is set to -1, then the ScatterPlotItem's default size
|
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"""
|
"""Set the user-data associated with this spot"""
|
||||||
self._data['data'] = data
|
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):
|
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 all symbol options are default, use default pixmap
|
||||||
if symbolOpts == (None, None, -1, ''):
|
#if symbolOpts == (None, None, -1, ''):
|
||||||
pixmap = self._plot.defaultSpotPixmap()
|
#pixmap = self._plot.defaultSpotPixmap()
|
||||||
else:
|
#else:
|
||||||
pixmap = makeSymbolPixmap(size=self.size(), pen=self.pen(), brush=self.brush(), symbol=self.symbol())
|
#pixmap = makeSymbolPixmap(size=self.size(), pen=self.pen(), brush=self.brush(), symbol=self.symbol())
|
||||||
self.setPixmap(pixmap)
|
#self.setPixmap(pixmap)
|
||||||
|
|
||||||
|
|
||||||
class PathSpotItem(SpotItem, QtGui.QGraphicsPathItem):
|
#class PathSpotItem(SpotItem, QtGui.QGraphicsPathItem):
|
||||||
def __init__(self, data, plot):
|
#def __init__(self, data, plot):
|
||||||
QtGui.QGraphicsPathItem.__init__(self)
|
#QtGui.QGraphicsPathItem.__init__(self)
|
||||||
SpotItem.__init__(self, data, plot)
|
#SpotItem.__init__(self, data, plot)
|
||||||
|
|
||||||
def updateItem(self):
|
#def updateItem(self):
|
||||||
QtGui.QGraphicsPathItem.setPath(self, Symbols[self.symbol()])
|
#QtGui.QGraphicsPathItem.setPath(self, Symbols[self.symbol()])
|
||||||
QtGui.QGraphicsPathItem.setPen(self, self.pen())
|
#QtGui.QGraphicsPathItem.setPen(self, self.pen())
|
||||||
QtGui.QGraphicsPathItem.setBrush(self, self.brush())
|
#QtGui.QGraphicsPathItem.setBrush(self, self.brush())
|
||||||
size = self.size()
|
#size = self.size()
|
||||||
self.resetTransform()
|
#self.resetTransform()
|
||||||
self.scale(size, size)
|
#self.scale(size, size)
|
||||||
|
@ -48,6 +48,7 @@ class ViewBox(GraphicsWidget):
|
|||||||
sigRangeChanged = QtCore.Signal(object, object)
|
sigRangeChanged = QtCore.Signal(object, object)
|
||||||
#sigActionPositionChanged = QtCore.Signal(object)
|
#sigActionPositionChanged = QtCore.Signal(object)
|
||||||
sigStateChanged = QtCore.Signal(object)
|
sigStateChanged = QtCore.Signal(object)
|
||||||
|
sigTransformChanged = QtCore.Signal(object)
|
||||||
|
|
||||||
## mouse modes
|
## mouse modes
|
||||||
PanMode = 3
|
PanMode = 3
|
||||||
@ -307,10 +308,6 @@ class ViewBox(GraphicsWidget):
|
|||||||
print("make qrectf failed:", self.state['viewRange'])
|
print("make qrectf failed:", self.state['viewRange'])
|
||||||
raise
|
raise
|
||||||
|
|
||||||
#def viewportTransform(self):
|
|
||||||
##return self.itemTransform(self.childGroup)[0]
|
|
||||||
#return self.childGroup.itemTransform(self)[0]
|
|
||||||
|
|
||||||
def targetRange(self):
|
def targetRange(self):
|
||||||
return [x[:] for x in self.state['targetRange']] ## return copy
|
return [x[:] for x in self.state['targetRange']] ## return copy
|
||||||
|
|
||||||
@ -554,10 +551,10 @@ class ViewBox(GraphicsWidget):
|
|||||||
## Make corrections to range
|
## Make corrections to range
|
||||||
xr = childRange[ax]
|
xr = childRange[ax]
|
||||||
if xr is not None:
|
if xr is not None:
|
||||||
if self.state['autoPan'][0]:
|
if self.state['autoPan'][ax]:
|
||||||
x = sum(xr) * 0.5
|
x = sum(xr) * 0.5
|
||||||
#x = childRect.center().x()
|
#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.setLeft(x-w2)
|
||||||
#childRect.setRight(x+w2)
|
#childRect.setRight(x+w2)
|
||||||
childRange[ax] = [x-w2, x+w2]
|
childRange[ax] = [x-w2, x+w2]
|
||||||
@ -1127,29 +1124,15 @@ class ViewBox(GraphicsWidget):
|
|||||||
m = QtGui.QTransform()
|
m = QtGui.QTransform()
|
||||||
|
|
||||||
## First center the viewport at 0
|
## First center the viewport at 0
|
||||||
#self.childGroup.resetTransform()
|
|
||||||
#self.resetTransform()
|
|
||||||
#center = self.transform().inverted()[0].map(bounds.center())
|
|
||||||
center = 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())
|
m.translate(center.x(), center.y())
|
||||||
#print " not inverted; translate", center.x(), -center.y()
|
|
||||||
|
|
||||||
## Now scale and translate properly
|
## Now scale and translate properly
|
||||||
m.scale(scale[0], scale[1])
|
m.scale(scale[0], scale[1])
|
||||||
st = Point(vr.center())
|
st = Point(vr.center())
|
||||||
#st = translate
|
|
||||||
m.translate(-st[0], -st[1])
|
m.translate(-st[0], -st[1])
|
||||||
|
|
||||||
self.childGroup.setTransform(m)
|
self.childGroup.setTransform(m)
|
||||||
#self.setTransform(m)
|
|
||||||
#self.prepareGeometryChange()
|
|
||||||
|
|
||||||
#self.currentScale = scale
|
|
||||||
|
|
||||||
if changed[0]:
|
if changed[0]:
|
||||||
self.sigXRangeChanged.emit(self, tuple(self.state['viewRange'][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]))
|
self.sigYRangeChanged.emit(self, tuple(self.state['viewRange'][1]))
|
||||||
if any(changed):
|
if any(changed):
|
||||||
self.sigRangeChanged.emit(self, self.state['viewRange'])
|
self.sigRangeChanged.emit(self, self.state['viewRange'])
|
||||||
|
|
||||||
|
self.sigTransformChanged.emit(self)
|
||||||
|
|
||||||
def paint(self, p, opt, widget):
|
def paint(self, p, opt, widget):
|
||||||
if self.border is not None:
|
if self.border is not None:
|
||||||
@ -1165,20 +1150,6 @@ class ViewBox(GraphicsWidget):
|
|||||||
#p.fillRect(bounds, QtGui.QColor(0, 0, 0))
|
#p.fillRect(bounds, QtGui.QColor(0, 0, 0))
|
||||||
p.drawPath(bounds)
|
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):
|
def updateBackground(self):
|
||||||
bg = self.state['background']
|
bg = self.state['background']
|
||||||
if bg is None:
|
if bg is None:
|
||||||
|
@ -21,13 +21,13 @@ def mkQApp():
|
|||||||
class GraphicsWindow(GraphicsLayoutWidget):
|
class GraphicsWindow(GraphicsLayoutWidget):
|
||||||
def __init__(self, title=None, size=(800,600), **kargs):
|
def __init__(self, title=None, size=(800,600), **kargs):
|
||||||
mkQApp()
|
mkQApp()
|
||||||
self.win = QtGui.QMainWindow()
|
#self.win = QtGui.QMainWindow()
|
||||||
GraphicsLayoutWidget.__init__(self, **kargs)
|
GraphicsLayoutWidget.__init__(self, **kargs)
|
||||||
self.win.setCentralWidget(self)
|
#self.win.setCentralWidget(self)
|
||||||
self.win.resize(*size)
|
self.resize(*size)
|
||||||
if title is not None:
|
if title is not None:
|
||||||
self.win.setWindowTitle(title)
|
self.setWindowTitle(title)
|
||||||
self.win.show()
|
self.show()
|
||||||
|
|
||||||
|
|
||||||
class TabWindow(QtGui.QMainWindow):
|
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):
|
class GLScatterPlotItem(GLGraphicsItem):
|
||||||
"""Draws points at a list of 3D positions."""
|
"""Draws points at a list of 3D positions."""
|
||||||
|
|
||||||
def __init__(self, data=None):
|
def __init__(self, **kwds):
|
||||||
GLGraphicsItem.__init__(self)
|
GLGraphicsItem.__init__(self)
|
||||||
self.data = []
|
self.pos = []
|
||||||
if data is not None:
|
self.size = 10
|
||||||
self.setData(data)
|
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
|
pos (N,3) array of floats specifying point locations.
|
||||||
color (r,g,b,a) tuple of floats (0.0-1.0) or QColor
|
color (N,4) array of floats (0.0-1.0) specifying
|
||||||
size (float) diameter of spot in pixels
|
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.
|
||||||
==================== ==================================================
|
==================== ==================================================
|
||||||
"""
|
"""
|
||||||
|
args = ['pos', 'color', 'size', 'pxMode']
|
||||||
|
for k in kwds.keys():
|
||||||
self.data = data
|
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()
|
self.update()
|
||||||
|
|
||||||
|
|
||||||
def initializeGL(self):
|
def initializeGL(self):
|
||||||
|
|
||||||
|
## Generate texture for rendering points
|
||||||
w = 64
|
w = 64
|
||||||
def fn(x,y):
|
def fn(x,y):
|
||||||
r = ((x-w/2.)**2 + (y-w/2.)**2) ** 0.5
|
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_S, GL_CLAMP_TO_EDGE)
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
|
||||||
|
|
||||||
for pt in self.data:
|
if self.pxMode:
|
||||||
pos = pt['pos']
|
glVertexPointerf(self.pos)
|
||||||
try:
|
if isinstance(self.color, np.ndarray):
|
||||||
color = pt['color']
|
glColorPointerf(self.color)
|
||||||
except KeyError:
|
else:
|
||||||
color = (1,1,1,1)
|
if isinstance(self.color, QtGui.QColor):
|
||||||
try:
|
glColor4f(*fn.glColor(self.color))
|
||||||
size = pt['size']
|
else:
|
||||||
except KeyError:
|
glColor4f(*self.color)
|
||||||
size = 10
|
|
||||||
|
|
||||||
if isinstance(color, QtGui.QColor):
|
|
||||||
color = fn.glColor(color)
|
|
||||||
|
|
||||||
pxSize = self.view().pixelSize(QtGui.QVector3D(*pos))
|
|
||||||
|
|
||||||
glPointSize(size / pxSize)
|
if isinstance(self.size, np.ndarray):
|
||||||
glBegin( GL_POINTS )
|
raise Exception('Array size not yet supported in pxMode (hopefully soon)')
|
||||||
glColor4f(*color) # x is blue
|
|
||||||
#glNormal3f(size, 0, 0)
|
glPointSize(self.size)
|
||||||
glVertex3f(*pos)
|
glEnableClientState(GL_VERTEX_ARRAY)
|
||||||
glEnd()
|
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)."""
|
enabled via enableMouse() (but ordinarily, we use ViewBox for this functionality)."""
|
||||||
|
|
||||||
sigRangeChanged = QtCore.Signal(object, object)
|
sigRangeChanged = QtCore.Signal(object, object)
|
||||||
|
sigTransformChanged = QtCore.Signal(object)
|
||||||
sigMouseReleased = QtCore.Signal(object)
|
sigMouseReleased = QtCore.Signal(object)
|
||||||
sigSceneMouseMoved = QtCore.Signal(object)
|
sigSceneMouseMoved = QtCore.Signal(object)
|
||||||
#sigRegionChanged = QtCore.Signal(object)
|
#sigRegionChanged = QtCore.Signal(object)
|
||||||
@ -212,6 +213,7 @@ class GraphicsView(QtGui.QGraphicsView):
|
|||||||
self.fitInView(self.range, QtCore.Qt.IgnoreAspectRatio)
|
self.fitInView(self.range, QtCore.Qt.IgnoreAspectRatio)
|
||||||
|
|
||||||
self.sigRangeChanged.emit(self, self.range)
|
self.sigRangeChanged.emit(self, self.range)
|
||||||
|
self.sigTransformChanged.emit(self)
|
||||||
|
|
||||||
if propagate:
|
if propagate:
|
||||||
for v in self.lockedViewports:
|
for v in self.lockedViewports:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user