diff options
Diffstat (limited to 'silx/gui/plot/items')
-rw-r--r-- | silx/gui/plot/items/core.py | 23 | ||||
-rw-r--r-- | silx/gui/plot/items/curve.py | 213 | ||||
-rw-r--r-- | silx/gui/plot/items/histogram.py | 7 | ||||
-rw-r--r-- | silx/gui/plot/items/marker.py | 42 | ||||
-rw-r--r-- | silx/gui/plot/items/scatter.py | 30 |
5 files changed, 277 insertions, 38 deletions
diff --git a/silx/gui/plot/items/core.py b/silx/gui/plot/items/core.py index 4ed0914..e000751 100644 --- a/silx/gui/plot/items/core.py +++ b/silx/gui/plot/items/core.py @@ -98,7 +98,10 @@ class ItemChangedType(enum.Enum): """Item's highlight state changed flag.""" HIGHLIGHTED_COLOR = 'highlightedColorChanged' - """Item's highlighted color changed flag.""" + """Deprecated, use HIGHLIGHTED_STYLE instead.""" + + HIGHLIGHTED_STYLE = 'highlightedStyleChanged' + """Item's highlighted style changed flag.""" SCALE = 'scaleChanged' """Item's scale changed flag.""" @@ -548,12 +551,26 @@ class LineMixIn(ItemMixInBase): _DEFAULT_LINESTYLE = '-' """Default line style""" + _SUPPORTED_LINESTYLE = '', ' ', '-', '--', '-.', ':', None + """Supported line styles""" + def __init__(self): self._linewidth = self._DEFAULT_LINEWIDTH self._linestyle = self._DEFAULT_LINESTYLE + @classmethod + def getSupportedLineStyles(cls): + """Returns list of supported line styles. + + :rtype: List[str,None] + """ + return cls._SUPPORTED_LINESTYLE + def getLineWidth(self): - """Return the curve line width in pixels (int)""" + """Return the curve line width in pixels + + :rtype: float + """ return self._linewidth def setLineWidth(self, width): @@ -591,7 +608,7 @@ class LineMixIn(ItemMixInBase): :param str style: Line style """ style = str(style) - assert style in ('', ' ', '-', '--', '-.', ':', None) + assert style in self.getSupportedLineStyles() if style is None: style = self._DEFAULT_LINESTYLE if style != self._linestyle: diff --git a/silx/gui/plot/items/curve.py b/silx/gui/plot/items/curve.py index 50ad86d..80d9dea 100644 --- a/silx/gui/plot/items/curve.py +++ b/silx/gui/plot/items/curve.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2017 European Synchrotron Radiation Facility +# Copyright (c) 2017-2018 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 @@ -33,14 +33,123 @@ __date__ = "24/04/2018" import logging import numpy +from silx.third_party import six +from ....utils.deprecation import deprecated from ... import colors from .core import (Points, LabelsMixIn, ColorMixIn, YAxisMixIn, - FillMixIn, LineMixIn, ItemChangedType) + FillMixIn, LineMixIn, SymbolMixIn, ItemChangedType) _logger = logging.getLogger(__name__) +class CurveStyle(object): + """Object storing the style of a curve. + + Set a value to None to use the default + + :param color: Color + :param Union[str,None] linestyle: Style of the line + :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 + """ + + def __init__(self, color=None, linestyle=None, linewidth=None, + symbol=None, symbolsize=None): + if color is None: + self._color = None + else: + if isinstance(color, six.string_types): + color = colors.rgba(color) + else: # array-like expected + color = numpy.array(color, copy=False) + if color.ndim == 1: # Array is 1D, this is a single color + color = colors.rgba(color) + self._color = color + + if linestyle is not None: + assert linestyle in LineMixIn.getSupportedLineStyles() + self._linestyle = linestyle + + self._linewidth = None if linewidth is None else float(linewidth) + + if symbol is not None: + assert symbol in SymbolMixIn.getSupportedSymbols() + self._symbol = symbol + + self._symbolsize = None if symbolsize is None else float(symbolsize) + + def getColor(self, copy=True): + """Returns the color or None if not set. + + :param bool copy: True to get a copy (default), + False to get internal representation (do not modify!) + + :rtype: Union[List[float],None] + """ + if isinstance(self._color, numpy.ndarray): + return numpy.array(self._color, copy=copy) + else: + return self._color + + def getLineStyle(self): + """Return the type of the line or None if not set. + + Type of line:: + + - ' ' no line + - '-' solid line + - '--' dashed line + - '-.' dash-dot line + - ':' dotted line + + :rtype: Union[str,None] + """ + return self._linestyle + + def getLineWidth(self): + """Return the curve line width in pixels or None if not set. + + :rtype: Union[float,None] + """ + return self._linewidth + + def getSymbol(self): + """Return the point marker type. + + Marker type:: + + - 'o' circle + - '.' point + - ',' pixel + - '+' cross + - 'x' x-cross + - 'd' diamond + - 's' square + + :rtype: Union[str,None] + """ + return self._symbol + + def getSymbolSize(self): + """Return the point marker size in points. + + :rtype: Union[float,None] + """ + return self._symbolsize + + def __eq__(self, other): + if isinstance(other, CurveStyle): + return (numpy.array_equal(self.getColor(), other.getColor()) and + self.getLineStyle() == other.getLineStyle() and + self.getLineWidth() == other.getLineWidth() and + self.getSymbol() == other.getSymbol() and + self.getSymbolSize() == other.getSymbolSize()) + else: + return False + + class Curve(Points, ColorMixIn, YAxisMixIn, FillMixIn, LabelsMixIn, LineMixIn): """Description of a curve""" @@ -56,8 +165,8 @@ class Curve(Points, ColorMixIn, YAxisMixIn, FillMixIn, LabelsMixIn, LineMixIn): _DEFAULT_LINESTYLE = '-' """Default line style of the curve""" - _DEFAULT_HIGHLIGHT_COLOR = (0, 0, 0, 255) - """Default highlight color of the item""" + _DEFAULT_HIGHLIGHT_STYLE = CurveStyle(color='black') + """Default highlight style of the item""" def __init__(self): Points.__init__(self) @@ -67,9 +176,18 @@ class Curve(Points, ColorMixIn, YAxisMixIn, FillMixIn, LabelsMixIn, LineMixIn): LabelsMixIn.__init__(self) LineMixIn.__init__(self) - self._highlightColor = self._DEFAULT_HIGHLIGHT_COLOR + self._highlightStyle = self._DEFAULT_HIGHLIGHT_STYLE self._highlighted = False + self.sigItemChanged.connect(self.__itemChanged) + + def __itemChanged(self, event): + if event == ItemChangedType.YAXIS: + # TODO hackish data range implementation + plot = self.getPlot() + if plot is not None: + plot._invalidateDataRange() + def _addBackendRenderer(self, backend): """Update backend renderer""" # Filter-out values <= 0 @@ -79,11 +197,13 @@ class Curve(Points, ColorMixIn, YAxisMixIn, FillMixIn, LabelsMixIn, LineMixIn): if len(xFiltered) == 0 or not numpy.any(numpy.isfinite(xFiltered)): return None # No data to display, do not add renderer to backend + style = self.getCurrentStyle() + return backend.addCurve(xFiltered, yFiltered, self.getLegend(), - color=self.getCurrentColor(), - symbol=self.getSymbol(), - linestyle=self.getLineStyle(), - linewidth=self.getLineWidth(), + color=style.getColor(), + symbol=style.getSymbol(), + linestyle=style.getLineStyle(), + linewidth=style.getLineWidth(), yaxis=self.getYAxis(), xerror=xerror, yerror=yerror, @@ -91,7 +211,7 @@ class Curve(Points, ColorMixIn, YAxisMixIn, FillMixIn, LabelsMixIn, LineMixIn): selectable=self.isSelectable(), fill=self.isFill(), alpha=self.getAlpha(), - symbolsize=self.getSymbolSize()) + symbolsize=style.getSymbolSize()) def __getitem__(self, item): """Compatibility with PyMca and silx <= 0.4.0""" @@ -158,13 +278,39 @@ class Curve(Points, ColorMixIn, YAxisMixIn, FillMixIn, LabelsMixIn, LineMixIn): # TODO inefficient: better to use backend's setCurveColor self._updated(ItemChangedType.HIGHLIGHTED) + def getHighlightedStyle(self): + """Returns the highlighted style in use + + :rtype: CurveStyle + """ + return self._highlightStyle + + def setHighlightedStyle(self, style): + """Set the style to use for highlighting + + :param CurveStyle style: New style to use + """ + previous = self.getHighlightedStyle() + if style != previous: + assert isinstance(style, CurveStyle) + self._highlightStyle = style + self._updated(ItemChangedType.HIGHLIGHTED_STYLE) + + # Backward compatibility event + if previous.getColor() != style.getColor(): + self._updated(ItemChangedType.HIGHLIGHTED_COLOR) + + @deprecated(replacement='Curve.getHighlightedStyle().getColor()', + since_version='0.9.0') def getHighlightedColor(self): """Returns the RGBA highlight color of the item - :rtype: 4-tuple of int in [0, 255] + :rtype: 4-tuple of float in [0, 1] """ - return self._highlightColor + return self.getHighlightedStyle().getColor() + @deprecated(replacement='Curve.setHighlightedStyle()', + since_version='0.9.0') def setHighlightedColor(self, color): """Set the color to use when highlighted @@ -172,20 +318,45 @@ class Curve(Points, ColorMixIn, YAxisMixIn, FillMixIn, LabelsMixIn, LineMixIn): :type color: str ("#RRGGBB") or (npoints, 4) unsigned byte array or one of the predefined color names defined in colors.py """ - color = colors.rgba(color) - if color != self._highlightColor: - self._highlightColor = color - self._updated(ItemChangedType.HIGHLIGHTED_COLOR) + self.setHighlightedStyle(CurveStyle(color)) + + def getCurrentStyle(self): + """Returns the current curve style. + + Curve style depends on curve highlighting + + :rtype: CurveStyle + """ + if self.isHighlighted(): + style = self.getHighlightedStyle() + color = style.getColor() + linestyle = style.getLineStyle() + linewidth = style.getLineWidth() + symbol = style.getSymbol() + symbolsize = style.getSymbolSize() + + 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) + else: + return CurveStyle(color=self.getColor(), + linestyle=self.getLineStyle(), + linewidth=self.getLineWidth(), + symbol=self.getSymbol(), + symbolsize=self.getSymbolSize()) + + @deprecated(replacement='Curve.getCurrentStyle()', + since_version='0.9.0') def getCurrentColor(self): """Returns the current color of the curve. This color is either the color of the curve or the highlighted color, depending on the highlight state. - :rtype: 4-tuple of int in [0, 255] + :rtype: 4-tuple of float in [0, 1] """ - if self.isHighlighted(): - return self.getHighlightedColor() - else: - return self.getColor() + return self.getCurrentStyle().getColor() diff --git a/silx/gui/plot/items/histogram.py b/silx/gui/plot/items/histogram.py index 3545345..389e8a6 100644 --- a/silx/gui/plot/items/histogram.py +++ b/silx/gui/plot/items/histogram.py @@ -27,7 +27,7 @@ __authors__ = ["H. Payno", "T. Vincent"] __license__ = "MIT" -__date__ = "27/06/2017" +__date__ = "28/08/2018" import logging @@ -290,6 +290,11 @@ class Histogram(Item, AlphaMixIn, ColorMixIn, FillMixIn, self._edges = edges self._alignement = align + if self.isVisible(): + plot = self.getPlot() + if plot is not None: + plot._invalidateDataRange() + self._updated(ItemChangedType.DATA) def getAlignment(self): diff --git a/silx/gui/plot/items/marker.py b/silx/gui/plot/items/marker.py index 8f79033..09767a5 100644 --- a/silx/gui/plot/items/marker.py +++ b/silx/gui/plot/items/marker.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2017 European Synchrotron Radiation Facility +# Copyright (c) 2017-2018 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 @@ -32,7 +32,7 @@ __date__ = "06/03/2017" import logging -from .core import (Item, DraggableMixIn, ColorMixIn, SymbolMixIn, +from .core import (Item, DraggableMixIn, ColorMixIn, LineMixIn, SymbolMixIn, ItemChangedType) @@ -55,11 +55,9 @@ class _BaseMarker(Item, DraggableMixIn, ColorMixIn): self._y = None self._constraint = self._defaultConstraint - def _addBackendRenderer(self, backend): - """Update backend renderer""" - # TODO not very nice way to do it, but simple - symbol = self.getSymbol() if isinstance(self, Marker) else None - + def _addRendererCall(self, backend, + symbol=None, linestyle='-', linewidth=1): + """Perform the update of the backend renderer""" return backend.addMarker( x=self.getXPosition(), y=self.getYPosition(), @@ -69,8 +67,14 @@ class _BaseMarker(Item, DraggableMixIn, ColorMixIn): selectable=self.isSelectable(), draggable=self.isDraggable(), symbol=symbol, + linestyle=linestyle, + linewidth=linewidth, constraint=self.getConstraint()) + def _addBackendRenderer(self, backend): + """Update backend renderer""" + raise NotImplementedError() + def isOverlay(self): """Return true if marker is drawn as an overlay. @@ -175,6 +179,9 @@ class Marker(_BaseMarker, SymbolMixIn): self._x = 0. self._y = 0. + def _addBackendRenderer(self, backend): + return self._addRendererCall(backend, symbol=self.getSymbol()) + def _setConstraint(self, constraint): """Set the constraint function of the marker drag. @@ -197,11 +204,24 @@ class Marker(_BaseMarker, SymbolMixIn): return x, self.getYPosition() -class XMarker(_BaseMarker): - """Description of a marker""" +class _LineMarker(_BaseMarker, LineMixIn): + """Base class for line markers""" def __init__(self): _BaseMarker.__init__(self) + LineMixIn.__init__(self) + + def _addBackendRenderer(self, backend): + return self._addRendererCall(backend, + linestyle=self.getLineStyle(), + linewidth=self.getLineWidth()) + + +class XMarker(_LineMarker): + """Description of a marker""" + + def __init__(self): + _LineMarker.__init__(self) self._x = 0. def setPosition(self, x, y): @@ -219,11 +239,11 @@ class XMarker(_BaseMarker): self._updated(ItemChangedType.POSITION) -class YMarker(_BaseMarker): +class YMarker(_LineMarker): """Description of a marker""" def __init__(self): - _BaseMarker.__init__(self) + _LineMarker.__init__(self) self._y = 0. def setPosition(self, x, y): diff --git a/silx/gui/plot/items/scatter.py b/silx/gui/plot/items/scatter.py index 72b8496..acc74b4 100644 --- a/silx/gui/plot/items/scatter.py +++ b/silx/gui/plot/items/scatter.py @@ -53,7 +53,8 @@ class Scatter(Points, ColormapMixIn): Points.__init__(self) ColormapMixIn.__init__(self) self._value = () - + self.__alpha = None + def _addBackendRenderer(self, backend): """Update backend renderer""" # Filter-out values <= 0 @@ -66,6 +67,9 @@ class Scatter(Points, ColormapMixIn): cmap = self.getColormap() rgbacolors = cmap.applyToData(self._value) + if self.__alpha is not None: + rgbacolors[:, -1] = (rgbacolors[:, -1] * self.__alpha).astype(numpy.uint8) + return backend.addCurve(xFiltered, yFiltered, self.getLegend(), color=rgbacolors, symbol=self.getSymbol(), @@ -112,6 +116,15 @@ class Scatter(Points, ColormapMixIn): """ return numpy.array(self._value, copy=copy) + def getAlphaData(self, copy=True): + """Returns the alpha (transparency) assigned to the scatter data points. + + :param copy: True (Default) to get a copy, + False to use internal representation (do not modify!) + :rtype: numpy.ndarray + """ + return numpy.array(self.__alpha, copy=copy) + def getData(self, copy=True, displayed=False): """Returns the x, y coordinates and the value of the data points @@ -137,7 +150,7 @@ class Scatter(Points, ColormapMixIn): self.getYErrorData(copy)) # reimplemented from Points to handle `value` - def setData(self, x, y, value, xerror=None, yerror=None, copy=True): + def setData(self, x, y, value, xerror=None, yerror=None, alpha=None, copy=True): """Set the data of the scatter. :param numpy.ndarray x: The data corresponding to the x coordinates. @@ -152,6 +165,8 @@ class Scatter(Points, ColormapMixIn): row 1 for negative errors. :param yerror: Values with the uncertainties on the y values :type yerror: A float, or a numpy.ndarray of float32. See xerror. + :param alpha: Values with the transparency (between 0 and 1) + :type alpha: A float, or a numpy.ndarray of float32 :param bool copy: True make a copy of the data (default), False to use provided arrays. """ @@ -161,6 +176,17 @@ class Scatter(Points, ColormapMixIn): self._value = value + if alpha is not None: + # Make sure alpha is an array of float in [0, 1] + alpha = numpy.array(alpha, copy=copy) + assert alpha.ndim == 1 + assert len(x) == len(alpha) + if alpha.dtype.kind != 'f': + alpha = alpha.astype(numpy.float32) + if numpy.any(numpy.logical_or(alpha < 0., alpha > 1.)): + alpha = numpy.clip(alpha, 0., 1.) + self.__alpha = alpha + # set x, y, xerror, yerror # call self._updated + plot._invalidateDataRange() |