summaryrefslogtreecommitdiff
path: root/silx/gui/plot/backends/glutils
diff options
context:
space:
mode:
authorPicca Frédéric-Emmanuel <picca@synchrotron-soleil.fr>2019-05-28 08:16:16 +0200
committerPicca Frédéric-Emmanuel <picca@synchrotron-soleil.fr>2019-05-28 08:16:16 +0200
commita763e5d1b3921b3194f3d4e94ab9de3fbe08bbdd (patch)
tree45d462ed36a5522e9f3b9fde6c4ec4918c2ae8e3 /silx/gui/plot/backends/glutils
parentcebdc9244c019224846cb8d2668080fe386a6adc (diff)
New upstream version 0.10.1+dfsg
Diffstat (limited to 'silx/gui/plot/backends/glutils')
-rw-r--r--silx/gui/plot/backends/glutils/GLPlotCurve.py119
-rw-r--r--silx/gui/plot/backends/glutils/GLPlotFrame.py124
-rw-r--r--silx/gui/plot/backends/glutils/GLSupport.py63
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 ######################################################################