summaryrefslogtreecommitdiff
path: root/src/silx/gui/plot/backends/BackendBase.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/silx/gui/plot/backends/BackendBase.py')
-rwxr-xr-xsrc/silx/gui/plot/backends/BackendBase.py568
1 files changed, 568 insertions, 0 deletions
diff --git a/src/silx/gui/plot/backends/BackendBase.py b/src/silx/gui/plot/backends/BackendBase.py
new file mode 100755
index 0000000..1e86807
--- /dev/null
+++ b/src/silx/gui/plot/backends/BackendBase.py
@@ -0,0 +1,568 @@
+# 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'
+
+
+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
+
+ 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()
+
+ 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()
+
+ 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()
+
+ 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()
+
+ 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
+
+ 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
+
+ def setGraphCursorShape(self, cursor):
+ """Set the cursor shape.
+
+ To override in interactive backends.
+
+ :param str cursor: Name of the cursor shape or None
+ """
+ pass
+
+ 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
+
+ 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()))
+
+ 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
+
+ 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.
+
+ def getWidgetHandle(self):
+ """Return the widget this backend is drawing to."""
+ return None
+
+ def postRedisplay(self):
+ """Trigger backend update and repaint."""
+ self.replot()
+
+ def replot(self):
+ """Redraw the plot."""
+ with self._plot._paintContext():
+ pass
+
+ 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
+
+ def setGraphTitle(self, title):
+ """Set the main title of the plot.
+
+ :param str title: Title associated to the plot
+ """
+ pass
+
+ def setGraphXLabel(self, label):
+ """Set the X axis label.
+
+ :param str label: label associated to the plot bottom X axis
+ """
+ pass
+
+ 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
+
+ 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
+
+ def getGraphXLimits(self):
+ """Get the graph X (bottom) limits.
+
+ :return: Minimum and maximum values of the X axis
+ """
+ return self.__xLimits
+
+ 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
+
+ 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]
+
+ 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
+
+
+ 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
+
+ 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
+
+ def isXAxisTimeSeries(self):
+ """Return True if the X-axis scale shows datetime objects.
+
+ :rtype: bool
+ """
+ return self.__xAxisTimeSeries
+
+ 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)
+
+ 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
+
+ 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
+
+ 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)
+
+ def isYAxisInverted(self):
+ """Return True if left Y axis is inverted, False otherwise."""
+ return self.__yAxisInverted
+
+ def isKeepDataAspectRatio(self):
+ """Returns whether the plot is keeping data aspect ratio or not."""
+ return self.__keepDataAspectRatio
+
+ 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)
+
+ 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
+
+ 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()
+
+ 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()
+
+ 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()
+
+ 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
+
+ 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
+
+ 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