pyqtgraph/widgets/GradientWidget.py

621 lines
22 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
if __name__ == '__main__':
import os, sys
path = os.path.join(os.path.dirname(__file__), '..', '..')
sys.path = [path] + sys.path
from pyqtgraph.Qt import QtGui, QtCore
from GraphicsView import GraphicsView
from pyqtgraph.graphicsItems.GradientEditorItem import GradientEditorItem
import weakref
import numpy as np
import collections
__all__ = ['TickSlider', 'GradientWidget', 'BlackWhiteSlider']
class GradientWidget(GraphicsView):
sigGradientChanged = QtCore.Signal(object)
def __init__(self, parent=None, orientation='bottom', *args, **kargs):
GraphicsView.__init__(self, parent, useOpenGL=False, background=None)
self.maxDim = 27
kargs['tickPen'] = 'k'
self.item = GradientEditorItem(*args, **kargs)
self.item.sigGradientChanged.connect(self.sigGradientChanged)
self.setCentralItem(self.item)
self.setOrientation(orientation)
self.setCacheMode(self.CacheNone)
self.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.TextAntialiasing)
self.setFrameStyle(QtGui.QFrame.NoFrame | QtGui.QFrame.Plain)
self.setBackgroundRole(QtGui.QPalette.NoRole)
#self.setBackgroundBrush(QtGui.QBrush(QtCore.Qt.NoBrush))
#self.setAutoFillBackground(False)
#self.setAttribute(QtCore.Qt.WA_PaintOnScreen, False)
#self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent, True)
def setOrientation(self, ort):
self.item.setOrientation(ort)
self.orientation = ort
self.setMaxDim()
def setMaxDim(self, mx=None):
if mx is None:
mx = self.maxDim
else:
self.maxDim = mx
if self.orientation in ['bottom', 'top']:
self.setFixedHeight(mx)
self.setMaximumWidth(16777215)
else:
self.setFixedWidth(mx)
self.setMaximumHeight(16777215)
def __getattr__(self, attr):
### wrap methods from GradientEditorItem
return getattr(self.item, attr)
#Gradients = collections.OrderedDict([
#('thermal', {'ticks': [(0.3333, (185, 0, 0, 255)), (0.6666, (255, 220, 0, 255)), (1, (255, 255, 255, 255)), (0, (0, 0, 0, 255))], 'mode': 'rgb'}),
#('flame', {'ticks': [(0.2, (7, 0, 220, 255)), (0.5, (236, 0, 134, 255)), (0.8, (246, 246, 0, 255)), (1.0, (255, 255, 255, 255)), (0.0, (0, 0, 0, 255))], 'mode': 'rgb'}),
#('yellowy', {'ticks': [(0.0, (0, 0, 0, 255)), (0.2328863796753704, (32, 0, 129, 255)), (0.8362738179251941, (255, 255, 0, 255)), (0.5257586450247, (115, 15, 255, 255)), (1.0, (255, 255, 255, 255))], 'mode': 'rgb'} ),
#('bipolar', {'ticks': [(0.0, (0, 255, 255, 255)), (1.0, (255, 255, 0, 255)), (0.5, (0, 0, 0, 255)), (0.25, (0, 0, 255, 255)), (0.75, (255, 0, 0, 255))], 'mode': 'rgb'}),
#('spectrum', {'ticks': [(1.0, (255, 0, 255, 255)), (0.0, (255, 0, 0, 255))], 'mode': 'hsv'}),
#('cyclic', {'ticks': [(0.0, (255, 0, 4, 255)), (1.0, (255, 0, 0, 255))], 'mode': 'hsv'}),
#('greyclip', {'ticks': [(0.0, (0, 0, 0, 255)), (0.99, (255, 255, 255, 255)), (1.0, (255, 0, 0, 255))], 'mode': 'rgb'}),
#])
#class TickSlider(GraphicsView):
#def __init__(self, parent=None, orientation='bottom', allowAdd=True, **kargs):
#self.orientation = orientation
#self.length = 100
#self.tickSize = 15
#self.ticks = {}
#self.maxDim = 20
#GraphicsView.__init__(self, parent, useOpenGL=False)
#self.allowAdd = allowAdd
##self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
##self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
##self.setTransformationAnchor(QtGui.QGraphicsView.NoAnchor)
##self.setResizeAnchor(QtGui.QGraphicsView.AnchorViewCenter)
#self.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.TextAntialiasing)
#self.orientations = {
#'left': (270, 1, -1),
#'right': (270, 1, 1),
#'top': (0, 1, -1),
#'bottom': (0, 1, 1)
#}
##self.scene = QtGui.QGraphicsScene()
##self.setScene(self.scene)
#self.setOrientation(orientation)
#self.setFrameStyle(QtGui.QFrame.NoFrame | QtGui.QFrame.Plain)
#self.setBackgroundRole(QtGui.QPalette.NoRole)
#self.setMouseTracking(True)
#def keyPressEvent(self, ev):
#ev.ignore()
#def setMaxDim(self, mx=None):
#if mx is None:
#mx = self.maxDim
#else:
#self.maxDim = mx
#if self.orientation in ['bottom', 'top']:
#self.setFixedHeight(mx)
#self.setMaximumWidth(16777215)
#else:
#self.setFixedWidth(mx)
#self.setMaximumHeight(16777215)
#def setOrientation(self, ort):
#self.orientation = ort
#self.resetTransform()
#self.rotate(self.orientations[ort][0])
#self.scale(*self.orientations[ort][1:])
#self.setMaxDim()
#def addTick(self, x, color=None, movable=True):
#if color is None:
#color = QtGui.QColor(255,255,255)
#tick = Tick(self, [x*self.length, 0], color, movable, self.tickSize)
#self.ticks[tick] = x
#self.scene.addItem(tick)
#return tick
#def removeTick(self, tick):
#del self.ticks[tick]
#self.scene.removeItem(tick)
#def tickMoved(self, tick, pos):
##print "tick changed"
### Correct position of tick if it has left bounds.
#newX = min(max(0, pos.x()), self.length)
#pos.setX(newX)
#tick.setPos(pos)
#self.ticks[tick] = float(newX) / self.length
#def tickClicked(self, tick, ev):
#if ev.button() == QtCore.Qt.RightButton:
#self.removeTick(tick)
#def widgetLength(self):
#if self.orientation in ['bottom', 'top']:
#return self.width()
#else:
#return self.height()
#def resizeEvent(self, ev):
#wlen = max(40, self.widgetLength())
#self.setLength(wlen-self.tickSize)
#bounds = self.scene().itemsBoundingRect()
#bounds.setLeft(min(-self.tickSize*0.5, bounds.left()))
#bounds.setRight(max(self.length + self.tickSize, bounds.right()))
##bounds.setTop(min(bounds.top(), self.tickSize))
##bounds.setBottom(max(0, bounds.bottom()))
#self.setSceneRect(bounds)
#self.fitInView(bounds, QtCore.Qt.KeepAspectRatio)
#def setLength(self, newLen):
#for t, x in self.ticks.items():
#t.setPos(x * newLen, t.pos().y())
#self.length = float(newLen)
#def mousePressEvent(self, ev):
#QtGui.QGraphicsView.mousePressEvent(self, ev)
#self.ignoreRelease = False
#for i in self.items(ev.pos()):
#if isinstance(i, Tick):
#self.ignoreRelease = True
#break
##if len(self.items(ev.pos())) > 0: ## Let items handle their own clicks
##self.ignoreRelease = True
#def mouseReleaseEvent(self, ev):
#QtGui.QGraphicsView.mouseReleaseEvent(self, ev)
#if self.ignoreRelease:
#return
#pos = self.mapToScene(ev.pos())
#if ev.button() == QtCore.Qt.LeftButton and self.allowAdd:
#if pos.x() < 0 or pos.x() > self.length:
#return
#if pos.y() < 0 or pos.y() > self.tickSize:
#return
#pos.setX(min(max(pos.x(), 0), self.length))
#self.addTick(pos.x()/self.length)
#elif ev.button() == QtCore.Qt.RightButton:
#self.showMenu(ev)
#def showMenu(self, ev):
#pass
#def setTickColor(self, tick, color):
#tick = self.getTick(tick)
#tick.color = color
#tick.setBrush(QtGui.QBrush(QtGui.QColor(tick.color)))
#def setTickValue(self, tick, val):
#tick = self.getTick(tick)
#val = min(max(0.0, val), 1.0)
#x = val * self.length
#pos = tick.pos()
#pos.setX(x)
#tick.setPos(pos)
#self.ticks[tick] = val
#def tickValue(self, tick):
#tick = self.getTick(tick)
#return self.ticks[tick]
#def getTick(self, tick):
#if type(tick) is int:
#tick = self.listTicks()[tick][0]
#return tick
#def mouseMoveEvent(self, ev):
#QtGui.QGraphicsView.mouseMoveEvent(self, ev)
##print ev.pos(), ev.buttons()
#def listTicks(self):
#ticks = self.ticks.items()
#ticks.sort(lambda a,b: cmp(a[1], b[1]))
#return ticks
#class GradientWidget(TickSlider):
#sigGradientChanged = QtCore.Signal(object)
#def __init__(self, *args, **kargs):
#self.currentTick = None
#self.currentTickColor = None
#self.rectSize = 15
#self.gradRect = QtGui.QGraphicsRectItem(QtCore.QRectF(0, -self.rectSize, 100, self.rectSize))
#self.backgroundRect = QtGui.QGraphicsRectItem(QtCore.QRectF(0, -self.rectSize, 100, self.rectSize))
#self.backgroundRect.setBrush(QtGui.QBrush(QtCore.Qt.DiagCrossPattern))
#self.colorMode = 'rgb'
#TickSlider.__init__(self, *args, **kargs)
#self.colorDialog = QtGui.QColorDialog()
#self.colorDialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True)
#self.colorDialog.setOption(QtGui.QColorDialog.DontUseNativeDialog, True)
#self.colorDialog.currentColorChanged.connect(self.currentColorChanged)
#self.colorDialog.rejected.connect(self.currentColorRejected)
##self.gradient = QtGui.QLinearGradient(QtCore.QPointF(0,0), QtCore.QPointF(100,0))
#self.scene.addItem(self.backgroundRect)
#self.scene.addItem(self.gradRect)
#self.setMaxDim(self.rectSize + self.tickSize)
##self.btn = QtGui.QPushButton('RGB')
##self.btnProxy = self.scene.addWidget(self.btn)
##self.btnProxy.setFlag(self.btnProxy.ItemIgnoresTransformations)
##self.btnProxy.scale(0.7, 0.7)
##self.btnProxy.translate(-self.btnProxy.sceneBoundingRect().width()+self.tickSize/2., 0)
##if self.orientation == 'bottom':
##self.btnProxy.translate(0, -self.rectSize)
#self.rgbAction = QtGui.QAction('RGB', self)
#self.rgbAction.setCheckable(True)
#self.rgbAction.triggered.connect(lambda: self.setColorMode('rgb'))
#self.hsvAction = QtGui.QAction('HSV', self)
#self.hsvAction.setCheckable(True)
#self.hsvAction.triggered.connect(lambda: self.setColorMode('hsv'))
#self.menu = QtGui.QMenu()
### build context menu of gradients
#global Gradients
#for g in Gradients:
#px = QtGui.QPixmap(100, 15)
#p = QtGui.QPainter(px)
#self.restoreState(Gradients[g])
#grad = self.getGradient()
#brush = QtGui.QBrush(grad)
#p.fillRect(QtCore.QRect(0, 0, 100, 15), brush)
#p.end()
#label = QtGui.QLabel()
#label.setPixmap(px)
#label.setContentsMargins(1, 1, 1, 1)
#act = QtGui.QWidgetAction(self)
#act.setDefaultWidget(label)
#act.triggered.connect(self.contextMenuClicked)
#act.name = g
#self.menu.addAction(act)
#self.menu.addSeparator()
#self.menu.addAction(self.rgbAction)
#self.menu.addAction(self.hsvAction)
#for t in self.ticks.keys():
#self.removeTick(t)
#self.addTick(0, QtGui.QColor(0,0,0), True)
#self.addTick(1, QtGui.QColor(255,0,0), True)
#self.setColorMode('rgb')
#self.updateGradient()
#def showMenu(self, ev):
#self.menu.popup(ev.globalPos())
#def contextMenuClicked(self, b):
#global Gradients
#act = self.sender()
#self.restoreState(Gradients[act.name])
#def setColorMode(self, cm):
#if cm not in ['rgb', 'hsv']:
#raise Exception("Unknown color mode %s. Options are 'rgb' and 'hsv'." % str(cm))
#try:
#self.rgbAction.blockSignals(True)
#self.hsvAction.blockSignals(True)
#self.rgbAction.setChecked(cm == 'rgb')
#self.hsvAction.setChecked(cm == 'hsv')
#finally:
#self.rgbAction.blockSignals(False)
#self.hsvAction.blockSignals(False)
#self.colorMode = cm
#self.updateGradient()
#def updateGradient(self):
#self.gradient = self.getGradient()
#self.gradRect.setBrush(QtGui.QBrush(self.gradient))
#self.sigGradientChanged.emit(self)
#def setLength(self, newLen):
#TickSlider.setLength(self, newLen)
#self.backgroundRect.setRect(0, -self.rectSize, newLen, self.rectSize)
#self.gradRect.setRect(0, -self.rectSize, newLen, self.rectSize)
#self.updateGradient()
#def currentColorChanged(self, color):
#if color.isValid() and self.currentTick is not None:
#self.setTickColor(self.currentTick, color)
#self.updateGradient()
#def currentColorRejected(self):
#self.setTickColor(self.currentTick, self.currentTickColor)
#self.updateGradient()
#def tickClicked(self, tick, ev):
#if ev.button() == QtCore.Qt.LeftButton:
#if not tick.colorChangeAllowed:
#return
#self.currentTick = tick
#self.currentTickColor = tick.color
#self.colorDialog.setCurrentColor(tick.color)
#self.colorDialog.open()
##color = QtGui.QColorDialog.getColor(tick.color, self, "Select Color", QtGui.QColorDialog.ShowAlphaChannel)
##if color.isValid():
##self.setTickColor(tick, color)
##self.updateGradient()
#elif ev.button() == QtCore.Qt.RightButton:
#if not tick.removeAllowed:
#return
#if len(self.ticks) > 2:
#self.removeTick(tick)
#self.updateGradient()
#def tickMoved(self, tick, pos):
#TickSlider.tickMoved(self, tick, pos)
#self.updateGradient()
#def getGradient(self):
#g = QtGui.QLinearGradient(QtCore.QPointF(0,0), QtCore.QPointF(self.length,0))
#if self.colorMode == 'rgb':
#ticks = self.listTicks()
#g.setStops([(x, QtGui.QColor(t.color)) for t,x in ticks])
#elif self.colorMode == 'hsv': ## HSV mode is approximated for display by interpolating 10 points between each stop
#ticks = self.listTicks()
#stops = []
#stops.append((ticks[0][1], ticks[0][0].color))
#for i in range(1,len(ticks)):
#x1 = ticks[i-1][1]
#x2 = ticks[i][1]
#dx = (x2-x1) / 10.
#for j in range(1,10):
#x = x1 + dx*j
#stops.append((x, self.getColor(x)))
#stops.append((x2, self.getColor(x2)))
#g.setStops(stops)
#return g
#def getColor(self, x, toQColor=True):
#ticks = self.listTicks()
#if x <= ticks[0][1]:
#c = ticks[0][0].color
#if toQColor:
#return QtGui.QColor(c) # always copy colors before handing them out
#else:
#return (c.red(), c.green(), c.blue(), c.alpha())
#if x >= ticks[-1][1]:
#c = ticks[-1][0].color
#if toQColor:
#return QtGui.QColor(c) # always copy colors before handing them out
#else:
#return (c.red(), c.green(), c.blue(), c.alpha())
#x2 = ticks[0][1]
#for i in range(1,len(ticks)):
#x1 = x2
#x2 = ticks[i][1]
#if x1 <= x and x2 >= x:
#break
#dx = (x2-x1)
#if dx == 0:
#f = 0.
#else:
#f = (x-x1) / dx
#c1 = ticks[i-1][0].color
#c2 = ticks[i][0].color
#if self.colorMode == 'rgb':
#r = c1.red() * (1.-f) + c2.red() * f
#g = c1.green() * (1.-f) + c2.green() * f
#b = c1.blue() * (1.-f) + c2.blue() * f
#a = c1.alpha() * (1.-f) + c2.alpha() * f
#if toQColor:
#return QtGui.QColor(r, g, b,a)
#else:
#return (r,g,b,a)
#elif self.colorMode == 'hsv':
#h1,s1,v1,_ = c1.getHsv()
#h2,s2,v2,_ = c2.getHsv()
#h = h1 * (1.-f) + h2 * f
#s = s1 * (1.-f) + s2 * f
#v = v1 * (1.-f) + v2 * f
#c = QtGui.QColor()
#c.setHsv(h,s,v)
#if toQColor:
#return c
#else:
#return (c.red(), c.green(), c.blue(), c.alpha())
#def getLookupTable(self, nPts, alpha=True):
#"""Return an RGB/A lookup table."""
#if alpha:
#table = np.empty((nPts,4), dtype=np.ubyte)
#else:
#table = np.empty((nPts,3), dtype=np.ubyte)
#for i in range(nPts):
#x = float(i)/(nPts-1)
#color = self.getColor(x, toQColor=False)
#table[i] = color[:table.shape[1]]
#return table
#def mouseReleaseEvent(self, ev):
#TickSlider.mouseReleaseEvent(self, ev)
#self.updateGradient()
#def addTick(self, x, color=None, movable=True):
#if color is None:
#color = self.getColor(x)
#t = TickSlider.addTick(self, x, color=color, movable=movable)
#t.colorChangeAllowed = True
#t.removeAllowed = True
#return t
#def saveState(self):
#ticks = []
#for t in self.ticks:
#c = t.color
#ticks.append((self.ticks[t], (c.red(), c.green(), c.blue(), c.alpha())))
#state = {'mode': self.colorMode, 'ticks': ticks}
#return state
#def restoreState(self, state):
#self.setColorMode(state['mode'])
#for t in self.ticks.keys():
#self.removeTick(t)
#for t in state['ticks']:
#c = QtGui.QColor(*t[1])
#self.addTick(t[0], c)
#self.updateGradient()
#class BlackWhiteSlider(GradientWidget):
#def __init__(self, parent):
#GradientWidget.__init__(self, parent)
#self.getTick(0).colorChangeAllowed = False
#self.getTick(1).colorChangeAllowed = False
#self.allowAdd = False
#self.setTickColor(self.getTick(1), QtGui.QColor(255,255,255))
#self.setOrientation('right')
#def getLevels(self):
#return (self.tickValue(0), self.tickValue(1))
#def setLevels(self, black, white):
#self.setTickValue(0, black)
#self.setTickValue(1, white)
#class GammaWidget(TickSlider):
#pass
#class Tick(QtGui.QGraphicsPolygonItem):
#def __init__(self, view, pos, color, movable=True, scale=10):
##QObjectWorkaround.__init__(self)
#self.movable = movable
#self.view = weakref.ref(view)
#self.scale = scale
#self.color = color
##self.endTick = endTick
#self.pg = QtGui.QPolygonF([QtCore.QPointF(0,0), QtCore.QPointF(-scale/3**0.5,scale), QtCore.QPointF(scale/3**0.5,scale)])
#QtGui.QGraphicsPolygonItem.__init__(self, self.pg)
#self.setPos(pos[0], pos[1])
#self.setFlags(QtGui.QGraphicsItem.ItemIsMovable | QtGui.QGraphicsItem.ItemIsSelectable)
#self.setBrush(QtGui.QBrush(QtGui.QColor(self.color)))
#if self.movable:
#self.setZValue(1)
#else:
#self.setZValue(0)
##def x(self):
##return self.pos().x()/100.
#def mouseMoveEvent(self, ev):
##print self, "move", ev.scenePos()
#if not self.movable:
#return
#if not ev.buttons() & QtCore.Qt.LeftButton:
#return
#newPos = ev.scenePos() + self.mouseOffset
#newPos.setY(self.pos().y())
##newPos.setX(min(max(newPos.x(), 0), 100))
#self.setPos(newPos)
#self.view().tickMoved(self, newPos)
#self.movedSincePress = True
##self.emit(QtCore.SIGNAL('tickChanged'), self)
#ev.accept()
#def mousePressEvent(self, ev):
#self.movedSincePress = False
#if ev.button() == QtCore.Qt.LeftButton:
#ev.accept()
#self.mouseOffset = self.pos() - ev.scenePos()
#self.pressPos = ev.scenePos()
#elif ev.button() == QtCore.Qt.RightButton:
#ev.accept()
##if self.endTick:
##return
##self.view.tickChanged(self, delete=True)
#def mouseReleaseEvent(self, ev):
##print self, "release", ev.scenePos()
#if not self.movedSincePress:
#self.view().tickClicked(self, ev)
##if ev.button() == QtCore.Qt.LeftButton and ev.scenePos() == self.pressPos:
##color = QtGui.QColorDialog.getColor(self.color, None, "Select Color", QtGui.QColorDialog.ShowAlphaChannel)
##if color.isValid():
##self.color = color
##self.setBrush(QtGui.QBrush(QtGui.QColor(self.color)))
###self.emit(QtCore.SIGNAL('tickChanged'), self)
##self.view.tickChanged(self)
if __name__ == '__main__':
app = QtGui.QApplication([])
w = QtGui.QMainWindow()
w.show()
w.resize(400,400)
cw = QtGui.QWidget()
w.setCentralWidget(cw)
l = QtGui.QGridLayout()
l.setSpacing(0)
cw.setLayout(l)
w1 = GradientWidget(orientation='top')
w2 = GradientWidget(orientation='right', allowAdd=False)
#w2.setTickColor(1, QtGui.QColor(255,255,255))
w3 = GradientWidget(orientation='bottom')
w4 = GradientWidget(orientation='left')
label = QtGui.QLabel("""
- Click a triangle to change its color
- Drag triangles to move
- Click in an empty area to add a new color
(adding is disabled for the right-side widget)
- Right click a triangle to remove
""")
l.addWidget(w1, 0, 1)
l.addWidget(w2, 1, 2)
l.addWidget(w3, 2, 1)
l.addWidget(w4, 1, 0)
l.addWidget(label, 1, 1)
## Start Qt event loop unless running in interactive mode.
import sys
if sys.flags.interactive != 1:
app.exec_()