# coding: utf-8
# /*##########################################################################
#
# Copyright (c) 2004-2021 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.
"""
__authors__ = ["V.A. Sole", "T. Vincent"]
__license__ = "MIT"
__date__ = "21/12/2018"
import weakref
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., 100.
self.__yLimits = {'left': (1., 100.), 'right': (1., 100.)}
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, 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 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 str linestyle: Type of line::
- ' ' or '' no line
- '-' solid line
- '--' dashed line
- '-.' dash-dot line
- ':' dotted line
: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, linebgcolor):
"""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 str 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
:param float linewidth: Width of the line.
Only relevant for line markers where X or Y is None.
:param str linebgcolor: 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, y, text, color,
symbol, linestyle, linewidth, constraint, yaxis):
"""Add a point, vertical line or horizontal line marker to the plot.
:param float x: Horizontal position of the marker in graph coordinates.
If None, the marker is a horizontal line.
:param float y: Vertical position of the marker in graph coordinates.
If None, the marker is a vertical line.
:param str text: Text associated to the marker (or None for no text)
:param str color: Color to be used for instance 'blue', 'b', '#FF0000'
:param str 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 str 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
:param float 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.
:type constraint: None or a callable that takes the coordinates of
the current cursor position in the plot as input
and that returns the filtered coordinates.
:param str yaxis: The Y axis this marker belongs to in: 'left', 'right'
: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
:type linestyle: None or one of the predefined styles.
"""
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 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 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 float x: The X coordinate in data space.
:param float y: The Y coordinate in data space.
: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