From 0cfc9cd440d98bdc7d80ca906e21fe56a6db4239 Mon Sep 17 00:00:00 2001 From: Ogi Moore Date: Thu, 29 Jul 2021 20:05:44 -0700 Subject: [PATCH] Add GLGradientLegendItem to pyqtgraph Huge thank you to @feketeimre for the initial PR for this feature Thank you to @pijyoi for re-implementing with no changes needed to GLViewWidget and supporting QOpenGLWidget vs. QGLWidget. --- examples/GLGradientLegendItem.py | 43 +++++++ examples/utils.py | 1 + pyqtgraph/opengl/__init__.py | 21 ++-- .../opengl/items/GLGradientLegendItem.py | 107 ++++++++++++++++++ 4 files changed, 162 insertions(+), 10 deletions(-) create mode 100644 examples/GLGradientLegendItem.py create mode 100644 pyqtgraph/opengl/items/GLGradientLegendItem.py diff --git a/examples/GLGradientLegendItem.py b/examples/GLGradientLegendItem.py new file mode 100644 index 00000000..e0d243cf --- /dev/null +++ b/examples/GLGradientLegendItem.py @@ -0,0 +1,43 @@ +import initExample + +import pyqtgraph as pg +import pyqtgraph.opengl as gl +import numpy + +app = pg.mkQApp() +w = gl.GLViewWidget() +w.show() +w.setWindowTitle("pyqtgraph example: GLGradientLegendItem") +w.setCameraPosition(distance=60) + +gx = gl.GLGridItem() +gx.rotate(90, 0, 1, 0) +w.addItem(gx) + +md = gl.MeshData.cylinder(rows=10, cols=20, radius=[5.0, 5], length=20.0) +md._vertexes[:, 2] = md._vertexes[:, 2] - 10 + +# set color based on z coordinates +color_map = pg.colormap.get("CET-L10") + +h = md.vertexes()[:, 2] +# remember these +h_max, h_min = h.max(), h.min() +h = (h - h_min) / (h_max - h_min) +colors = color_map.map(h, mode="float") +md.setFaceColors(colors) +m = gl.GLMeshItem(meshdata=md, smooth=True) +w.addItem(m) + +legendLabels = numpy.linspace(h_max, h_min, 5) +legendPos = numpy.linspace(1, 0, 5) +legend = dict(zip(map(str, legendLabels), legendPos)) + +gll = gl.GLGradientLegendItem( + pos=(10, 10), size=(50, 300), gradient=color_map, labels=legend +) +w.addItem(gll) + +## Start Qt event loop unless running in interactive mode. +if __name__ == "__main__": + pg.exec() diff --git a/examples/utils.py b/examples/utils.py index 911f55c7..53710795 100644 --- a/examples/utils.py +++ b/examples/utils.py @@ -76,6 +76,7 @@ examples_ = OrderedDict([ ('Text', 'GLTextItem.py'), ('BarGraph', 'GLBarGraphItem.py'), ('Painter', 'GLPainterItem.py'), + ('Gradient Legend', 'GLGradientLegendItem.py') ])), ('Widgets', OrderedDict([ ('PlotWidget', 'PlotWidget.py'), diff --git a/pyqtgraph/opengl/__init__.py b/pyqtgraph/opengl/__init__.py index 8e53e81e..8db7c4a5 100644 --- a/pyqtgraph/opengl/__init__.py +++ b/pyqtgraph/opengl/__init__.py @@ -4,17 +4,18 @@ from .GLViewWidget import GLViewWidget #from .. import importAll #importAll('items', globals(), locals()) -from .items.GLGridItem import * -from .items.GLBarGraphItem import * -from .items.GLScatterPlotItem import * -from .items.GLMeshItem import * -from .items.GLLinePlotItem import * -from .items.GLAxisItem import * -from .items.GLImageItem import * -from .items.GLSurfacePlotItem import * -from .items.GLBoxItem import * -from .items.GLVolumeItem import * +from .items.GLGridItem import * +from .items.GLBarGraphItem import * +from .items.GLScatterPlotItem import * +from .items.GLMeshItem import * +from .items.GLLinePlotItem import * +from .items.GLAxisItem import * +from .items.GLImageItem import * +from .items.GLSurfacePlotItem import * +from .items.GLBoxItem import * +from .items.GLVolumeItem import * from .items.GLTextItem import * +from .items.GLGradientLegendItem import * from .MeshData import MeshData ## for backward compatibility: diff --git a/pyqtgraph/opengl/items/GLGradientLegendItem.py b/pyqtgraph/opengl/items/GLGradientLegendItem.py new file mode 100644 index 00000000..1409a160 --- /dev/null +++ b/pyqtgraph/opengl/items/GLGradientLegendItem.py @@ -0,0 +1,107 @@ +from ... Qt import QtCore, QtGui +from ... import functions as fn +from OpenGL.GL import * +from ..GLGraphicsItem import GLGraphicsItem + +__all__ = ['GLGradientLegendItem'] + +class GLGradientLegendItem(GLGraphicsItem): + """ + Displays legend colorbar on the screen. + """ + + def __init__(self, **kwds): + """ + Arguments: + pos: position of the colorbar on the screen, from the top left corner, in pixels + size: size of the colorbar without the text, in pixels + gradient: a pg.ColorMap used to color the colorbar + labels: a dict of "text":value to display next to the colorbar. + The value corresponds to a position in the gradient from 0 to 1. + fontColor: sets the color of the texts + #Todo: + size as percentage + legend title + """ + GLGraphicsItem.__init__(self) + glopts = kwds.pop("glOptions", "additive") + self.setGLOptions(glopts) + self.pos = (10, 10) + self.size = (10, 100) + self.fontColor = (1.0, 1.0, 1.0, 1.0) + self.stops = None + self.colors = None + self.gradient = None + self.setData(**kwds) + + def setData(self, **kwds): + args = ["size", "pos", "gradient", "labels", "fontColor"] + for k in kwds.keys(): + if k not in args: + raise Exception( + "Invalid keyword argument: %s (allowed arguments are %s)" + % (k, str(args)) + ) + + self.antialias = False + + for arg in args: + if arg in kwds: + setattr(self, arg, kwds[arg]) + + ##todo: add title + ##todo: take more gradient types + if self.gradient is not None and hasattr(self.gradient, "getStops"): + self.stops, self.colors = self.gradient.getStops("float") + self.qgradient = self.gradient.getGradient() + self.update() + + def paint(self): + if self.pos is None or self.stops is None: + return + self.setupGLState() + glMatrixMode(GL_PROJECTION) + glPushMatrix() + glLoadIdentity() + glOrtho(0.0, self.view().width(), self.view().height(), 0.0, -1.0, 10.0) + glMatrixMode(GL_MODELVIEW) + glLoadIdentity() + glDisable(GL_CULL_FACE) + + glClear(GL_DEPTH_BUFFER_BIT) + + # draw the colorbar + glTranslate(self.pos[0], self.pos[1], 0) + glScale(self.size[0], self.size[1], 0) + glBegin(GL_QUAD_STRIP) + for p, c in zip(self.stops, self.colors): + glColor4f(*c) + glVertex2d(0, 1 - p) + glColor4f(*c) + glVertex2d(1, 1 - p) + glEnd() + + # draw labels + # scaling and translate doesnt work on rendertext + fontColor = QtGui.QColor(*[x * 255 for x in self.fontColor]) + + # could also draw the gradient using QPainter + barRect = QtCore.QRectF(self.view().width() - 60, self.pos[1], self.size[0], self.size[1]) + self.qgradient.setStart(barRect.bottomLeft()) + self.qgradient.setFinalStop(barRect.topLeft()) + + painter = QtGui.QPainter(self.view()) + painter.fillRect(barRect, self.qgradient) + painter.setPen(fn.mkPen(fontColor)) + for labelText, labelPosition in self.labels.items(): + ## todo: draw ticks, position text properly + x = 1.1 * self.size[0] + self.pos[0] + y = self.size[1] - labelPosition * self.size[1] + self.pos[1] + 8 + ##todo: fonts + painter.drawText(x, y, labelText) + painter.end() + + glMatrixMode(GL_PROJECTION) + glPopMatrix() + glMatrixMode(GL_MODELVIEW) +