Source code for silx.gui.plot.items.curve

# /*##########################################################################
#
# Copyright (c) 2017-2023 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
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ###########################################################################*/
"""This module provides the :class:`Curve` item of the :class:`Plot`.
"""
from __future__ import annotations

__authors__ = ["T. Vincent"]
__license__ = "MIT"
__date__ = "24/04/2018"


import logging

import numpy

from ....utils.deprecation import deprecated_warning
from ... import colors
from .core import (
    PointsBase,
    LabelsMixIn,
    ColorMixIn,
    YAxisMixIn,
    FillMixIn,
    LineMixIn,
    LineGapColorMixIn,
    LineStyleType,
    SymbolMixIn,
    BaselineMixIn,
    HighlightedMixIn,
    _Style,
)
from silx._utils import NP_OPTIONAL_COPY


_logger = logging.getLogger(__name__)


[docs] class CurveStyle(_Style): """Object storing the style of a curve. Set a value to None to use the default :param color: Color :param linestyle: Style of the line :param linewidth: Width of the line :param symbol: Symbol for markers :param symbolsize: Size of the markers :param gapcolor: Color of gaps of dashed line """ def __init__( self, color: colors.RGBAColorType | None = None, linestyle: LineStyleType | None = None, linewidth: float | None = None, symbol: str | None = None, symbolsize: float | None = None, gapcolor: colors.RGBAColorType | None = None, ): if color is None: self._color = None else: if isinstance(color, str): color = colors.rgba(color) else: # array-like expected color = numpy.asarray(color) if color.ndim == 1: # Array is 1D, this is a single color color = colors.rgba(color) self._color = color if not LineMixIn.isValidLineStyle(linestyle): raise ValueError(f"Not a valid line style: {linestyle}") 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) self._gapcolor = None if gapcolor is None else colors.rgba(gapcolor)
[docs] 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 or NP_OPTIONAL_COPY) 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
[docs] def getLineStyle(self) -> LineStyleType | None: """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 - (offset, (dash pattern)) """ return self._linestyle
[docs] def getLineWidth(self): """Return the curve line width in pixels or None if not set. :rtype: Union[float,None] """ return self._linewidth
[docs] 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
[docs] 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() and self.getLineGapColor() == other.getLineGapColor() ) else: return False
[docs] class Curve( PointsBase, ColorMixIn, YAxisMixIn, FillMixIn, LabelsMixIn, LineMixIn, LineGapColorMixIn, BaselineMixIn, HighlightedMixIn, ): """Description of a curve""" _DEFAULT_Z_LAYER = 1 """Default overlay layer for curves""" _DEFAULT_SELECTABLE = True """Default selectable state for curves""" _DEFAULT_LINEWIDTH = 1.0 """Default line width of the curve""" _DEFAULT_LINESTYLE = "-" """Default line style of the curve""" _DEFAULT_HIGHLIGHT_STYLE = CurveStyle(color="black") """Default highlight style of the item""" _DEFAULT_BASELINE = None def __init__(self): PointsBase.__init__(self) ColorMixIn.__init__(self) YAxisMixIn.__init__(self) FillMixIn.__init__(self) LabelsMixIn.__init__(self) LineMixIn.__init__(self) LineGapColorMixIn.__init__(self) BaselineMixIn.__init__(self) HighlightedMixIn.__init__(self) self._setBaseline(Curve._DEFAULT_BASELINE) def _addBackendRenderer(self, backend): """Update backend renderer""" # Filter-out values <= 0 xFiltered, yFiltered, xerror, yerror = self.getData(copy=False, displayed=True) 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, color=style.getColor(), gapcolor=style.getLineGapColor(), symbol=style.getSymbol(), linestyle=style.getLineStyle(), linewidth=style.getLineWidth(), yaxis=self.getYAxis(), xerror=xerror, yerror=yerror, fill=self.isFill(), alpha=self.getAlpha(), symbolsize=style.getSymbolSize(), baseline=self.getBaseline(copy=False), ) def __getitem__(self, item): """Compatibility with PyMca and silx <= 0.4.0""" deprecated_warning( "Attributes", "__getitem__", since_version="2.0.0", replacement="Use Curve methods", ) if isinstance(item, slice): return [self[index] for index in range(*item.indices(5))] elif item == 0: return self.getXData(copy=False) elif item == 1: return self.getYData(copy=False) elif item == 2: return self.getName() elif item == 3: info = self.getInfo(copy=False) return {} if info is None else info elif item == 4: params = { "info": self.getInfo(), "color": self.getColor(), "symbol": self.getSymbol(), "linewidth": self.getLineWidth(), "linestyle": self.getLineStyle(), "xlabel": self.getXLabel(), "ylabel": self.getYLabel(), "yaxis": self.getYAxis(), "xerror": self.getXErrorData(copy=False), "yerror": self.getYErrorData(copy=False), "z": self.getZValue(), "selectable": self.isSelectable(), "fill": self.isFill(), } return params else: raise IndexError("Index out of range: %s", str(item))
[docs] 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() 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, 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(), gapcolor=self.getLineGapColor(), )
[docs] def setData(self, x, y, xerror=None, yerror=None, baseline=None, copy=True): """Set the data of the curve. :param numpy.ndarray x: The data corresponding to the x coordinates. :param numpy.ndarray y: The data corresponding to the y coordinates. :param xerror: Values with the uncertainties on the x values :type xerror: A float, or a numpy.ndarray of float32. If it is an array, it can either be a 1D array of same length as the data or a 2D array with 2 rows of same length as the data: row 0 for positive errors, 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 baseline: curve baseline :type baseline: Union[None,float,numpy.ndarray] :param bool copy: True make a copy of the data (default), False to use provided arrays. """ PointsBase.setData(self, x=x, y=y, xerror=xerror, yerror=yerror, copy=copy) self._setBaseline(baseline=baseline)