Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

silx.gui.plot.PlotWidget: Added support of dashed line gap color #3973

Merged
merged 6 commits into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/silx/gui/plot/backends/BackendBase.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def _setPlot(self, plot):
# Add methods

def addCurve(self, x, y,
color, symbol, linewidth, linestyle,
color, gapcolor, symbol, linewidth, linestyle,
yaxis,
xerror, yerror,
fill, alpha, symbolsize, baseline):
Expand All @@ -99,6 +99,8 @@ def addCurve(self, x, y,
:param color: color(s) to be used
:type color: string ("#RRGGBB") or (npoints, 4) unsigned byte array or
one of the predefined color names defined in colors.py
:param Union[str, None] gapcolor:
color used to fill dashed line gaps.
:param str symbol: Symbol to be drawn at each (x, y) position::

- ' ' or '' no symbol
Expand Down
6 changes: 5 additions & 1 deletion src/silx/gui/plot/backends/BackendMatplotlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,7 @@ def _getMarkerFromSymbol(self, symbol):
return symbol

def addCurve(self, x, y,
color, symbol, linewidth, linestyle,
color, gapcolor, symbol, linewidth, linestyle,
yaxis,
xerror, yerror,
fill, alpha, symbolsize, baseline):
Expand Down Expand Up @@ -685,6 +685,10 @@ def addCurve(self, x, y,
picker=True,
pickradius=pickradius,
markersize=symbolsize)

if gapcolor is not None and self._matplotlibVersion >= Version('3.6.0'):
for line2d in curveList:
line2d.set_gapcolor(gapcolor)
artists += list(curveList)

if fill:
Expand Down
5 changes: 3 additions & 2 deletions src/silx/gui/plot/backends/BackendOpenGL.py
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,7 @@ def _renderItems(self, overlay=False):
points[:, 0], points[:, 1],
style=item['linestyle'],
color=item['color'],
dash2ndColor=item['linebgcolor'],
gapColor=item['linebgcolor'],
width=item['linewidth'])
context.matrix = self.matScreenProj
lines.render(context)
Expand Down Expand Up @@ -768,7 +768,7 @@ def _castArrayTo(v):
raise ValueError('Unsupported data type')

def addCurve(self, x, y,
color, symbol, linewidth, linestyle,
color, gapcolor, symbol, linewidth, linestyle,
yaxis,
xerror, yerror,
fill, alpha, symbolsize, baseline):
Expand Down Expand Up @@ -872,6 +872,7 @@ def addCurve(self, x, y,
yError=yerror,
lineStyle=linestyle,
lineColor=color,
lineGapColor=gapcolor,
lineWidth=linewidth,
marker=symbol,
markerColor=color,
Expand Down
22 changes: 13 additions & 9 deletions src/silx/gui/plot/backends/glutils/GLPlotCurve.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,18 +321,18 @@ class GLLines2D(object):
/* Dashes: [0, x], [y, z]
Dash period: w */
uniform vec4 dash;
uniform vec4 dash2ndColor;
uniform vec4 gapColor;

varying float vDist;
varying vec4 vColor;

void main(void) {
float dist = mod(vDist, dash.w);
if ((dist > dash.x && dist < dash.y) || dist > dash.z) {
if (dash2ndColor.a == 0.) {
if (gapColor.a == 0.) {
discard; // Discard full transparent bg color
} else {
gl_FragColor = dash2ndColor;
gl_FragColor = gapColor;
}
} else {
gl_FragColor = vColor;
Expand All @@ -343,7 +343,7 @@ class GLLines2D(object):

def __init__(self, xVboData=None, yVboData=None,
colorVboData=None, distVboData=None,
style=SOLID, color=(0., 0., 0., 1.), dash2ndColor=None,
style=SOLID, color=(0., 0., 0., 1.), gapColor=None,
width=1, dashPeriod=10., drawMode=None,
offset=(0., 0.)):
if (xVboData is not None and
Expand Down Expand Up @@ -374,7 +374,7 @@ def __init__(self, xVboData=None, yVboData=None,
self.useColorVboData = colorVboData is not None

self.color = color
self.dash2ndColor = dash2ndColor
self.gapColor = gapColor
self.width = width
self._style = None
self.style = style
Expand Down Expand Up @@ -439,12 +439,12 @@ def render(self, context):

gl.glUniform4f(program.uniforms['dash'], *dash)

if self.dash2ndColor is None:
if self.gapColor is None:
# Use fully transparent color which gets discarded in shader
dash2ndColor = (0., 0., 0., 0.)
gapColor = (0., 0., 0., 0.)
else:
dash2ndColor = self.dash2ndColor
gl.glUniform4f(program.uniforms['dash2ndColor'], *dash2ndColor)
gapColor = self.gapColor
gl.glUniform4f(program.uniforms['gapColor'], *gapColor)

viewWidth = gl.glGetFloatv(gl.GL_VIEWPORT)[2]
xNDCPerData = (
Expand Down Expand Up @@ -1130,6 +1130,7 @@ def __init__(self, xData, yData, colorData=None,
xError=None, yError=None,
lineStyle=SOLID,
lineColor=(0., 0., 0., 1.),
lineGapColor=None,
lineWidth=1,
lineDashPeriod=20,
marker=SQUARE,
Expand Down Expand Up @@ -1211,6 +1212,7 @@ def deduce_baseline(baseline):
self.lines = GLLines2D()
self.lines.style = lineStyle
self.lines.color = lineColor
self.lines.gapColor = lineGapColor
self.lines.width = lineWidth
self.lines.dashPeriod = lineDashPeriod
self.lines.offset = self.offset
Expand All @@ -1237,6 +1239,8 @@ def deduce_baseline(baseline):

lineColor = _proxyProperty(('lines', 'color'))

lineGapColor = _proxyProperty(('lines', 'gapColor'))

lineWidth = _proxyProperty(('lines', 'width'))

lineDashPeriod = _proxyProperty(('lines', 'dashPeriod'))
Expand Down
2 changes: 1 addition & 1 deletion src/silx/gui/plot/items/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
__date__ = "22/06/2017"

from .core import (Item, DataItem, # noqa
LabelsMixIn, DraggableMixIn, ColormapMixIn, # noqa
LabelsMixIn, DraggableMixIn, ColormapMixIn, LineGapColorMixIn, # noqa
SymbolMixIn, ColorMixIn, YAxisMixIn, FillMixIn, # noqa
AlphaMixIn, LineMixIn, ScatterVisualizationMixIn, # noqa
ComplexMixIn, ItemChangedType, PointsBase) # noqa
Expand Down
36 changes: 36 additions & 0 deletions src/silx/gui/plot/items/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ class ItemChangedType(enum.Enum):
LINE_BG_COLOR = 'lineBgColorChanged'
"""Item's line background color changed flag."""

LINE_GAP_COLOR = 'lineGapColorChanged'
"""Item's dashed line gap color changed flag."""

YAXIS = 'yAxisChanged'
"""Item's Y axis binding changed flag."""

Expand Down Expand Up @@ -935,6 +938,39 @@ def setColor(self, color, copy=True):
self._updated(ItemChangedType.COLOR)


class LineGapColorMixIn(ItemMixInBase):
"""Mix-in class for dashed line gap color"""

_DEFAULT_LINE_GAP_COLOR = None
"""Default dashed line gap color of the item"""

def __init__(self):
self.__lineGapColor = self._DEFAULT_LINE_GAP_COLOR

def getLineGapColor(self):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hesitated to reuse shapes method naming: get|setLineBgColor but it's implemented slightly differently (it's not a background color here) and I choose to stick to matplotlib naming.

"""Returns the RGBA color of dashed line gap of the item

:rtype: 4-tuple of float in [0, 1] or None
"""
return self.__lineGapColor

def setLineGapColor(self, color):
"""Set dashed line gap color

It supports:
- color names: e.g., 'green'
- color codes: '#RRGGBB' and '#RRGGBBAA'
- indexed color names: e.g., 'C0'
- RGB(A) sequence of uint8 in [0, 255] or float in [0, 1]
- QColor

:param color: line background color to be used
:type color: Union[str, List[int], List[float], QColor, None]
"""
self.__lineGapColor = colors.rgba(color)
self._updated(ItemChangedType.LINE_GAP_COLOR)


class YAxisMixIn(ItemMixInBase):
"""Mix-in class for item with yaxis"""

Expand Down
29 changes: 23 additions & 6 deletions src/silx/gui/plot/items/curve.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
from ....utils.deprecation import deprecated_warning
from ... import colors
from .core import (PointsBase, LabelsMixIn, ColorMixIn, YAxisMixIn,
FillMixIn, LineMixIn, SymbolMixIn,
FillMixIn, LineMixIn, LineGapColorMixIn, SymbolMixIn,
BaselineMixIn, HighlightedMixIn, _Style)


Expand All @@ -53,10 +53,11 @@ class CurveStyle(_Style):
:param Union[float,None] linewidth: Width of the line
:param Union[str,None] symbol: Symbol for markers
:param Union[float,None] symbolsize: Size of the markers
:param gapcolor: Color of gaps of dashed line
"""

def __init__(self, color=None, linestyle=None, linewidth=None,
symbol=None, symbolsize=None):
symbol=None, symbolsize=None, gapcolor=None):
if color is None:
self._color = None
else:
Expand All @@ -80,6 +81,8 @@ def __init__(self, color=None, linestyle=None, linewidth=None,

self._symbolsize = None if symbolsize is None else float(symbolsize)

self._gapcolor = None if gapcolor is None else colors.rgba(gapcolor)

def getColor(self, copy=True):
"""Returns the color or None if not set.

Expand All @@ -93,6 +96,13 @@ def getColor(self, copy=True):
else:
return self._color

def getLineGapColor(self):
"""Returns the color of dashed line gaps or None if not set.

:rtype: Union[List[float],None]
"""
return self._gapcolor

def getLineStyle(self):
"""Return the type of the line or None if not set.

Expand Down Expand Up @@ -145,13 +155,14 @@ def __eq__(self, other):
self.getLineStyle() == other.getLineStyle() and
self.getLineWidth() == other.getLineWidth() and
self.getSymbol() == other.getSymbol() and
self.getSymbolSize() == other.getSymbolSize())
self.getSymbolSize() == other.getSymbolSize() and
self.getLineGapColor() == other.getLineGapColor())
else:
return False


class Curve(PointsBase, ColorMixIn, YAxisMixIn, FillMixIn, LabelsMixIn,
LineMixIn, BaselineMixIn, HighlightedMixIn):
LineMixIn, LineGapColorMixIn, BaselineMixIn, HighlightedMixIn):
"""Description of a curve"""

_DEFAULT_Z_LAYER = 1
Expand All @@ -178,6 +189,7 @@ def __init__(self):
FillMixIn.__init__(self)
LabelsMixIn.__init__(self)
LineMixIn.__init__(self)
LineGapColorMixIn.__init__(self)
BaselineMixIn.__init__(self)
HighlightedMixIn.__init__(self)

Expand All @@ -196,6 +208,7 @@ def _addBackendRenderer(self, backend):

return backend.addCurve(xFiltered, yFiltered,
color=style.getColor(),
gapcolor=style.getLineGapColor(),
symbol=style.getSymbol(),
linestyle=style.getLineStyle(),
linewidth=style.getLineWidth(),
Expand Down Expand Up @@ -255,20 +268,24 @@ def getCurrentStyle(self):
linewidth = style.getLineWidth()
symbol = style.getSymbol()
symbolsize = style.getSymbolSize()
gapcolor = style.getLineGapColor()

return CurveStyle(
color=self.getColor() if color is None else color,
linestyle=self.getLineStyle() if linestyle is None else linestyle,
linewidth=self.getLineWidth() if linewidth is None else linewidth,
symbol=self.getSymbol() if symbol is None else symbol,
symbolsize=self.getSymbolSize() if symbolsize is None else symbolsize)
symbolsize=self.getSymbolSize() if symbolsize is None else symbolsize,
gapcolor=self.getLineGapColor() if gapcolor is None else gapcolor,
)

else:
return CurveStyle(color=self.getColor(),
linestyle=self.getLineStyle(),
linewidth=self.getLineWidth(),
symbol=self.getSymbol(),
symbolsize=self.getSymbolSize())
symbolsize=self.getSymbolSize(),
gapcolor=self.getLineGapColor())

def setData(self, x, y, xerror=None, yerror=None, baseline=None, copy=True):
"""Set the data of the curve.
Expand Down
6 changes: 4 additions & 2 deletions src/silx/gui/plot/items/histogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@

from ....utils.proxy import docstring
from .core import (DataItem, AlphaMixIn, BaselineMixIn, ColorMixIn, FillMixIn,
LineMixIn, YAxisMixIn, ItemChangedType)
LineMixIn, LineGapColorMixIn, YAxisMixIn, ItemChangedType)
from ._pick import PickingResult

_logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -99,7 +99,7 @@ def _getHistogramCurve(histogram, edges):

# TODO: Yerror, test log scale
class Histogram(DataItem, AlphaMixIn, ColorMixIn, FillMixIn,
LineMixIn, YAxisMixIn, BaselineMixIn):
LineMixIn, LineGapColorMixIn, YAxisMixIn, BaselineMixIn):
"""Description of an histogram"""

_DEFAULT_Z_LAYER = 1
Expand All @@ -123,6 +123,7 @@ def __init__(self):
ColorMixIn.__init__(self)
FillMixIn.__init__(self)
LineMixIn.__init__(self)
LineGapColorMixIn.__init__(self)
YAxisMixIn.__init__(self)

self._histogram = ()
Expand Down Expand Up @@ -162,6 +163,7 @@ def _addBackendRenderer(self, backend):

return backend.addCurve(x, y,
color=self.getColor(),
gapcolor=self.getLineGapColor(),
symbol='',
linestyle=self.getLineStyle(),
linewidth=self.getLineWidth(),
Expand Down
2 changes: 2 additions & 0 deletions src/silx/gui/plot/items/scatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,7 @@ def _addBackendRenderer(self, backend):
rgbacolors = self.__applyColormapToData()
return backend.addCurve(xFiltered, yFiltered,
color=rgbacolors[mask],
gapcolor=None,
symbol=self.getSymbol(),
linewidth=0,
linestyle="",
Expand Down Expand Up @@ -632,6 +633,7 @@ def _addBackendRenderer(self, backend):
# single point, render as a square points
return backend.addCurve(xFiltered, yFiltered,
color=rgbacolors[mask],
gapcolor=None,
symbol='s',
linewidth=0,
linestyle="",
Expand Down
24 changes: 24 additions & 0 deletions src/silx/gui/plot/test/testPlotWidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,15 @@ def testPlotCurveComplexData(self):
data = numpy.arange(100.) + 1j
self.plot.addCurve(x=data, y=data, xerror=data, yerror=data)

def testPlotCurveGapColor(self):
"""Test dashed curve with gap color"""
data = numpy.arange(100)
self.plot.addCurve(x=data, y=data, legend='curve1', linestyle='--', color='blue')
curve = self.plot.getCurve('curve1')
assert curve.getLineGapColor() is None
curve.setLineGapColor('red')
assert curve.getLineGapColor() == (1., 0., 0., 1.)


class TestPlotHistogram(PlotWidgetTestCase):
"""Basic tests for add Histogram"""
Expand All @@ -598,6 +607,21 @@ def testPlotBaseline(self):
z=2,
fill=True)

def testPlotGapColor(self):
"""Test dashed histogram with gap color"""
data = numpy.arange(100)
self.plot.addHistogram(
histogram=self.histogram,
edges=self.edges,
legend='histogram1',
color='blue',
)
histogram = self.plot.getItems()[0]
assert histogram.getLineGapColor() is None
histogram.setLineGapColor('red')
assert histogram.getLineGapColor() == (1., 0., 0., 1.)
histogram.setLineStyle(':')


class TestPlotScatter(PlotWidgetTestCase, ParametricTestCase):
"""Basic tests for addScatter"""
Expand Down