diff options
Diffstat (limited to 'silx/gui/plot/backends/glutils')
-rw-r--r-- | silx/gui/plot/backends/glutils/GLPlotCurve.py | 119 | ||||
-rw-r--r-- | silx/gui/plot/backends/glutils/GLPlotFrame.py | 124 | ||||
-rw-r--r-- | silx/gui/plot/backends/glutils/GLSupport.py | 63 |
3 files changed, 182 insertions, 124 deletions
diff --git a/silx/gui/plot/backends/glutils/GLPlotCurve.py b/silx/gui/plot/backends/glutils/GLPlotCurve.py index 12b6bbe..5f8d652 100644 --- a/silx/gui/plot/backends/glutils/GLPlotCurve.py +++ b/silx/gui/plot/backends/glutils/GLPlotCurve.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2014-2018 European Synchrotron Radiation Facility +# Copyright (c) 2014-2019 European Synchrotron Radiation Facility # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -42,7 +42,7 @@ import numpy from silx.math.combo import min_max from ...._glutils import gl -from ...._glutils import Program, vertexBuffer +from ...._glutils import Program, vertexBuffer, VertexBufferAttrib from .GLSupport import buildFillMaskIndices, mat4Identity, mat4Translate @@ -245,7 +245,7 @@ class _Fill2D(object): SOLID, DASHED, DASHDOT, DOTTED = '-', '--', '-.', ':' -class _Lines2D(object): +class GLLines2D(object): """Object rendering curve as a polyline :param xVboData: X coordinates VBO @@ -323,6 +323,7 @@ class _Lines2D(object): /* Dashes: [0, x], [y, z] Dash period: w */ uniform vec4 dash; + uniform vec4 dash2ndColor; varying float vDist; varying vec4 vColor; @@ -330,25 +331,52 @@ class _Lines2D(object): void main(void) { float dist = mod(vDist, dash.w); if ((dist > dash.x && dist < dash.y) || dist > dash.z) { - discard; + if (dash2ndColor.a == 0.) { + discard; // Discard full transparent bg color + } else { + gl_FragColor = dash2ndColor; + } + } else { + gl_FragColor = vColor; } - gl_FragColor = vColor; } """, attrib0='xPos') def __init__(self, xVboData=None, yVboData=None, colorVboData=None, distVboData=None, - style=SOLID, color=(0., 0., 0., 1.), - width=1, dashPeriod=20, drawMode=None, + style=SOLID, color=(0., 0., 0., 1.), dash2ndColor=None, + width=1, dashPeriod=10., drawMode=None, offset=(0., 0.)): + if (xVboData is not None and + not isinstance(xVboData, VertexBufferAttrib)): + xVboData = numpy.array(xVboData, copy=False, dtype=numpy.float32) self.xVboData = xVboData + + if (yVboData is not None and + not isinstance(yVboData, VertexBufferAttrib)): + yVboData = numpy.array(yVboData, copy=False, dtype=numpy.float32) self.yVboData = yVboData + + # Compute distances if not given while providing numpy array coordinates + if (isinstance(self.xVboData, numpy.ndarray) and + isinstance(self.yVboData, numpy.ndarray) and + distVboData is None): + distVboData = distancesFromArrays(self.xVboData, self.yVboData) + + if (distVboData is not None and + not isinstance(distVboData, VertexBufferAttrib)): + distVboData = numpy.array( + distVboData, copy=False, dtype=numpy.float32) self.distVboData = distVboData + + if colorVboData is not None: + assert isinstance(colorVboData, VertexBufferAttrib) self.colorVboData = colorVboData self.useColorVboData = colorVboData is not None self.color = color + self.dash2ndColor = dash2ndColor self.width = width self._style = None self.style = style @@ -396,29 +424,46 @@ class _Lines2D(object): gl.glUniform2f(program.uniforms['halfViewportSize'], 0.5 * viewWidth, 0.5 * viewHeight) + dashPeriod = self.dashPeriod * self.width if self.style == DOTTED: - dash = (0.1 * self.dashPeriod, - 0.6 * self.dashPeriod, - 0.7 * self.dashPeriod, - self.dashPeriod) + dash = (0.2 * dashPeriod, + 0.5 * dashPeriod, + 0.7 * dashPeriod, + dashPeriod) elif self.style == DASHDOT: - dash = (0.3 * self.dashPeriod, - 0.5 * self.dashPeriod, - 0.6 * self.dashPeriod, - self.dashPeriod) + dash = (0.3 * dashPeriod, + 0.5 * dashPeriod, + 0.6 * dashPeriod, + dashPeriod) else: - dash = (0.5 * self.dashPeriod, - self.dashPeriod, - self.dashPeriod, - self.dashPeriod) + dash = (0.5 * dashPeriod, + dashPeriod, + dashPeriod, + dashPeriod) gl.glUniform4f(program.uniforms['dash'], *dash) + if self.dash2ndColor is None: + # Use fully transparent color which gets discarded in shader + dash2ndColor = (0., 0., 0., 0.) + else: + dash2ndColor = self.dash2ndColor + gl.glUniform4f(program.uniforms['dash2ndColor'], *dash2ndColor) + distAttrib = program.attributes['distance'] gl.glEnableVertexAttribArray(distAttrib) - self.distVboData.setVertexAttrib(distAttrib) + if isinstance(self.distVboData, VertexBufferAttrib): + self.distVboData.setVertexAttrib(distAttrib) + else: + gl.glVertexAttribPointer(distAttrib, + 1, + gl.GL_FLOAT, + False, + 0, + self.distVboData) - gl.glEnable(gl.GL_LINE_SMOOTH) + if self.width != 1: + gl.glEnable(gl.GL_LINE_SMOOTH) matrix = numpy.dot(matrix, mat4Translate(*self.offset)).astype(numpy.float32) @@ -435,11 +480,27 @@ class _Lines2D(object): xPosAttrib = program.attributes['xPos'] gl.glEnableVertexAttribArray(xPosAttrib) - self.xVboData.setVertexAttrib(xPosAttrib) + if isinstance(self.xVboData, VertexBufferAttrib): + self.xVboData.setVertexAttrib(xPosAttrib) + else: + gl.glVertexAttribPointer(xPosAttrib, + 1, + gl.GL_FLOAT, + False, + 0, + self.xVboData) yPosAttrib = program.attributes['yPos'] gl.glEnableVertexAttribArray(yPosAttrib) - self.yVboData.setVertexAttrib(yPosAttrib) + if isinstance(self.yVboData, VertexBufferAttrib): + self.yVboData.setVertexAttrib(yPosAttrib) + else: + gl.glVertexAttribPointer(yPosAttrib, + 1, + gl.GL_FLOAT, + False, + 0, + self.yVboData) gl.glLineWidth(self.width) gl.glDrawArrays(self._drawMode, 0, self.xVboData.size) @@ -447,7 +508,7 @@ class _Lines2D(object): gl.glDisable(gl.GL_LINE_SMOOTH) -def _distancesFromArrays(xData, yData): +def distancesFromArrays(xData, yData): """Returns distances between each points :param numpy.ndarray xData: X coordinate of points @@ -711,7 +772,7 @@ class _ErrorBars(object): This is using its own VBO as opposed to fill/points/lines. There is no picking on error bars. - It uses 2 vertices per error bars and uses :class:`_Lines2D` to + It uses 2 vertices per error bars and uses :class:`GLLines2D` to render error bars and :class:`_Points2D` to render the ends. :param numpy.ndarray xData: X coordinates of the data. @@ -753,7 +814,7 @@ class _ErrorBars(object): self._xData, self._yData = None, None self._xError, self._yError = None, None - self._lines = _Lines2D( + self._lines = GLLines2D( None, None, color=color, drawMode=gl.GL_LINES, offset=offset) self._xErrPoints = _Points2D( None, None, color=color, marker=V_LINE, offset=offset) @@ -957,7 +1018,7 @@ class GLPlotCurve2D(object): self.xMin, self.yMin, offset=self.offset) - self.lines = _Lines2D() + self.lines = GLLines2D() self.lines.style = lineStyle self.lines.color = lineColor self.lines.width = lineWidth @@ -999,7 +1060,7 @@ class GLPlotCurve2D(object): @classmethod def init(cls): """OpenGL context initialization""" - _Lines2D.init() + GLLines2D.init() _Points2D.init() def prepare(self): @@ -1007,7 +1068,7 @@ class GLPlotCurve2D(object): if self.xVboData is None: xAttrib, yAttrib, cAttrib, dAttrib = None, None, None, None if self.lineStyle in (DASHED, DASHDOT, DOTTED): - dists = _distancesFromArrays(self.xData, self.yData) + dists = distancesFromArrays(self.xData, self.yData) if self.colorData is None: xAttrib, yAttrib, dAttrib = vertexBuffer( (self.xData, self.yData, dists)) diff --git a/silx/gui/plot/backends/glutils/GLPlotFrame.py b/silx/gui/plot/backends/glutils/GLPlotFrame.py index 4ad1547..43f6e10 100644 --- a/silx/gui/plot/backends/glutils/GLPlotFrame.py +++ b/silx/gui/plot/backends/glutils/GLPlotFrame.py @@ -63,6 +63,7 @@ class PlotAxis(object): def __init__(self, plot, tickLength=(0., 0.), + foregroundColor=(0., 0., 0., 1.0), labelAlign=CENTER, labelVAlign=CENTER, titleAlign=CENTER, titleVAlign=CENTER, titleRotate=0, titleOffset=(0., 0.)): @@ -78,6 +79,7 @@ class PlotAxis(object): self._title = '' self._tickLength = tickLength + self._foregroundColor = foregroundColor self._labelAlign = labelAlign self._labelVAlign = labelVAlign self._titleAlign = titleAlign @@ -169,6 +171,20 @@ class PlotAxis(object): plot._dirty() @property + def foregroundColor(self): + """Color used for frame and labels""" + return self._foregroundColor + + @foregroundColor.setter + def foregroundColor(self, color): + """Color used for frame and labels""" + assert len(color) == 4, \ + "foregroundColor must have length 4, got {}".format(len(self._foregroundColor)) + if self._foregroundColor != color: + self._foregroundColor = color + self._dirtyTicks() + + @property def ticks(self): """Ticks as tuples: ((x, y) in display, dataPos, textLabel).""" if self._ticks is None: @@ -192,6 +208,7 @@ class PlotAxis(object): tickScale = 1. label = Text2D(text=text, + color=self._foregroundColor, x=xPixel - xTickLength, y=yPixel - yTickLength, align=self._labelAlign, @@ -223,6 +240,7 @@ class PlotAxis(object): # yOffset -= 3 * yTickLength axisTitle = Text2D(text=self.title, + color=self._foregroundColor, x=xAxisCenter + xOffset, y=yAxisCenter + yOffset, align=self._titleAlign, @@ -373,15 +391,21 @@ class GLPlotFrame(object): # Margins used when plot frame is not displayed _NoDisplayMargins = _Margins(0, 0, 0, 0) - def __init__(self, margins): + def __init__(self, margins, foregroundColor, gridColor): """ :param margins: The margins around plot area for axis and labels. :type margins: dict with 'left', 'right', 'top', 'bottom' keys and values as ints. + :param foregroundColor: color used for the frame and labels. + :type foregroundColor: tuple with RGBA values ranging from 0.0 to 1.0 + :param gridColor: color used for grid lines. + :type gridColor: tuple RGBA with RGBA values ranging from 0.0 to 1.0 """ self._renderResources = None self._margins = self._Margins(**margins) + self._foregroundColor = foregroundColor + self._gridColor = gridColor self.axes = [] # List of PlotAxis to be updated by subclasses @@ -401,6 +425,36 @@ class GLPlotFrame(object): GRID_ALL_TICKS = (GRID_MAIN_TICKS + GRID_SUB_TICKS) @property + def foregroundColor(self): + """Color used for frame and labels""" + return self._foregroundColor + + @foregroundColor.setter + def foregroundColor(self, color): + """Color used for frame and labels""" + assert len(color) == 4, \ + "foregroundColor must have length 4, got {}".format(len(self._foregroundColor)) + if self._foregroundColor != color: + self._foregroundColor = color + for axis in self.axes: + axis.foregroundColor = color + self._dirty() + + @property + def gridColor(self): + """Color used for frame and labels""" + return self._gridColor + + @gridColor.setter + def gridColor(self, color): + """Color used for frame and labels""" + assert len(color) == 4, \ + "gridColor must have length 4, got {}".format(len(self._gridColor)) + if self._gridColor != color: + self._gridColor = color + self._dirty() + + @property def displayed(self): """Whether axes and their labels are displayed or not (bool)""" return self._displayed @@ -522,6 +576,7 @@ class GLPlotFrame(object): self.margins.right) // 2 yTitle = self.margins.top - self._TICK_LENGTH_IN_PIXELS labels.append(Text2D(text=self.title, + color=self._foregroundColor, x=xTitle, y=yTitle, align=CENTER, @@ -556,7 +611,7 @@ class GLPlotFrame(object): gl.glUniformMatrix4fv(prog.uniforms['matrix'], 1, gl.GL_TRUE, matProj.astype(numpy.float32)) - gl.glUniform4f(prog.uniforms['color'], 0., 0., 0., 1.) + gl.glUniform4f(prog.uniforms['color'], *self._foregroundColor) gl.glUniform1f(prog.uniforms['tickFactor'], 0.) gl.glEnableVertexAttribArray(prog.attributes['position']) @@ -590,7 +645,7 @@ class GLPlotFrame(object): gl.glLineWidth(self._LINE_WIDTH) gl.glUniformMatrix4fv(prog.uniforms['matrix'], 1, gl.GL_TRUE, matProj.astype(numpy.float32)) - gl.glUniform4f(prog.uniforms['color'], 0.7, 0.7, 0.7, 1.) + gl.glUniform4f(prog.uniforms['color'], *self._gridColor) gl.glUniform1f(prog.uniforms['tickFactor'], 0.) # 1/2.) # 1/tickLen gl.glEnableVertexAttribArray(prog.attributes['position']) @@ -606,15 +661,21 @@ class GLPlotFrame(object): # GLPlotFrame2D ############################################################### class GLPlotFrame2D(GLPlotFrame): - def __init__(self, margins): + def __init__(self, margins, foregroundColor, gridColor): """ :param margins: The margins around plot area for axis and labels. :type margins: dict with 'left', 'right', 'top', 'bottom' keys and values as ints. + :param foregroundColor: color used for the frame and labels. + :type foregroundColor: tuple with RGBA values ranging from 0.0 to 1.0 + :param gridColor: color used for grid lines. + :type gridColor: tuple RGBA with RGBA values ranging from 0.0 to 1.0 + """ - super(GLPlotFrame2D, self).__init__(margins) + super(GLPlotFrame2D, self).__init__(margins, foregroundColor, gridColor) self.axes.append(PlotAxis(self, tickLength=(0., -5.), + foregroundColor=self._foregroundColor, labelAlign=CENTER, labelVAlign=TOP, titleAlign=CENTER, titleVAlign=TOP, titleRotate=0, @@ -624,6 +685,7 @@ class GLPlotFrame2D(GLPlotFrame): self.axes.append(PlotAxis(self, tickLength=(5., 0.), + foregroundColor=self._foregroundColor, labelAlign=RIGHT, labelVAlign=CENTER, titleAlign=CENTER, titleVAlign=BOTTOM, titleRotate=ROTATE_270, @@ -632,6 +694,7 @@ class GLPlotFrame2D(GLPlotFrame): self._y2Axis = PlotAxis(self, tickLength=(-5., 0.), + foregroundColor=self._foregroundColor, labelAlign=LEFT, labelVAlign=CENTER, titleAlign=CENTER, titleVAlign=TOP, titleRotate=ROTATE_270, @@ -825,23 +888,6 @@ class GLPlotFrame2D(GLPlotFrame): _logger.info('yMax: warning log10(%f)', y2Max) y2Max = 0. - # Non-orthogonal axes - if self.baseVectors != self.DEFAULT_BASE_VECTORS: - (xx, xy), (yx, yy) = self.baseVectors - skew_mat = numpy.array(((xx, yx), (xy, yy))) - - corners = [(xMin, yMin), (xMin, yMax), - (xMax, yMin), (xMax, yMax), - (xMin, y2Min), (xMin, y2Max), - (xMax, y2Min), (xMax, y2Max)] - - corners = numpy.array( - [numpy.dot(skew_mat, corner) for corner in corners], - dtype=numpy.float32) - xMin, xMax = corners[:, 0].min(), corners[:, 0].max() - yMin, yMax = corners[0:4, 1].min(), corners[0:4, 1].max() - y2Min, y2Max = corners[4:, 1].min(), corners[4:, 1].max() - self._transformedDataRanges = self._DataRanges( (xMin, xMax), (yMin, yMax), (y2Min, y2Max)) @@ -861,16 +907,6 @@ class GLPlotFrame2D(GLPlotFrame): mat = mat4Ortho(xMin, xMax, yMax, yMin, 1, -1) else: mat = mat4Ortho(xMin, xMax, yMin, yMax, 1, -1) - - # Non-orthogonal axes - if self.baseVectors != self.DEFAULT_BASE_VECTORS: - (xx, xy), (yx, yy) = self.baseVectors - mat = numpy.dot(mat, numpy.array(( - (xx, yx, 0., 0.), - (xy, yy, 0., 0.), - (0., 0., 1., 0.), - (0., 0., 0., 1.)), dtype=numpy.float64)) - self._transformedDataProjMat = mat return self._transformedDataProjMat @@ -890,16 +926,6 @@ class GLPlotFrame2D(GLPlotFrame): mat = mat4Ortho(xMin, xMax, y2Max, y2Min, 1, -1) else: mat = mat4Ortho(xMin, xMax, y2Min, y2Max, 1, -1) - - # Non-orthogonal axes - if self.baseVectors != self.DEFAULT_BASE_VECTORS: - (xx, xy), (yx, yy) = self.baseVectors - mat = numpy.dot(mat, numpy.matrix(( - (xx, yx, 0., 0.), - (xy, yy, 0., 0.), - (0., 0., 1., 0.), - (0., 0., 0., 1.)), dtype=numpy.float64)) - self._transformedDataY2ProjMat = mat return self._transformedDataY2ProjMat @@ -1114,3 +1140,17 @@ class GLPlotFrame2D(GLPlotFrame): vertices = numpy.append(vertices, extraVertices, axis=0) self._renderResources = (vertices, gridVertices, labels) + + @property + def foregroundColor(self): + """Color used for frame and labels""" + return self._foregroundColor + + @foregroundColor.setter + def foregroundColor(self, color): + """Color used for frame and labels""" + assert len(color) == 4, \ + "foregroundColor must have length 4, got {}".format(len(self._foregroundColor)) + if self._foregroundColor != color: + self._y2Axis.foregroundColor = color + GLPlotFrame.foregroundColor.fset(self, color) # call parent property diff --git a/silx/gui/plot/backends/glutils/GLSupport.py b/silx/gui/plot/backends/glutils/GLSupport.py index 18c5eb7..da6dffa 100644 --- a/silx/gui/plot/backends/glutils/GLSupport.py +++ b/silx/gui/plot/backends/glutils/GLSupport.py @@ -60,16 +60,12 @@ def buildFillMaskIndices(nIndices, dtype=None): return indices -class Shape2D(object): +class FilledShape2D(object): _NO_HATCH = 0 _HATCH_STEP = 20 - def __init__(self, points, fill='solid', stroke=True, - fillColor=(0., 0., 0., 1.), strokeColor=(0., 0., 0., 1.), - strokeClosed=True): + def __init__(self, points, style='solid', color=(0., 0., 0., 1.)): self.vertices = numpy.array(points, dtype=numpy.float32, copy=False) - self.strokeClosed = strokeClosed - self._indices = buildFillMaskIndices(len(self.vertices)) tVertex = numpy.transpose(self.vertices) @@ -81,28 +77,16 @@ class Shape2D(object): self._xMin, self._xMax = xMin, xMax self._yMin, self._yMax = yMin, yMax - self.fill = fill - self.fillColor = fillColor - self.stroke = stroke - self.strokeColor = strokeColor - - @property - def xMin(self): - return self._xMin - - @property - def xMax(self): - return self._xMax - - @property - def yMin(self): - return self._yMin + self.style = style + self.color = color - @property - def yMax(self): - return self._yMax + def render(self, posAttrib, colorUnif, hatchStepUnif): + assert self.style in ('hatch', 'solid') + gl.glUniform4f(colorUnif, *self.color) + step = self._HATCH_STEP if self.style == 'hatch' else self._NO_HATCH + gl.glUniform1i(hatchStepUnif, step) - def prepareFillMask(self, posAttrib): + # Prepare fill mask gl.glEnableVertexAttribArray(posAttrib) gl.glVertexAttribPointer(posAttrib, 2, @@ -126,9 +110,6 @@ class Shape2D(object): gl.glColorMask(gl.GL_TRUE, gl.GL_TRUE, gl.GL_TRUE, gl.GL_TRUE) gl.glDepthMask(gl.GL_TRUE) - def renderFill(self, posAttrib): - self.prepareFillMask(posAttrib) - gl.glVertexAttribPointer(posAttrib, 2, gl.GL_FLOAT, @@ -138,30 +119,6 @@ class Shape2D(object): gl.glDisable(gl.GL_STENCIL_TEST) - def renderStroke(self, posAttrib): - gl.glEnableVertexAttribArray(posAttrib) - gl.glVertexAttribPointer(posAttrib, - 2, - gl.GL_FLOAT, - gl.GL_FALSE, - 0, self.vertices) - gl.glLineWidth(1) - drawMode = gl.GL_LINE_LOOP if self.strokeClosed else gl.GL_LINE_STRIP - gl.glDrawArrays(drawMode, 0, len(self.vertices)) - - def render(self, posAttrib, colorUnif, hatchStepUnif): - assert self.fill in ['hatch', 'solid', None] - if self.fill is not None: - gl.glUniform4f(colorUnif, *self.fillColor) - step = self._HATCH_STEP if self.fill == 'hatch' else self._NO_HATCH - gl.glUniform1i(hatchStepUnif, step) - self.renderFill(posAttrib) - - if self.stroke: - gl.glUniform4f(colorUnif, *self.strokeColor) - gl.glUniform1i(hatchStepUnif, self._NO_HATCH) - self.renderStroke(posAttrib) - # matrix ###################################################################### |