Source code for silx.gui.plot.backends.BackendBase

# /*##########################################################################
#
# Copyright (c) 2004-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.
#
# ############################################################################*/
"""Base class for Plot backends.

It documents the Plot backend API.

This API is a simplified version of PyMca PlotBackend API.
"""

from __future__ import annotations


__authors__ = ["V.A. Sole", "T. Vincent"]
__license__ = "MIT"
__date__ = "21/12/2018"

from collections.abc import Callable
import weakref
from silx.gui.colors import RGBAColorType

from ... import qt


# Names for setCursor
CURSOR_DEFAULT = "default"
CURSOR_POINTING = "pointing"
CURSOR_SIZE_HOR = "size horizontal"
CURSOR_SIZE_VER = "size vertical"
CURSOR_SIZE_ALL = "size all"


[docs] class BackendBase(object): """Class defining the API a backend of the Plot should provide.""" def __init__(self, plot, parent=None): """Init. :param Plot plot: The Plot this backend is attached to :param parent: The parent widget of the plot widget. """ self.__xLimits = 1.0, 100.0 self.__yLimits = {"left": (1.0, 100.0), "right": (1.0, 100.0)} self.__yAxisInverted = False self.__keepDataAspectRatio = False self.__xAxisTimeSeries = False self._xAxisTimeZone = None # Store a weakref to get access to the plot state. self._setPlot(plot) @property def _plot(self): """The plot this backend is attached to.""" if self._plotRef is None: raise RuntimeError("This backend is not attached to a Plot") plot = self._plotRef() if plot is None: raise RuntimeError("This backend is no more attached to a Plot") return plot def _setPlot(self, plot): """Allow to set plot after init. Use with caution, basically **immediately** after init. """ self._plotRef = weakref.ref(plot) # Add methods
[docs] def addCurve( self, x, y, color, gapcolor, symbol, linewidth, linestyle, yaxis, xerror, yerror, fill, alpha, symbolsize, baseline, ): """Add a 1D curve given by x an y to the graph. :param numpy.ndarray x: The data corresponding to the x axis :param numpy.ndarray y: The data corresponding to the y axis :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 - 'o' circle - '.' point - ',' pixel - '+' cross - 'x' x-cross - 'd' diamond - 's' square :param float linewidth: The width of the curve in pixels :param linestyle: Type of line:: - ' ' or '' no line - '-' solid line - '--' dashed line - '-.' dash-dot line - ':' dotted line - (offset, (dash pattern)) :param str yaxis: The Y axis this curve belongs to in: 'left', 'right' :param xerror: Values with the uncertainties on the x values :type xerror: numpy.ndarray or None :param yerror: Values with the uncertainties on the y values :type yerror: numpy.ndarray or None :param bool fill: True to fill the curve, False otherwise :param float alpha: Curve opacity, as a float in [0., 1.] :param float symbolsize: Size of the symbol (if any) drawn at each (x, y) position. :returns: The handle used by the backend to univocally access the curve """ return object()
[docs] def addImage(self, data, origin, scale, colormap, alpha): """Add an image to the plot. :param numpy.ndarray data: (nrows, ncolumns) data or (nrows, ncolumns, RGBA) ubyte array :param origin: (origin X, origin Y) of the data. Default: (0., 0.) :type origin: 2-tuple of float :param scale: (scale X, scale Y) of the data. Default: (1., 1.) :type scale: 2-tuple of float :param ~silx.gui.colors.Colormap colormap: Colormap object to use. Ignored if data is RGB(A). :param float alpha: Opacity of the image, as a float in range [0, 1]. :returns: The handle used by the backend to univocally access the image """ return object()
[docs] def addTriangles(self, x, y, triangles, color, alpha): """Add a set of triangles. :param numpy.ndarray x: The data corresponding to the x axis :param numpy.ndarray y: The data corresponding to the y axis :param numpy.ndarray triangles: The indices to make triangles as a (Ntriangle, 3) array :param numpy.ndarray color: color(s) as (npoints, 4) array :param float alpha: Opacity as a float in [0., 1.] :returns: The triangles' unique identifier used by the backend """ return object()
[docs] def addShape( self, x, y, shape, color, fill, overlay, linestyle, linewidth, gapcolor ): """Add an item (i.e. a shape) to the plot. :param numpy.ndarray x: The X coords of the points of the shape :param numpy.ndarray y: The Y coords of the points of the shape :param str shape: Type of item to be drawn in hline, polygon, rectangle, vline, polylines :param str color: Color of the item :param bool fill: True to fill the shape :param bool overlay: True if item is an overlay, False otherwise :param linestyle: Style of the line. Only relevant for line markers where X or Y is None. Value in: - ' ' no line - '-' solid line - '--' dashed line - '-.' dash-dot line - ':' dotted line - (offset, (dash pattern)) :param float linewidth: Width of the line. Only relevant for line markers where X or Y is None. :param str gapcolor: Background color of the line, e.g., 'blue', 'b', '#FF0000'. It is used to draw dotted line using a second color. :returns: The handle used by the backend to univocally access the item """ return object()
[docs] def addMarker( self, x: float | None, y: float | None, text: str | None, color: str, symbol: str | None, linestyle: str | tuple[float, tuple[float, ...] | None], linewidth: float, constraint: Callable[[float, float], tuple[float, float]] | None, yaxis: str, font: qt.QFont, bgcolor: RGBAColorType | None, ) -> object: """Add a point, vertical line or horizontal line marker to the plot. :param x: Horizontal position of the marker in graph coordinates. If None, the marker is a horizontal line. :param y: Vertical position of the marker in graph coordinates. If None, the marker is a vertical line. :param text: Text associated to the marker (or None for no text) :param color: Color to be used for instance 'blue', 'b', '#FF0000' :param bgcolor: Text background color to be used for instance 'blue', 'b', '#FF0000' :param symbol: Symbol representing the marker. Only relevant for point markers where X and Y are not None. Value in: - 'o' circle - '.' point - ',' pixel - '+' cross - 'x' x-cross - 'd' diamond - 's' square :param linestyle: Style of the line. Only relevant for line markers where X or Y is None. Value in: - ' ' no line - '-' solid line - '--' dashed line - '-.' dash-dot line - ':' dotted line - (offset, (dash pattern)) :param linewidth: Width of the line. Only relevant for line markers where X or Y is None. :param constraint: A function filtering marker displacement by dragging operations or None for no filter. This function is called each time a marker is moved. It takes the coordinates of the current cursor position in the plot as input and that returns the filtered coordinates. :param yaxis: The Y axis this marker belongs to in: 'left', 'right' :param font: QFont to use to render text :return: Handle used by the backend to univocally access the marker """ return object()
# Remove methods
[docs] def remove(self, item): """Remove an existing item from the plot. :param item: A backend specific item handle returned by a add* method """ pass
# Interaction methods
[docs] def setGraphCursorShape(self, cursor): """Set the cursor shape. To override in interactive backends. :param str cursor: Name of the cursor shape or None """ pass
[docs] def setGraphCursor(self, flag, color, linewidth, linestyle): """Toggle the display of a crosshair cursor and set its attributes. To override in interactive backends. :param bool flag: Toggle the display of a crosshair cursor. :param color: The color to use for the crosshair. :type color: A string (either a predefined color name in colors.py or "#RRGGBB")) or a 4 columns unsigned byte array. :param int linewidth: The width of the lines of the crosshair. :param linestyle: Type of line:: - ' ' no line - '-' solid line - '--' dashed line - '-.' dash-dot line - ':' dotted line - (offset, (dash pattern)) :type linestyle: None, one of the predefined styles or (offset, (dash pattern)). """ pass
[docs] def getItemsFromBackToFront(self, condition=None): """Returns the list of plot items order as rendered by the backend. This is the order used for rendering. By default, it takes into account overlays, z value and order of addition of items, but backends can override it. :param callable condition: Callable taking an item as input and returning False for items to skip. If None (default), no item is skipped. :rtype: List[~silx.gui.plot.items.Item] """ # Sort items: Overlays first, then others # and in each category ordered by z and then by order of addition # as content keeps this order. content = self._plot.getItems() if condition is not None: content = [item for item in content if condition(item)] return sorted( content, key=lambda i: ((1 if i.isOverlay() else 0), i.getZValue()) )
[docs] def pickItem(self, x, y, item): """Return picked indices if any, or None. :param float x: The x pixel coord where to pick. :param float y: The y pixel coord where to pick. :param item: A backend item created with add* methods. :return: None if item was not picked, else returns picked indices information. :rtype: Union[None,List] """ return None
# Update curve
[docs] def setCurveColor(self, curve, color): """Set the color of a curve. :param curve: The curve handle :param str color: The color to use. """ pass
# Misc.
[docs] def getWidgetHandle(self): """Return the widget this backend is drawing to.""" return None
[docs] def postRedisplay(self): """Trigger backend update and repaint.""" self.replot()
[docs] def replot(self): """Redraw the plot.""" with self._plot._paintContext(): pass
[docs] def saveGraph(self, fileName, fileFormat, dpi): """Save the graph to a file (or a StringIO) At least "png", "svg" are supported. :param fileName: Destination :type fileName: String or StringIO or BytesIO :param str fileFormat: String specifying the format :param int dpi: The resolution to use or None. """ pass
# Graph labels
[docs] def setGraphTitle(self, title): """Set the main title of the plot. :param str title: Title associated to the plot """ pass
[docs] def setGraphXLabel(self, label): """Set the X axis label. :param str label: label associated to the plot bottom X axis """ pass
[docs] def setGraphYLabel(self, label, axis): """Set the left Y axis label. :param str label: label associated to the plot left Y axis :param str axis: The axis for which to get the limits: left or right """ pass
# Graph limits
[docs] def setLimits(self, xmin, xmax, ymin, ymax, y2min=None, y2max=None): """Set the limits of the X and Y axes at once. :param float xmin: minimum bottom axis value :param float xmax: maximum bottom axis value :param float ymin: minimum left axis value :param float ymax: maximum left axis value :param float y2min: minimum right axis value :param float y2max: maximum right axis value """ self.__xLimits = xmin, xmax self.__yLimits["left"] = ymin, ymax if y2min is not None and y2max is not None: self.__yLimits["right"] = y2min, y2max
[docs] def getGraphXLimits(self): """Get the graph X (bottom) limits. :return: Minimum and maximum values of the X axis """ return self.__xLimits
[docs] def setGraphXLimits(self, xmin, xmax): """Set the limits of X axis. :param float xmin: minimum bottom axis value :param float xmax: maximum bottom axis value """ self.__xLimits = xmin, xmax
[docs] def getGraphYLimits(self, axis): """Get the graph Y (left) limits. :param str axis: The axis for which to get the limits: left or right :return: Minimum and maximum values of the Y axis """ return self.__yLimits[axis]
[docs] def setGraphYLimits(self, ymin, ymax, axis): """Set the limits of the Y axis. :param float ymin: minimum left axis value :param float ymax: maximum left axis value :param str axis: The axis for which to get the limits: left or right """ self.__yLimits[axis] = ymin, ymax
# Graph axes
[docs] def getXAxisTimeZone(self): """Returns tzinfo that is used if the X-Axis plots date-times. None means the datetimes are interpreted as local time. :rtype: datetime.tzinfo of None. """ return self._xAxisTimeZone
[docs] def setXAxisTimeZone(self, tz): """Sets tzinfo that is used if the X-Axis plots date-times. Use None to let the datetimes be interpreted as local time. :rtype: datetime.tzinfo of None. """ self._xAxisTimeZone = tz
[docs] def isXAxisTimeSeries(self): """Return True if the X-axis scale shows datetime objects. :rtype: bool """ return self.__xAxisTimeSeries
[docs] def setXAxisTimeSeries(self, isTimeSeries): """Set whether the X-axis is a time series :param bool flag: True to switch to time series, False for regular axis. """ self.__xAxisTimeSeries = bool(isTimeSeries)
[docs] def setXAxisLogarithmic(self, flag): """Set the X axis scale between linear and log. :param bool flag: If True, the bottom axis will use a log scale """ pass
[docs] def setYAxisLogarithmic(self, flag): """Set the Y axis scale between linear and log. :param bool flag: If True, the left axis will use a log scale """ pass
[docs] def setYAxisInverted(self, flag): """Invert the Y axis. :param bool flag: If True, put the vertical axis origin on the top """ self.__yAxisInverted = bool(flag)
[docs] def isYAxisInverted(self): """Return True if left Y axis is inverted, False otherwise.""" return self.__yAxisInverted
[docs] def isYRightAxisVisible(self) -> bool: """Return True if the Y axis on the right side of the plot is visible""" return False
[docs] def isKeepDataAspectRatio(self): """Returns whether the plot is keeping data aspect ratio or not.""" return self.__keepDataAspectRatio
[docs] def setKeepDataAspectRatio(self, flag): """Set whether to keep data aspect ratio or not. :param flag: True to respect data aspect ratio :type flag: Boolean, default True """ self.__keepDataAspectRatio = bool(flag)
[docs] def setGraphGrid(self, which): """Set grid. :param which: None to disable grid, 'major' for major grid, 'both' for major and minor grid """ pass
# Data <-> Pixel coordinates conversion
[docs] def dataToPixel(self, x, y, axis): """Convert a position in data space to a position in pixels in the widget. :param x: The X coordinate in data space. :type x: float or sequence of float :param y: The Y coordinate in data space. :type y: float or sequence of float :param str axis: The Y axis to use for the conversion ('left' or 'right'). :returns: The corresponding position in pixels or None if the data position is not in the displayed area. :rtype: A tuple of 2 floats: (xPixel, yPixel) or None. """ raise NotImplementedError()
[docs] def pixelToData(self, x, y, axis): """Convert a position in pixels in the widget to a position in the data space. :param float x: The X coordinate in pixels. :param float y: The Y coordinate in pixels. :param str axis: The Y axis to use for the conversion ('left' or 'right'). :returns: The corresponding position in data space or None if the pixel position is not in the plot area. :rtype: A tuple of 2 floats: (xData, yData) or None. """ raise NotImplementedError()
[docs] def getPlotBoundsInPixels(self): """Plot area bounds in widget coordinates in pixels. :return: bounds as a 4-tuple of int: (left, top, width, height) """ raise NotImplementedError()
[docs] def setAxesMargins(self, left: float, top: float, right: float, bottom: float): """Set the size of plot margins as ratios. Values are expected in [0., 1.] :param float left: :param float top: :param float right: :param float bottom: """ pass
[docs] def setForegroundColors(self, foregroundColor, gridColor): """Set foreground and grid colors used to display this widget. :param List[float] foregroundColor: RGBA foreground color of the widget :param List[float] gridColor: RGBA grid color of the data view """ pass
[docs] def setBackgroundColors(self, backgroundColor, dataBackgroundColor): """Set background colors used to display this widget. :param List[float] backgroundColor: RGBA background color of the widget :param Union[Tuple[float],None] dataBackgroundColor: RGBA background color of the data view """ pass