summaryrefslogtreecommitdiff
path: root/silx/gui/plot/items
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/plot/items')
-rw-r--r--silx/gui/plot/items/core.py23
-rw-r--r--silx/gui/plot/items/curve.py213
-rw-r--r--silx/gui/plot/items/histogram.py7
-rw-r--r--silx/gui/plot/items/marker.py42
-rw-r--r--silx/gui/plot/items/scatter.py30
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()