summaryrefslogtreecommitdiff
path: root/silx/gui/plot/Plot.py
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/plot/Plot.py')
-rw-r--r--silx/gui/plot/Plot.py2925
1 files changed, 0 insertions, 2925 deletions
diff --git a/silx/gui/plot/Plot.py b/silx/gui/plot/Plot.py
deleted file mode 100644
index fe0a7b8..0000000
--- a/silx/gui/plot/Plot.py
+++ /dev/null
@@ -1,2925 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2004-2017 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.
-# ###########################################################################*/
-"""Plot API for 1D and 2D data.
-
-The :class:`Plot` implements the plot API initially provided in PyMca.
-
-
-Colormap
---------
-
-The :class:`Plot` uses a dictionary to describe a colormap.
-This dictionary has the following keys:
-
-- 'name': str, name of the colormap. Available colormap are returned by
- :meth:`Plot.getSupportedColormaps`.
- At least 'gray', 'reversed gray', 'temperature',
- 'red', 'green', 'blue' are supported.
-- 'normalization': Either 'linear' or 'log'
-- 'autoscale': bool, True to get bounds from the min and max of the
- data, False to use [vmin, vmax]
-- 'vmin': float, min value, ignored if autoscale is True
-- 'vmax': float, max value, ignored if autoscale is True
-- 'colors': optional, custom colormap.
- Nx3 or Nx4 numpy array of RGB(A) colors,
- either uint8 or float in [0, 1].
- If 'name' is None, then this array is used as the colormap.
-
-
-Plot Events
------------
-
-The Plot sends some event to the registered callback
-(See :meth:`Plot.setCallback`).
-Those events are sent as a dictionary with a key 'event' describing the kind
-of event.
-
-Drawing events
-..............
-
-'drawingProgress' and 'drawingFinished' events are sent during drawing
-interaction (See :meth:`Plot.setInteractiveMode`).
-
-- 'event': 'drawingProgress' or 'drawingFinished'
-- 'parameters': dict of parameters used by the drawing mode.
- It has the following keys: 'shape', 'label', 'color'.
- See :meth:`Plot.setInteractiveMode`.
-- 'points': Points (x, y) in data coordinates of the drawn shape.
- For 'hline' and 'vline', it is the 2 points defining the line.
- For 'line' and 'rectangle', it is the coordinates of the start
- drawing point and the latest drawing point.
- For 'polygon', it is the coordinates of all points of the shape.
-- 'type': The type of drawing in 'line', 'hline', 'polygon', 'rectangle',
- 'vline'.
-- 'xdata' and 'ydata': X coords and Y coords of shape points in data
- coordinates (as in 'points').
-
-When the type is 'rectangle', the following additional keys are provided:
-
-- 'x' and 'y': The origin of the rectangle in data coordinates
-- 'widht' and 'height': The size of the rectangle in data coordinates
-
-
-Mouse events
-............
-
-'mouseMoved', 'mouseClicked' and 'mouseDoubleClicked' events are sent for
-mouse events.
-
-They provide the following keys:
-
-- 'event': 'mouseMoved', 'mouseClicked' or 'mouseDoubleClicked'
-- 'button': the mouse button that was pressed in 'left', 'middle', 'right'
-- 'x' and 'y': The mouse position in data coordinates
-- 'xpixel' and 'ypixel': The mouse position in pixels
-
-
-Marker events
-.............
-
-'hover', 'markerClicked', 'markerMoving' and 'markerMoved' events are
-sent during interaction with markers.
-
-'hover' is sent when the mouse cursor is over a marker.
-'markerClicker' is sent when the user click on a selectable marker.
-'markerMoving' and 'markerMoved' are sent when a draggable marker is moved.
-
-They provide the following keys:
-
-- 'event': 'hover', 'markerClicked', 'markerMoving' or 'markerMoved'
-- 'button': the mouse button that is pressed in 'left', 'middle', 'right'
-- 'draggable': True if the marker is draggable, False otherwise
-- 'label': The legend associated with the clicked image or curve
-- 'selectable': True if the marker is selectable, False otherwise
-- 'type': 'marker'
-- 'x' and 'y': The mouse position in data coordinates
-- 'xdata' and 'ydata': The marker position in data coordinates
-
-'markerClicked' and 'markerMoving' events have a 'xpixel' and a 'ypixel'
-additional keys, that provide the mouse position in pixels.
-
-
-Image and curve events
-......................
-
-'curveClicked' and 'imageClicked' events are sent when a selectable curve
-or image is clicked.
-
-Both share the following keys:
-
-- 'event': 'curveClicked' or 'imageClicked'
-- 'button': the mouse button that was pressed in 'left', 'middle', 'right'
-- 'label': The legend associated with the clicked image or curve
-- 'type': The type of item in 'curve', 'image'
-- 'x' and 'y': The clicked position in data coordinates
-- 'xpixel' and 'ypixel': The clicked position in pixels
-
-'curveClicked' events have a 'xdata' and a 'ydata' additional keys, that
-provide the coordinates of the picked points of the curve.
-There can be more than one point of the curve being picked, and if a line of
-the curve is picked, only the first point of the line is included in the list.
-
-'imageClicked' have a 'col' and a 'row' additional keys, that provide
-the column and row index in the image array that was clicked.
-
-
-Limits changed events
-.....................
-
-'limitsChanged' events are sent when the limits of the plot are changed.
-This can results from user interaction or API calls.
-
-It provides the following keys:
-
-- 'event': 'limitsChanged'
-- 'source': id of the widget that emitted this event.
-- 'xdata': Range of X in graph coordinates: (xMin, xMax).
-- 'ydata': Range of Y in graph coordinates: (yMin, yMax).
-- 'y2data': Range of right axis in graph coordinates (y2Min, y2Max) or None.
-
-Plot state change events
-........................
-
-The following events are emitted when the plot is modified.
-They provide the new state:
-
-- 'setGraphCursor' event with a 'state' key (bool)
-- 'setGraphGrid' event with a 'which' key (str), see :meth:`setGraphGrid`
-- 'setKeepDataAspectRatio' event with a 'state' key (bool)
-- 'setXAxisAutoScale' event with a 'state' key (bool)
-- 'setXAxisLogarithmic' event with a 'state' key (bool)
-- 'setYAxisAutoScale' event with a 'state' key (bool)
-- 'setYAxisInverted' event with a 'state' key (bool)
-- 'setYAxisLogarithmic' event with a 'state' key (bool)
-
-A 'contentChanged' event is triggered when the content of the plot is updated.
-It provides the following keys:
-
-- 'action': The change of the plot: 'add' or 'remove'
-- 'kind': The kind of primitive changed: 'curve', 'image', 'item' or 'marker'
-- 'legend': The legend of the primitive changed.
-
-'activeCurveChanged' and 'activeImageChanged' events with the following keys:
-
-- 'legend': Name (str) of the current active item or None if no active item.
-- 'previous': Name (str) of the previous active item or None if no item was
- active. It is the same as 'legend' if 'updated' == True
-- 'updated': (bool) True if active item name did not changed,
- but active item data or style was updated.
-
-'interactiveModeChanged' event with a 'source' key identifying the object
-setting the interactive mode.
-"""
-
-from __future__ import division
-
-
-__authors__ = ["V.A. Sole", "T. Vincent"]
-__license__ = "MIT"
-__date__ = "16/02/2017"
-
-
-from collections import OrderedDict, namedtuple
-import itertools
-import logging
-
-import numpy
-
-# Import matplotlib backend here to init matplotlib our way
-from .backends.BackendMatplotlib import BackendMatplotlibQt
-
-try:
- from matplotlib import cm as matplotlib_cm
-except ImportError:
- matplotlib_cm = None
-
-from . import Colors
-from . import PlotInteraction
-from . import PlotEvents
-from . import _utils
-
-from . import items
-
-
-_logger = logging.getLogger(__name__)
-
-
-_COLORDICT = Colors.COLORDICT
-_COLORLIST = [_COLORDICT['black'],
- _COLORDICT['blue'],
- _COLORDICT['red'],
- _COLORDICT['green'],
- _COLORDICT['pink'],
- _COLORDICT['yellow'],
- _COLORDICT['brown'],
- _COLORDICT['cyan'],
- _COLORDICT['magenta'],
- _COLORDICT['orange'],
- _COLORDICT['violet'],
- # _COLORDICT['bluegreen'],
- _COLORDICT['grey'],
- _COLORDICT['darkBlue'],
- _COLORDICT['darkRed'],
- _COLORDICT['darkGreen'],
- _COLORDICT['darkCyan'],
- _COLORDICT['darkMagenta'],
- _COLORDICT['darkYellow'],
- _COLORDICT['darkBrown']]
-
-
-"""
-Object returned when requesting the data range.
-"""
-_PlotDataRange = namedtuple('PlotDataRange',
- ['x', 'y', 'yright'])
-
-
-class Plot(object):
- """This class implements the plot API initially provided in PyMca.
-
- Supported backends:
-
- - 'matplotlib' and 'mpl': Matplotlib with Qt.
- - 'opengl' and 'gl': OpenGL backend (requires PyOpenGL and OpenGL >= 2.1)
- - 'none': No backend, to run headless for testing purpose.
-
- :param parent: The parent widget of the plot (Default: None)
- :param backend: The backend to use. A str in:
- 'matplotlib', 'mpl', 'opengl', 'gl', 'none'
- or a :class:`BackendBase.BackendBase` class
- """
-
- DEFAULT_BACKEND = 'matplotlib'
- """Class attribute setting the default backend for all instances."""
-
- colorList = _COLORLIST
- colorDict = _COLORDICT
-
- def __init__(self, parent=None, backend=None):
- self._autoreplot = False
- self._dirty = False
- self._cursorInPlot = False
-
- if backend is None:
- backend = self.DEFAULT_BACKEND
-
- if hasattr(backend, "__call__"):
- self._backend = backend(self, parent)
-
- elif hasattr(backend, "lower"):
- lowerCaseString = backend.lower()
- if lowerCaseString in ("matplotlib", "mpl"):
- backendClass = BackendMatplotlibQt
- elif lowerCaseString in ('gl', 'opengl'):
- from .backends.BackendOpenGL import BackendOpenGL
- backendClass = BackendOpenGL
- elif lowerCaseString == 'none':
- from .backends.BackendBase import BackendBase as backendClass
- else:
- raise ValueError("Backend not supported %s" % backend)
- self._backend = backendClass(self, parent)
-
- else:
- raise ValueError("Backend not supported %s" % str(backend))
-
- super(Plot, self).__init__()
-
- self.setCallback() # set _callback
-
- # Items handling
- self._content = OrderedDict()
- self._contentToUpdate = set()
-
- self._dataRange = None
-
- # line types
- self._styleList = ['-', '--', '-.', ':']
- self._colorIndex = 0
- self._styleIndex = 0
-
- self._activeCurveHandling = True
- self._activeCurveColor = "#000000"
- self._activeLegend = {'curve': None, 'image': None,
- 'scatter': None}
-
- # default properties
- self._cursorConfiguration = None
-
- self._logY = False
- self._logX = False
- self._xAutoScale = True
- self._yAutoScale = True
- self._grid = None
-
- # Store default labels provided to setGraph[X|Y]Label
- self._defaultLabels = {'x': '', 'y': '', 'yright': ''}
- # Store currently displayed labels
- # Current label can differ from input one with active curve handling
- self._currentLabels = {'x': '', 'y': '', 'yright': ''}
-
- self._graphTitle = ''
-
- self.setGraphTitle()
- self.setGraphXLabel()
- self.setGraphYLabel()
- self.setGraphYLabel('', axis='right')
-
- self.setDefaultColormap() # Init default colormap
-
- self.setDefaultPlotPoints(False)
- self.setDefaultPlotLines(True)
-
- self._eventHandler = PlotInteraction.PlotInteraction(self)
- self._eventHandler.setInteractiveMode('zoom', color=(0., 0., 0., 1.))
-
- self._pressedButtons = [] # Currently pressed mouse buttons
-
- self._defaultDataMargins = (0., 0., 0., 0.)
-
- # Only activate autoreplot at the end
- # This avoids errors when loaded in Qt designer
- self._dirty = False
- self._autoreplot = True
-
- def _getDirtyPlot(self):
- """Return the plot dirty flag.
-
- If False, the plot has not changed since last replot.
- If True, the full plot need to be redrawn.
- If 'overlay', only the overlay has changed since last replot.
-
- It can be accessed by backend to check the dirty state.
-
- :return: False, True, 'overlay'
- """
- return self._dirty
-
- def _setDirtyPlot(self, overlayOnly=False):
- """Mark the plot as needing redraw
-
- :param bool overlayOnly: True to redraw only the overlay,
- False to redraw everything
- """
- wasDirty = self._dirty
-
- if not self._dirty and overlayOnly:
- self._dirty = 'overlay'
- else:
- self._dirty = True
-
- if self._autoreplot and not wasDirty:
- self._backend.postRedisplay()
-
- def _invalidateDataRange(self):
- """
- Notifies this Plot instance that the range has changed and will have
- to be recomputed.
- """
- self._dataRange = None
-
- def _updateDataRange(self):
- """
- Recomputes the range of the data displayed on this Plot.
- """
- xMin = yMinLeft = yMinRight = float('nan')
- xMax = yMaxLeft = yMaxRight = float('nan')
-
- for item in self._content.values():
- if item.isVisible():
- bounds = item.getBounds()
- if bounds is not None:
- xMin = numpy.nanmin([xMin, bounds[0]])
- xMax = numpy.nanmax([xMax, bounds[1]])
- # Take care of right axis
- if (isinstance(item, items.YAxisMixIn) and
- item.getYAxis() == 'right'):
- yMinRight = numpy.nanmin([yMinRight, bounds[2]])
- yMaxRight = numpy.nanmax([yMaxRight, bounds[3]])
- else:
- yMinLeft = numpy.nanmin([yMinLeft, bounds[2]])
- yMaxLeft = numpy.nanmax([yMaxLeft, bounds[3]])
-
- def lGetRange(x, y):
- return None if numpy.isnan(x) and numpy.isnan(y) else (x, y)
- xRange = lGetRange(xMin, xMax)
- yLeftRange = lGetRange(yMinLeft, yMaxLeft)
- yRightRange = lGetRange(yMinRight, yMaxRight)
-
- self._dataRange = _PlotDataRange(x=xRange,
- y=yLeftRange,
- yright=yRightRange)
-
- def getDataRange(self):
- """
- Returns this Plot's data range.
-
- :return: a namedtuple with the following members :
- x, y (left y axis), yright. Each member is a tuple (min, max)
- or None if no data is associated with the axis.
- :rtype: namedtuple
- """
- if self._dataRange is None:
- self._updateDataRange()
- return self._dataRange
-
- # Content management
-
- @staticmethod
- def _itemKey(item):
- """Build the key of given :class:`Item` in the plot
-
- :param Item item: The item to make the key from
- :return: (legend, kind)
- :rtype: (str, str)
- """
- if isinstance(item, items.Curve):
- kind = 'curve'
- elif isinstance(item, items.ImageBase):
- kind = 'image'
- elif isinstance(item, items.Scatter):
- kind = 'scatter'
- elif isinstance(item, (items.Marker,
- items.XMarker, items.YMarker)):
- kind = 'marker'
- elif isinstance(item, items.Shape):
- kind = 'item'
- elif isinstance(item, items.Histogram):
- kind = 'histogram'
- else:
- raise ValueError('Unsupported item type %s' % type(item))
-
- return item.getLegend(), kind
-
- def _add(self, item):
- """Add the given :class:`Item` to the plot.
-
- :param Item item: The item to append to the plot content
- """
- key = self._itemKey(item)
- if key in self._content:
- raise RuntimeError('Item already in the plot')
-
- # Add item to plot
- self._content[key] = item
- item._setPlot(self)
- if item.isVisible():
- self._itemRequiresUpdate(item)
- if isinstance(item, (items.Curve, items.ImageBase)):
- self._invalidateDataRange() # TODO handle this automatically
-
- def _remove(self, item):
- """Remove the given :class:`Item` from the plot.
-
- :param Item item: The item to remove from the plot content
- """
- key = self._itemKey(item)
- if key not in self._content:
- raise RuntimeError('Item not in the plot')
-
- # Remove item from plot
- self._content.pop(key)
- self._contentToUpdate.discard(item)
- if item.isVisible():
- self._setDirtyPlot(overlayOnly=item.isOverlay())
- if item.getBounds() is not None:
- self._invalidateDataRange()
- item._removeBackendRenderer(self._backend)
- item._setPlot(None)
-
- def _itemRequiresUpdate(self, item):
- """Called by items in the plot for asynchronous update
-
- :param Item item: The item that required update
- """
- assert item.getPlot() == self
- self._contentToUpdate.add(item)
- self._setDirtyPlot(overlayOnly=item.isOverlay())
-
- # Add
-
- # add * input arguments management:
- # If an arg is set, then use it.
- # Else:
- # If a curve with the same legend exists, then use its arg value
- # Else, use a default value.
- # Store used value.
- # This value is used when curve is updated either internally or by user.
-
- def addCurve(self, x, y, legend=None, info=None,
- replace=False, replot=None,
- color=None, symbol=None,
- linewidth=None, linestyle=None,
- xlabel=None, ylabel=None, yaxis=None,
- xerror=None, yerror=None, z=None, selectable=None,
- fill=None, resetzoom=True,
- histogram=None, copy=True, **kw):
- """Add a 1D curve given by x an y to the graph.
-
- Curves are uniquely identified by their legend.
- To add multiple curves, call :meth:`addCurve` multiple times with
- different legend argument.
- To replace an existing curve, call :meth:`addCurve` with the
- existing curve legend.
- If you want to display the curve values as an histogram see the
- histogram parameter or :meth:`addHistogram`.
-
- When curve parameters are not provided, if a curve with the
- same legend is displayed in the plot, its parameters are used.
-
- :param numpy.ndarray x: The data corresponding to the x coordinates.
- If you attempt to plot an histogram you can set edges values in x.
- In this case len(x) = len(y) + 1
- :param numpy.ndarray y: The data corresponding to the y coordinates
- :param str legend: The legend to be associated to the curve (or None)
- :param info: User-defined information associated to the curve
- :param bool replace: True (the default) to delete already existing
- curves
- :param color: color(s) to be used
- :type color: str ("#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::
-
- - 'o' circle
- - '.' point
- - ',' pixel
- - '+' cross
- - 'x' x-cross
- - 'd' diamond
- - 's' square
- - None (the default) to use default symbol
-
- :param float linewidth: The width of the curve in pixels (Default: 1).
- :param str linestyle: Type of line::
-
- - ' ' no line
- - '-' solid line
- - '--' dashed line
- - '-.' dash-dot line
- - ':' dotted line
- - None (the default) to use default line style
-
- :param str xlabel: Label to show on the X axis when the curve is active
- or None to keep default axis label.
- :param str ylabel: Label to show on the Y axis when the curve is active
- or None to keep default axis label.
- :param str yaxis: The Y axis this curve is attached to.
- Either 'left' (the default) or 'right'
- :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 int z: Layer on which to draw the curve (default: 1)
- This allows to control the overlay.
- :param bool selectable: Indicate if the curve can be selected.
- (Default: True)
- :param bool fill: True to fill the curve, False otherwise (default).
- :param bool resetzoom: True (the default) to reset the zoom.
- :param str histogram: if not None then the curve will be draw as an
- histogram. The step for each values of the curve can be set to the
- left, center or right of the original x curve values.
- If histogram is not None and len(x) == len(y)+1 then x is directly
- take as edges of the histogram.
- Type of histogram::
-
- - None (default)
- - 'left'
- - 'right'
- - 'center'
- :param bool copy: True make a copy of the data (default),
- False to use provided arrays.
- :returns: The key string identify this curve
- """
- # Deprecation warnings
- if replot is not None:
- _logger.warning(
- 'addCurve deprecated replot argument, use resetzoom instead')
- resetzoom = replot and resetzoom
-
- if kw:
- _logger.warning('addCurve: deprecated extra arguments')
-
- # This is an histogram, use addHistogram
- if histogram is not None:
- histoLegend = self.addHistogram(histogram=y,
- edges=x,
- legend=legend,
- color=color,
- fill=fill,
- align=histogram,
- copy=copy)
- histo = self.getHistogram(histoLegend)
-
- histo.setInfo(info)
- if linewidth is not None:
- histo.setLineWidth(linewidth)
- if linestyle is not None:
- histo.setLineStyle(linestyle)
- if xlabel is not None:
- _logger.warning(
- 'addCurve: Histogram does not support xlabel argument')
- if ylabel is not None:
- _logger.warning(
- 'addCurve: Histogram does not support ylabel argument')
- if yaxis is not None:
- histo.setYAxis(yaxis)
- if z is not None:
- histo.setZValue(z)
- if selectable is not None:
- _logger.warning(
- 'addCurve: Histogram does not support selectable argument')
-
- return
-
- legend = 'Unnamed curve 1.1' if legend is None else str(legend)
-
- # Check if curve was previously active
- wasActive = self.getActiveCurve(just_legend=True) == legend
-
- # Create/Update curve object
- curve = self.getCurve(legend)
- if curve is None:
- # No previous curve, create a default one and add it to the plot
- curve = items.Curve() if histogram is None else items.Histogram()
- curve._setLegend(legend)
- # Set default color, linestyle and symbol
- default_color, default_linestyle = self._getColorAndStyle()
- curve.setColor(default_color)
- curve.setLineStyle(default_linestyle)
- curve.setSymbol(self._defaultPlotPoints)
- self._add(curve)
-
- # Override previous/default values with provided ones
- curve.setInfo(info)
- if color is not None:
- curve.setColor(color)
- if symbol is not None:
- curve.setSymbol(symbol)
- if linewidth is not None:
- curve.setLineWidth(linewidth)
- if linestyle is not None:
- curve.setLineStyle(linestyle)
- if xlabel is not None:
- curve._setXLabel(xlabel)
- if ylabel is not None:
- curve._setYLabel(ylabel)
- if yaxis is not None:
- curve.setYAxis(yaxis)
- if z is not None:
- curve.setZValue(z)
- if selectable is not None:
- curve._setSelectable(selectable)
- if fill is not None:
- curve.setFill(fill)
-
- # Set curve data
- # If errors not provided, reuse previous ones
- # TODO: Issue if size of data change but not that of errors
- if xerror is None:
- xerror = curve.getXErrorData(copy=False)
- if yerror is None:
- yerror = curve.getYErrorData(copy=False)
-
- curve.setData(x, y, xerror, yerror, copy=copy)
-
- if replace: # Then remove all other curves
- for c in self.getAllCurves(withhidden=True):
- if c is not curve:
- self._remove(c)
-
- self.notify(
- 'contentChanged', action='add', kind='curve', legend=legend)
-
- if wasActive:
- self.setActiveCurve(curve.getLegend())
-
- if resetzoom:
- # We ask for a zoom reset in order to handle the plot scaling
- # if the user does not want that, autoscale of the different
- # axes has to be set to off.
- self.resetZoom()
-
- return legend
-
- def addHistogram(self,
- histogram,
- edges,
- legend=None,
- color=None,
- fill=None,
- align='center',
- resetzoom=True,
- copy=True):
- """Add an histogram to the graph.
-
- This is NOT computing the histogram, this method takes as parameter
- already computed histogram values.
-
- Histogram are uniquely identified by their legend.
- To add multiple histograms, call :meth:`addHistogram` multiple times
- with different legend argument.
-
- When histogram parameters are not provided, if an histogram with the
- same legend is displayed in the plot, its parameters are used.
-
- :param numpy.ndarray histogram: The values of the histogram.
- :param numpy.ndarray edges:
- The bin edges of the histogram.
- If histogram and edges have the same length, the bin edges
- are computed according to the align parameter.
- :param str legend:
- The legend to be associated to the histogram (or None)
- :param color: color to be used
- :type color: str ("#RRGGBB") or RGB unsigned byte array or
- one of the predefined color names defined in Colors.py
- :param bool fill: True to fill the curve, False otherwise (default).
- :param str align:
- In case histogram values and edges have the same length N,
- the N+1 bin edges are computed according to the alignment in:
- 'center' (default), 'left', 'right'.
- :param bool resetzoom: True (the default) to reset the zoom.
- :param bool copy: True make a copy of the data (default),
- False to use provided arrays.
- :returns: The key string identify this histogram
- """
- legend = 'Unnamed histogram' if legend is None else str(legend)
-
- # Create/Update histogram object
- histo = self.getHistogram(legend)
- if histo is None:
- # No previous histogram, create a default one and
- # add it to the plot
- histo = items.Histogram()
- histo._setLegend(legend)
- histo.setColor(self._getColorAndStyle()[0])
- self._add(histo)
-
- # Override previous/default values with provided ones
- if color is not None:
- histo.setColor(color)
- if fill is not None:
- histo.setFill(fill)
-
- # Set histogram data
- histo.setData(histogram, edges, align=align, copy=copy)
-
- self.notify(
- 'contentChanged', action='add', kind='histogram', legend=legend)
-
- if resetzoom:
- # We ask for a zoom reset in order to handle the plot scaling
- # if the user does not want that, autoscale of the different
- # axes has to be set to off.
- self.resetZoom()
-
- return legend
-
- def addImage(self, data, legend=None, info=None,
- replace=True, replot=None,
- xScale=None, yScale=None, z=None,
- selectable=None, draggable=None,
- colormap=None, pixmap=None,
- xlabel=None, ylabel=None,
- origin=None, scale=None,
- resetzoom=True, copy=True, **kw):
- """Add a 2D dataset or an image to the plot.
-
- It displays either an array of data using a colormap or a RGB(A) image.
-
- Images are uniquely identified by their legend.
- To add multiple images, call :meth:`addImage` multiple times with
- different legend argument.
- To replace/update an existing image, call :meth:`addImage` with the
- existing image legend.
-
- When image parameters are not provided, if an image with the
- same legend is displayed in the plot, its parameters are used.
-
- :param numpy.ndarray data: (nrows, ncolumns) data or
- (nrows, ncolumns, RGBA) ubyte array
- :param str legend: The legend to be associated to the image (or None)
- :param info: User-defined information associated to the image
- :param bool replace: True (default) to delete already existing images
- :param int z: Layer on which to draw the image (default: 0)
- This allows to control the overlay.
- :param bool selectable: Indicate if the image can be selected.
- (default: False)
- :param bool draggable: Indicate if the image can be moved.
- (default: False)
- :param dict colormap: Description of the colormap to use (or None)
- This is ignored if data is a RGB(A) image.
- See :mod:`Plot` for the documentation
- of the colormap dict.
- :param pixmap: Pixmap representation of the data (if any)
- :type pixmap: (nrows, ncolumns, RGBA) ubyte array or None (default)
- :param str xlabel: X axis label to show when this curve is active,
- or None to keep default axis label.
- :param str ylabel: Y axis label to show when this curve is active,
- or None to keep default axis label.
- :param origin: (origin X, origin Y) of the data.
- It is possible to pass a single float if both
- coordinates are equal.
- Default: (0., 0.)
- :type origin: float or 2-tuple of float
- :param scale: (scale X, scale Y) of the data.
- It is possible to pass a single float if both
- coordinates are equal.
- Default: (1., 1.)
- :type scale: float or 2-tuple of float
- :param bool resetzoom: True (the default) to reset the zoom.
- :param bool copy: True make a copy of the data (default),
- False to use provided arrays.
- :returns: The key string identify this image
- """
- # Deprecation warnings
- if xScale is not None or yScale is not None:
- _logger.warning(
- 'addImage deprecated xScale and yScale arguments,'
- 'use origin, scale arguments instead.')
- if origin is None and scale is None:
- origin = xScale[0], yScale[0]
- scale = xScale[1], yScale[1]
- else:
- _logger.warning(
- 'addCurve: xScale, yScale and origin, scale arguments'
- ' are conflicting. xScale and yScale are ignored.'
- ' Use only origin, scale arguments.')
-
- if replot is not None:
- _logger.warning(
- 'addImage deprecated replot argument, use resetzoom instead')
- resetzoom = replot and resetzoom
-
- if kw:
- _logger.warning('addImage: deprecated extra arguments')
-
- legend = "Unnamed Image 1.1" if legend is None else str(legend)
-
- # Check if image was previously active
- wasActive = self.getActiveImage(just_legend=True) == legend
-
- data = numpy.array(data, copy=False)
- assert data.ndim in (2, 3)
-
- image = self.getImage(legend)
- if image is not None and image.getData(copy=False).ndim != data.ndim:
- # Update a data image with RGBA image or the other way around:
- # Remove previous image
- # In this case, we don't retrieve defaults from the previous image
- self._remove(image)
- image = None
-
- if image is None:
- # No previous image, create a default one and add it to the plot
- if data.ndim == 2:
- image = items.ImageData()
- image.setColormap(self.getDefaultColormap())
- else:
- image = items.ImageRgba()
- image._setLegend(legend)
- self._add(image)
-
- # Override previous/default values with provided ones
- image.setInfo(info)
- if origin is not None:
- image.setOrigin(origin)
- if scale is not None:
- image.setScale(scale)
- if z is not None:
- image.setZValue(z)
- if selectable is not None:
- image._setSelectable(selectable)
- if draggable is not None:
- image._setDraggable(draggable)
- if colormap is not None and isinstance(image, items.ColormapMixIn):
- image.setColormap(colormap)
- if xlabel is not None:
- image._setXLabel(xlabel)
- if ylabel is not None:
- image._setYLabel(ylabel)
-
- if data.ndim == 2:
- image.setData(data, alternative=pixmap, copy=copy)
- else: # RGB(A) image
- if pixmap is not None:
- _logger.warning(
- 'addImage: pixmap argument ignored when data is RGB(A)')
- image.setData(data, copy=copy)
-
- if replace:
- for img in self.getAllImages():
- if img is not image:
- self._remove(img)
-
- if len(self.getAllImages()) == 1 or wasActive:
- self.setActiveImage(legend)
-
- self.notify(
- 'contentChanged', action='add', kind='image', legend=legend)
-
- if resetzoom:
- # We ask for a zoom reset in order to handle the plot scaling
- # if the user does not want that, autoscale of the different
- # axes has to be set to off.
- self.resetZoom()
-
- return legend
-
- def addScatter(self, x, y, value, legend=None, colormap=None,
- info=None, symbol=None, xerror=None, yerror=None,
- z=None, copy=True):
- """Add a (x, y, value) scatter to the graph.
-
- Scatters are uniquely identified by their legend.
- To add multiple scatters, call :meth:`addScatter` multiple times with
- different legend argument.
- To replace/update an existing scatter, call :meth:`addScatter` with the
- existing scatter legend.
-
- When scatter parameters are not provided, if a scatter with the
- same legend is displayed in the plot, its parameters are used.
-
- :param numpy.ndarray x: The data corresponding to the x coordinates.
- :param numpy.ndarray y: The data corresponding to the y coordinates
- :param numpy.ndarray value: The data value associated with each point
- :param str legend: The legend to be associated to the scatter (or None)
- :param dict colormap: The colormap to be used for the scatter (or None)
- See :mod:`Plot` for the documentation
- of the colormap dict.
- :param info: User-defined information associated to the curve
- :param str symbol: Symbol to be drawn at each (x, y) position::
-
- - 'o' circle
- - '.' point
- - ',' pixel
- - '+' cross
- - 'x' x-cross
- - 'd' diamond
- - 's' square
- - None (the default) to use default symbol
-
- :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 int z: Layer on which to draw the scatter (default: 1)
- This allows to control the overlay.
-
- :param bool copy: True make a copy of the data (default),
- False to use provided arrays.
- :returns: The key string identify this scatter
- """
- legend = 'Unnamed scatter 1.1' if legend is None else str(legend)
-
- # Check if scatter was previously active
- wasActive = self._getActiveItem(kind='scatter',
- just_legend=True) == legend
-
- # Create/Update curve object
- scatter = self._getItem(kind='scatter', legend=legend)
- if scatter is None:
- # No previous scatter, create a default one and add it to the plot
- scatter = items.Scatter()
- scatter._setLegend(legend)
- scatter.setColormap(self.getDefaultColormap())
- self._add(scatter)
-
- # Override previous/default values with provided ones
- scatter.setInfo(info)
- if symbol is not None:
- scatter.setSymbol(symbol)
- if z is not None:
- scatter.setZValue(z)
- if colormap is not None:
- scatter.setColormap(colormap)
-
- # Set scatter data
- # If errors not provided, reuse previous ones
- if xerror is None:
- xerror = scatter.getXErrorData(copy=False)
- if xerror is not None and len(xerror) != len(x):
- xerror = None
- if yerror is None:
- yerror = scatter.getYErrorData(copy=False)
- if yerror is not None and len(yerror) != len(y):
- yerror = None
-
- scatter.setData(x, y, value, xerror, yerror, copy=copy)
-
- self.notify(
- 'contentChanged', action='add', kind='scatter', legend=legend)
-
- if len(self._getItems(kind="scatter")) == 1 or wasActive:
- self._setActiveItem('scatter', scatter.getLegend())
-
- return legend
-
- def addItem(self, xdata, ydata, legend=None, info=None,
- replace=False,
- shape="polygon", color='black', fill=True,
- overlay=False, z=None, **kw):
- """Add an item (i.e. a shape) to the plot.
-
- Items are uniquely identified by their legend.
- To add multiple items, call :meth:`addItem` multiple times with
- different legend argument.
- To replace/update an existing item, call :meth:`addItem` with the
- existing item legend.
-
- :param numpy.ndarray xdata: The X coords of the points of the shape
- :param numpy.ndarray ydata: The Y coords of the points of the shape
- :param str legend: The legend to be associated to the item
- :param info: User-defined information associated to the item
- :param bool replace: True (default) to delete already existing images
- :param str shape: Type of item to be drawn in
- hline, polygon (the default), rectangle, vline,
- polylines
- :param str color: Color of the item, e.g., 'blue', 'b', '#FF0000'
- (Default: 'black')
- :param bool fill: True (the default) to fill the shape
- :param bool overlay: True if item is an overlay (Default: False).
- This allows for rendering optimization if this
- item is changed often.
- :param int z: Layer on which to draw the item (default: 2)
- :returns: The key string identify this item
- """
- # expected to receive the same parameters as the signal
-
- if kw:
- _logger.warning('addItem deprecated parameters: %s', str(kw))
-
- legend = "Unnamed Item 1.1" if legend is None else str(legend)
-
- z = int(z) if z is not None else 2
-
- if replace:
- self.remove(kind='item')
- else:
- self.remove(legend, kind='item')
-
- item = items.Shape(shape)
- item._setLegend(legend)
- item.setInfo(info)
- item.setColor(color)
- item.setFill(fill)
- item.setOverlay(overlay)
- item.setZValue(z)
- item.setPoints(numpy.array((xdata, ydata)).T)
-
- self._add(item)
-
- self.notify('contentChanged', action='add', kind='item', legend=legend)
-
- return legend
-
- def addXMarker(self, x, legend=None,
- text=None,
- color=None,
- selectable=False,
- draggable=False,
- constraint=None,
- **kw):
- """Add a vertical line marker to the plot.
-
- Markers are uniquely identified by their legend.
- As opposed to curves, images and items, two calls to
- :meth:`addXMarker` without legend argument adds two markers with
- different identifying legends.
-
- :param float x: Position of the marker on the X axis in data
- coordinates
- :param str legend: Legend associated to the marker to identify it
- :param str text: Text to display on the marker.
- :param str color: Color of the marker, e.g., 'blue', 'b', '#FF0000'
- (Default: 'black')
- :param bool selectable: Indicate if the marker can be selected.
- (default: False)
- :param bool draggable: Indicate if the marker can be moved.
- (default: False)
- :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.
- This parameter is only used if draggable is True.
- :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.
- :return: The key string identify this marker
- """
- if kw:
- _logger.warning(
- 'addXMarker deprecated extra parameters: %s', str(kw))
-
- return self._addMarker(x=x, y=None, legend=legend,
- text=text, color=color,
- selectable=selectable, draggable=draggable,
- symbol=None, constraint=constraint)
-
- def addYMarker(self, y,
- legend=None,
- text=None,
- color=None,
- selectable=False,
- draggable=False,
- constraint=None,
- **kw):
- """Add a horizontal line marker to the plot.
-
- Markers are uniquely identified by their legend.
- As opposed to curves, images and items, two calls to
- :meth:`addYMarker` without legend argument adds two markers with
- different identifying legends.
-
- :param float y: Position of the marker on the Y axis in data
- coordinates
- :param str legend: Legend associated to the marker to identify it
- :param str text: Text to display next to the marker.
- :param str color: Color of the marker, e.g., 'blue', 'b', '#FF0000'
- (Default: 'black')
- :param bool selectable: Indicate if the marker can be selected.
- (default: False)
- :param bool draggable: Indicate if the marker can be moved.
- (default: False)
- :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.
- This parameter is only used if draggable is True.
- :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.
- :return: The key string identify this marker
- """
- if kw:
- _logger.warning(
- 'addYMarker deprecated extra parameters: %s', str(kw))
-
- return self._addMarker(x=None, y=y, legend=legend,
- text=text, color=color,
- selectable=selectable, draggable=draggable,
- symbol=None, constraint=constraint)
-
- def addMarker(self, x, y, legend=None,
- text=None,
- color=None,
- selectable=False,
- draggable=False,
- symbol='+',
- constraint=None,
- **kw):
- """Add a point marker to the plot.
-
- Markers are uniquely identified by their legend.
- As opposed to curves, images and items, two calls to
- :meth:`addMarker` without legend argument adds two markers with
- different identifying legends.
-
- :param float x: Position of the marker on the X axis in data
- coordinates
- :param float y: Position of the marker on the Y axis in data
- coordinates
- :param str legend: Legend associated to the marker to identify it
- :param str text: Text to display next to the marker
- :param str color: Color of the marker, e.g., 'blue', 'b', '#FF0000'
- (Default: 'black')
- :param bool selectable: Indicate if the marker can be selected.
- (default: False)
- :param bool draggable: Indicate if the marker can be moved.
- (default: False)
- :param str symbol: Symbol representing the marker in::
-
- - 'o' circle
- - '.' point
- - ',' pixel
- - '+' cross (the default)
- - 'x' x-cross
- - 'd' diamond
- - 's' square
-
- :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.
- This parameter is only used if draggable is True.
- :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.
- :return: The key string identify this marker
- """
- if kw:
- _logger.warning(
- 'addMarker deprecated extra parameters: %s', str(kw))
-
- if x is None:
- xmin, xmax = self.getGraphXLimits()
- x = 0.5 * (xmax + xmin)
-
- if y is None:
- ymin, ymax = self.getGraphYLimits()
- y = 0.5 * (ymax + ymin)
-
- return self._addMarker(x=x, y=y, legend=legend,
- text=text, color=color,
- selectable=selectable, draggable=draggable,
- symbol=symbol, constraint=constraint)
-
- def _addMarker(self, x, y, legend,
- text, color,
- selectable, draggable,
- symbol, constraint):
- """Common method for adding point, vline and hline marker.
-
- See :meth:`addMarker` for argument documentation.
- """
- assert (x, y) != (None, None)
-
- if legend is None: # Find an unused legend
- markerLegends = self._getAllMarkers(just_legend=True)
- for index in itertools.count():
- legend = "Unnamed Marker %d" % index
- if legend not in markerLegends:
- break # Keep this legend
- legend = str(legend)
-
- if x is None:
- markerClass = items.YMarker
- elif y is None:
- markerClass = items.XMarker
- else:
- markerClass = items.Marker
-
- # Create/Update marker object
- marker = self._getMarker(legend)
- if marker is not None and not isinstance(marker, markerClass):
- _logger.warning('Adding marker with same legend'
- ' but different type replaces it')
- self._remove(marker)
- marker = None
-
- if marker is None:
- # No previous marker, create one
- marker = markerClass()
- marker._setLegend(legend)
- self._add(marker)
-
- if text is not None:
- marker.setText(text)
- if color is not None:
- marker.setColor(color)
- if selectable is not None:
- marker._setSelectable(selectable)
- if draggable is not None:
- marker._setDraggable(draggable)
- if symbol is not None:
- marker.setSymbol(symbol)
-
- # TODO to improve, but this ensure constraint is applied
- marker.setPosition(x, y)
- if constraint is not None:
- marker._setConstraint(constraint)
- marker.setPosition(x, y)
-
- self.notify(
- 'contentChanged', action='add', kind='marker', legend=legend)
-
- return legend
-
- # Hide
-
- def isCurveHidden(self, legend):
- """Returns True if the curve associated to legend is hidden, else False
-
- :param str legend: The legend key identifying the curve
- :return: True if the associated curve is hidden, False otherwise
- """
- curve = self._getItem('curve', legend)
- return curve is not None and not curve.isVisible()
-
- def hideCurve(self, legend, flag=True, replot=None):
- """Show/Hide the curve associated to legend.
-
- Even when hidden, the curve is kept in the list of curves.
-
- :param str legend: The legend associated to the curve to be hidden
- :param bool flag: True (default) to hide the curve, False to show it
- """
- if replot is not None:
- _logger.warning('hideCurve deprecated replot parameter')
-
- curve = self._getItem('curve', legend)
- if curve is None:
- _logger.warning('Curve not in plot: %s', legend)
- return
-
- isVisible = not flag
- if isVisible != curve.isVisible():
- curve.setVisible(isVisible)
-
- # Remove
-
- ITEM_KINDS = 'curve', 'image', 'scatter', 'item', 'marker', 'histogram'
-
- def remove(self, legend=None, kind=ITEM_KINDS):
- """Remove one or all element(s) of the given legend and kind.
-
- Examples:
-
- - ``remove()`` clears the plot
- - ``remove(kind='curve')`` removes all curves from the plot
- - ``remove('myCurve', kind='curve')`` removes the curve with
- legend 'myCurve' from the plot.
- - ``remove('myImage, kind='image')`` removes the image with
- legend 'myImage' from the plot.
- - ``remove('myImage')`` removes elements (for instance curve, image,
- item and marker) with legend 'myImage'.
-
- :param str legend: The legend associated to the element to remove,
- or None to remove
- :param kind: The kind of elements to remove from the plot.
- In: 'all', 'curve', 'image', 'item', 'marker'.
- By default, it removes all kind of elements.
- :type kind: str or tuple of str to specify multiple kinds.
- """
- if kind is 'all': # Replace all by tuple of all kinds
- kind = self.ITEM_KINDS
-
- if kind in self.ITEM_KINDS: # Kind is a str, make it a tuple
- kind = (kind,)
-
- for aKind in kind:
- assert aKind in self.ITEM_KINDS
-
- if legend is None: # This is a clear
- # Clear each given kind
- for aKind in kind:
- for legend in self._getItems(
- kind=aKind, just_legend=True, withhidden=True):
- self.remove(legend=legend, kind=aKind)
-
- else: # This is removing a single element
- # Remove each given kind
- for aKind in kind:
- item = self._getItem(aKind, legend)
- if item is not None:
- if aKind in ('curve', 'image'):
- if self._getActiveItem(aKind) == item:
- # Reset active item
- self._setActiveItem(aKind, None)
-
- self._remove(item)
-
- if (aKind == 'curve' and
- not self.getAllCurves(just_legend=True,
- withhidden=True)):
- self._colorIndex = 0
- self._styleIndex = 0
-
- self.notify('contentChanged', action='remove',
- kind=aKind, legend=legend)
-
- def removeCurve(self, legend):
- """Remove the curve associated to legend from the graph.
-
- :param str legend: The legend associated to the curve to be deleted
- """
- if legend is None:
- return
- self.remove(legend, kind='curve')
-
- def removeImage(self, legend):
- """Remove the image associated to legend from the graph.
-
- :param str legend: The legend associated to the image to be deleted
- """
- if legend is None:
- return
- self.remove(legend, kind='image')
-
- def removeItem(self, legend):
- """Remove the item associated to legend from the graph.
-
- :param str legend: The legend associated to the item to be deleted
- """
- if legend is None:
- return
- self.remove(legend, kind='item')
-
- def removeMarker(self, legend):
- """Remove the marker associated to legend from the graph.
-
- :param str legend: The legend associated to the marker to be deleted
- """
- if legend is None:
- return
- self.remove(legend, kind='marker')
-
- # Clear
-
- def clear(self):
- """Remove everything from the plot."""
- self.remove()
-
- def clearCurves(self):
- """Remove all the curves from the plot."""
- self.remove(kind='curve')
-
- def clearImages(self):
- """Remove all the images from the plot."""
- self.remove(kind='image')
-
- def clearItems(self):
- """Remove all the items from the plot. """
- self.remove(kind='item')
-
- def clearMarkers(self):
- """Remove all the markers from the plot."""
- self.remove(kind='marker')
-
- # Interaction
-
- def getGraphCursor(self):
- """Returns the state of the crosshair cursor.
-
- See :meth:`setGraphCursor`.
-
- :return: None if the crosshair cursor is not active,
- else a tuple (color, linewidth, linestyle).
- """
- return self._cursorConfiguration
-
- def setGraphCursor(self, flag=False, color='black',
- linewidth=1, linestyle='-'):
- """Toggle the display of a crosshair cursor and set its attributes.
-
- :param bool flag: Toggle the display of a crosshair cursor.
- The crosshair cursor is hidden by default.
- :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
- (Default: black).
- :param int linewidth: The width of the lines of the crosshair
- (Default: 1).
- :param str linestyle: Type of line::
-
- - ' ' no line
- - '-' solid line (the default)
- - '--' dashed line
- - '-.' dash-dot line
- - ':' dotted line
- """
- if flag:
- self._cursorConfiguration = color, linewidth, linestyle
- else:
- self._cursorConfiguration = None
-
- self._backend.setGraphCursor(flag=flag, color=color,
- linewidth=linewidth, linestyle=linestyle)
- self._setDirtyPlot()
- self.notify('setGraphCursor',
- state=self._cursorConfiguration is not None)
-
- def pan(self, direction, factor=0.1):
- """Pan the graph in the given direction by the given factor.
-
- Warning: Pan of right Y axis not implemented!
-
- :param str direction: One of 'up', 'down', 'left', 'right'.
- :param float factor: Proportion of the range used to pan the graph.
- Must be strictly positive.
- """
- assert direction in ('up', 'down', 'left', 'right')
- assert factor > 0.
-
- if direction in ('left', 'right'):
- xFactor = factor if direction == 'right' else - factor
- xMin, xMax = self.getGraphXLimits()
-
- xMin, xMax = _utils.applyPan(xMin, xMax, xFactor,
- self.isXAxisLogarithmic())
- self.setGraphXLimits(xMin, xMax)
-
- else: # direction in ('up', 'down')
- sign = -1. if self.isYAxisInverted() else 1.
- yFactor = sign * (factor if direction == 'up' else -factor)
- yMin, yMax = self.getGraphYLimits()
- yIsLog = self.isYAxisLogarithmic()
-
- yMin, yMax = _utils.applyPan(yMin, yMax, yFactor, yIsLog)
- self.setGraphYLimits(yMin, yMax, axis='left')
-
- y2Min, y2Max = self.getGraphYLimits(axis='right')
-
- y2Min, y2Max = _utils.applyPan(y2Min, y2Max, yFactor, yIsLog)
- self.setGraphYLimits(y2Min, y2Max, axis='right')
-
- # Active Curve/Image
-
- def isActiveCurveHandling(self):
- """Returns True if active curve selection is enabled."""
- return self._activeCurveHandling
-
- def setActiveCurveHandling(self, flag=True):
- """Enable/Disable active curve selection.
-
- :param bool flag: True (the default) to enable active curve selection.
- """
- if not flag:
- self.setActiveCurve(None) # Reset active curve
-
- self._activeCurveHandling = bool(flag)
-
- def getActiveCurveColor(self):
- """Get the color used to display the currently active curve.
-
- See :meth:`setActiveCurveColor`.
- """
- return self._activeCurveColor
-
- def setActiveCurveColor(self, color="#000000"):
- """Set the color to use to display the currently active curve.
-
- :param str color: Color of the active curve,
- e.g., 'blue', 'b', '#FF0000' (Default: 'black')
- """
- if color is None:
- color = "black"
- if color in self.colorDict:
- color = self.colorDict[color]
- self._activeCurveColor = color
-
- def getActiveCurve(self, just_legend=False):
- """Return the currently active curve.
-
- It returns None in case of not having an active curve.
-
- :param bool just_legend: True to get the legend of the curve,
- False (the default) to get the curve data
- and info.
- :return: Active curve's legend or corresponding
- :class:`.items.Curve`
- :rtype: str or :class:`.items.Curve` or None
- """
- if not self.isActiveCurveHandling():
- return None
-
- return self._getActiveItem(kind='curve', just_legend=just_legend)
-
- def setActiveCurve(self, legend, replot=None):
- """Make the curve associated to legend the active curve.
-
- :param legend: The legend associated to the curve
- or None to have no active curve.
- :type legend: str or None
- """
- if replot is not None:
- _logger.warning('setActiveCurve deprecated replot parameter')
-
- if not self.isActiveCurveHandling():
- return
-
- return self._setActiveItem(kind='curve', legend=legend)
-
- def getActiveImage(self, just_legend=False):
- """Returns the currently active image.
-
- It returns None in case of not having an active image.
-
- :param bool just_legend: True to get the legend of the image,
- False (the default) to get the image data
- and info.
- :return: Active image's legend or corresponding image object
- :rtype: str, :class:`.items.ImageData`, :class:`.items.ImageRgba`
- or None
- """
- return self._getActiveItem(kind='image', just_legend=just_legend)
-
- def setActiveImage(self, legend, replot=None):
- """Make the image associated to legend the active image.
-
- :param str legend: The legend associated to the image
- or None to have no active image.
- """
- if replot is not None:
- _logger.warning('setActiveImage deprecated replot parameter')
-
- return self._setActiveItem(kind='image', legend=legend)
-
- def _getActiveItem(self, kind, just_legend=False):
- """Return the currently active item of that kind if any
-
- :param str kind: Type of item: 'curve', 'scatter' or 'image'
- :param bool just_legend: True to get the legend,
- False (default) to get the item
- :return: legend or item or None if no active item
- """
- assert kind in ('curve', 'scatter', 'image')
-
- if self._activeLegend[kind] is None:
- return None
-
- if (self._activeLegend[kind], kind) not in self._content:
- self._activeLegend[kind] = None
- return None
-
- if just_legend:
- return self._activeLegend[kind]
- else:
- return self._getItem(kind, self._activeLegend[kind])
-
- def _setActiveItem(self, kind, legend):
- """Make the curve associated to legend the active curve.
-
- :param str kind: Type of item: 'curve' or 'image'
- :param legend: The legend associated to the curve
- or None to have no active curve.
- :type legend: str or None
- """
- assert kind in ('curve', 'image', 'scatter')
-
- xLabel = self._defaultLabels['x']
- yLabel = self._defaultLabels['y']
- yRightLabel = self._defaultLabels['yright']
-
- oldActiveItem = self._getActiveItem(kind=kind)
-
- # Curve specific: Reset highlight of previous active curve
- if kind == 'curve' and oldActiveItem is not None:
- oldActiveItem.setHighlighted(False)
-
- if legend is None:
- self._activeLegend[kind] = None
- else:
- legend = str(legend)
- item = self._getItem(kind, legend)
- if item is None:
- _logger.warning("This %s does not exist: %s", kind, legend)
- self._activeLegend[kind] = None
- else:
- self._activeLegend[kind] = legend
-
- # Curve specific: handle highlight
- if kind == 'curve':
- item.setHighlightedColor(self.getActiveCurveColor())
- item.setHighlighted(True)
-
- if isinstance(item, items.LabelsMixIn):
- if item.getXLabel() is not None:
- xLabel = item.getXLabel()
- if item.getYLabel() is not None:
- if (isinstance(item, items.YAxisMixIn) and
- item.getYAxis() == 'right'):
- yRightLabel = item.getYLabel()
- else:
- yLabel = item.getYLabel()
-
- # Store current labels and update plot
- self._currentLabels['x'] = xLabel
- self._currentLabels['y'] = yLabel
- self._currentLabels['yright'] = yRightLabel
-
- self._backend.setGraphXLabel(xLabel)
- self._backend.setGraphYLabel(yLabel, axis='left')
- self._backend.setGraphYLabel(yRightLabel, axis='right')
-
- self._setDirtyPlot()
-
- activeLegend = self._activeLegend[kind]
- if oldActiveItem is not None or activeLegend is not None:
- if oldActiveItem is None:
- oldActiveLegend = None
- else:
- oldActiveLegend = oldActiveItem.getLegend()
- self.notify(
- 'active' + kind[0].upper() + kind[1:] + 'Changed',
- updated=oldActiveLegend != activeLegend,
- previous=oldActiveLegend,
- legend=activeLegend)
-
- return activeLegend
-
- # Getters
-
- def getAllCurves(self, just_legend=False, withhidden=False):
- """Returns all curves legend or info and data.
-
- It returns an empty list in case of not having any curve.
-
- If just_legend is False, it returns a list of :class:`items.Curve`
- objects describing the curves.
- If just_legend is True, it returns a list of curves' legend.
-
- :param bool just_legend: True to get the legend of the curves,
- False (the default) to get the curves' data
- and info.
- :param bool withhidden: False (default) to skip hidden curves.
- :return: list of curves' legend or :class:`.items.Curve`
- :rtype: list of str or list of :class:`.items.Curve`
- """
- return self._getItems(kind='curve',
- just_legend=just_legend,
- withhidden=withhidden)
-
- def getCurve(self, legend=None):
- """Get the object describing a specific curve.
-
- It returns None in case no matching curve is found.
-
- :param str legend:
- The legend identifying the curve.
- If not provided or None (the default), the active curve is returned
- or if there is no active curve, the latest updated curve that is
- not hidden is returned if there are curves in the plot.
- :return: None or :class:`.items.Curve` object
- """
- return self._getItem(kind='curve', legend=legend)
-
- def getAllImages(self, just_legend=False):
- """Returns all images legend or objects.
-
- It returns an empty list in case of not having any image.
-
- If just_legend is False, it returns a list of :class:`items.ImageBase`
- objects describing the images.
- If just_legend is True, it returns a list of legends.
-
- :param bool just_legend: True to get the legend of the images,
- False (the default) to get the images'
- object.
- :return: list of images' legend or :class:`.items.ImageBase`
- :rtype: list of str or list of :class:`.items.ImageBase`
- """
- return self._getItems(kind='image',
- just_legend=just_legend,
- withhidden=True)
-
- def getImage(self, legend=None):
- """Get the object describing a specific image.
-
- It returns None in case no matching image is found.
-
- :param str legend:
- The legend identifying the image.
- If not provided or None (the default), the active image is returned
- or if there is no active image, the latest updated image
- is returned if there are images in the plot.
- :return: None or :class:`.items.ImageBase` object
- """
- return self._getItem(kind='image', legend=legend)
-
- def getScatter(self, legend=None):
- """Get the object describing a specific scatter.
-
- It returns None in case no matching scatter is found.
-
- :param str legend:
- The legend identifying the scatter.
- If not provided or None (the default), the active scatter is
- returned or if there is no active scatter, the latest updated
- scatter is returned if there are scatters in the plot.
- :return: None or :class:`.items.Scatter` object
- """
- return self._getItem(kind='scatter', legend=legend)
-
- def getHistogram(self, legend=None):
- """Get the object describing a specific histogram.
-
- It returns None in case no matching histogram is found.
-
- :param str legend:
- The legend identifying the histogram.
- If not provided or None (the default), the latest updated scatter
- is returned if there are histograms in the plot.
- :return: None or :class:`.items.Histogram` object
- """
- return self._getItem(kind='histogram', legend=legend)
-
- def _getItems(self, kind, just_legend=False, withhidden=False):
- """Retrieve all items of a kind in the plot
-
- :param str kind: Type of item: 'curve' or 'image'
- :param bool just_legend: True to get the legend of the curves,
- False (the default) to get the curves' data
- and info.
- :param bool withhidden: False (default) to skip hidden curves.
- :return: list of legends or item objects
- """
- assert kind in self.ITEM_KINDS
- output = []
- for (legend, type_), item in self._content.items():
- if type_ == kind and (withhidden or item.isVisible()):
- output.append(legend if just_legend else item)
- return output
-
- def _getItem(self, kind, legend=None):
- """Get an item from the plot: either an image or a curve.
-
- Returns None if no match found
-
- :param str kind: Type of item: 'curve' or 'image'
- :param str legend: Legend of the item or
- None to get active or last item
- :return: Object describing the item or None
- """
- assert kind in self.ITEM_KINDS
-
- if legend is not None:
- return self._content.get((legend, kind), None)
- else:
- if kind in ('curve', 'image', 'scatter'):
- item = self._getActiveItem(kind=kind)
- if item is not None: # Return active item if available
- return item
- # Return last visible item if any
- allItems = self._getItems(
- kind=kind, just_legend=False, withhidden=False)
- return allItems[-1] if allItems else None
-
- # Limits
-
- def _notifyLimitsChanged(self):
- """Send an event when plot area limits are changed."""
- xRange = self.getGraphXLimits()
- yRange = self.getGraphYLimits(axis='left')
- y2Range = self.getGraphYLimits(axis='right')
- event = PlotEvents.prepareLimitsChangedSignal(
- id(self.getWidgetHandle()), xRange, yRange, y2Range)
- self.notify(**event)
-
- def _checkLimits(self, min_, max_, axis):
- """Makes sure axis range is not empty
-
- :param float min_: Min axis value
- :param float max_: Max axis value
- :param str axis: 'x', 'y' or 'y2' the axis to deal with
- :return: (min, max) making sure min < max
- :rtype: 2-tuple of float
- """
- if max_ < min_:
- _logger.info('%s axis: max < min, inverting limits.', axis)
- min_, max_ = max_, min_
- elif max_ == min_:
- _logger.info('%s axis: max == min, expanding limits.', axis)
- if min_ == 0.:
- min_, max_ = -0.1, 0.1
- elif min_ < 0:
- min_, max_ = min_ * 1.1, min_ * 0.9
- else: # xmin > 0
- min_, max_ = min_ * 0.9, min_ * 1.1
-
- return min_, max_
-
- def getGraphXLimits(self):
- """Get the graph X (bottom) limits.
-
- :return: Minimum and maximum values of the X axis
- """
- return self._backend.getGraphXLimits()
-
- def setGraphXLimits(self, xmin, xmax, replot=None):
- """Set the graph X (bottom) limits.
-
- :param float xmin: minimum bottom axis value
- :param float xmax: maximum bottom axis value
- """
- if replot is not None:
- _logger.warning('setGraphXLimits deprecated replot parameter')
-
- xmin, xmax = self._checkLimits(xmin, xmax, axis='x')
-
- self._backend.setGraphXLimits(xmin, xmax)
- self._setDirtyPlot()
-
- self._notifyLimitsChanged()
-
- def getGraphYLimits(self, axis='left'):
- """Get the graph Y limits.
-
- :param str axis: The axis for which to get the limits:
- Either 'left' or 'right'
- :return: Minimum and maximum values of the X axis
- """
- assert axis in ('left', 'right')
- return self._backend.getGraphYLimits(axis)
-
- def setGraphYLimits(self, ymin, ymax, axis='left', replot=None):
- """Set the graph Y limits.
-
- :param float ymin: minimum bottom axis value
- :param float ymax: maximum bottom axis value
- :param str axis: The axis for which to get the limits:
- Either 'left' or 'right'
- """
- if replot is not None:
- _logger.warning('setGraphYLimits deprecated replot parameter')
-
- assert axis in ('left', 'right')
-
- ymin, ymax = self._checkLimits(ymin, ymax,
- axis='y' if axis == 'left' else 'y2')
-
- self._backend.setGraphYLimits(ymin, ymax, axis)
- self._setDirtyPlot()
-
- self._notifyLimitsChanged()
-
- def setLimits(self, xmin, xmax, ymin, ymax, y2min=None, y2max=None):
- """Set the limits of the X and Y axes at once.
-
- If y2min or y2max is None, the right Y axis limits are not updated.
-
- :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 or None (the default)
- :param float y2max: maximum right axis value or None (the default)
- """
- # Deal with incorrect values
- xmin, xmax = self._checkLimits(xmin, xmax, axis='x')
- ymin, ymax = self._checkLimits(ymin, ymax, axis='y')
-
- if y2min is None or y2max is None:
- # if one limit is None, both are ignored
- y2min, y2max = None, None
- else:
- y2min, y2max = self._checkLimits(y2min, y2max, axis='y2')
-
- self._backend.setLimits(xmin, xmax, ymin, ymax, y2min, y2max)
- self._setDirtyPlot()
- self._notifyLimitsChanged()
-
- # Title and labels
-
- def getGraphTitle(self):
- """Return the plot main title as a str."""
- return self._graphTitle
-
- def setGraphTitle(self, title=""):
- """Set the plot main title.
-
- :param str title: Main title of the plot (default: '')
- """
- self._graphTitle = str(title)
- self._backend.setGraphTitle(title)
- self._setDirtyPlot()
-
- def getGraphXLabel(self):
- """Return the current X axis label as a str."""
- return self._currentLabels['x']
-
- def setGraphXLabel(self, label="X"):
- """Set the plot X axis label.
-
- The provided label can be temporarily replaced by the X label of the
- active curve if any.
-
- :param str label: The X axis label (default: 'X')
- """
- self._defaultLabels['x'] = label
- self._currentLabels['x'] = label
- self._backend.setGraphXLabel(label)
- self._setDirtyPlot()
-
- def getGraphYLabel(self, axis='left'):
- """Return the current Y axis label as a str.
-
- :param str axis: The Y axis for which to get the label (left or right)
- """
- assert axis in ('left', 'right')
-
- return self._currentLabels['y' if axis == 'left' else 'yright']
-
- def setGraphYLabel(self, label="Y", axis='left'):
- """Set the plot Y axis label.
-
- The provided label can be temporarily replaced by the Y label of the
- active curve if any.
-
- :param str label: The Y axis label (default: 'Y')
- :param str axis: The Y axis for which to set the label (left or right)
- """
- assert axis in ('left', 'right')
-
- if axis == 'left':
- self._defaultLabels['y'] = label
- self._currentLabels['y'] = label
- else:
- self._defaultLabels['yright'] = label
- self._currentLabels['yright'] = label
-
- self._backend.setGraphYLabel(label, axis=axis)
- self._setDirtyPlot()
-
- # Axes
-
- def setYAxisInverted(self, flag=True):
- """Set the Y axis orientation.
-
- :param bool flag: True for Y axis going from top to bottom,
- False for Y axis going from bottom to top
- """
- flag = bool(flag)
- self._backend.setYAxisInverted(flag)
- self._setDirtyPlot()
- self.notify('setYAxisInverted', state=flag)
-
- def isYAxisInverted(self):
- """Return True if Y axis goes from top to bottom, False otherwise."""
- return self._backend.isYAxisInverted()
-
- def isXAxisLogarithmic(self):
- """Return True if X axis scale is logarithmic, False if linear."""
- return self._logX
-
- def setXAxisLogarithmic(self, flag):
- """Set the bottom X axis scale (either linear or logarithmic).
-
- :param bool flag: True to use a logarithmic scale, False for linear.
- """
- if bool(flag) == self._logX:
- return
- self._logX = bool(flag)
-
- self._backend.setXAxisLogarithmic(self._logX)
-
- # TODO hackish way of forcing update of curves and images
- for curve in self.getAllCurves():
- curve._updated()
- for image in self.getAllImages():
- image._updated()
- self._invalidateDataRange()
-
- self.resetZoom()
- self.notify('setXAxisLogarithmic', state=self._logX)
-
- def isYAxisLogarithmic(self):
- """Return True if Y axis scale is logarithmic, False if linear."""
- return self._logY
-
- def setYAxisLogarithmic(self, flag):
- """Set the Y axes scale (either linear or logarithmic).
-
- :param bool flag: True to use a logarithmic scale, False for linear.
- """
- if bool(flag) == self._logY:
- return
- self._logY = bool(flag)
-
- self._backend.setYAxisLogarithmic(self._logY)
-
- # TODO hackish way of forcing update of curves and images
- for curve in self.getAllCurves():
- curve._updated()
- for image in self.getAllImages():
- image._updated()
- self._invalidateDataRange()
-
- self.resetZoom()
- self.notify('setYAxisLogarithmic', state=self._logY)
-
- def isXAxisAutoScale(self):
- """Return True if X axis is automatically adjusting its limits."""
- return self._xAutoScale
-
- def setXAxisAutoScale(self, flag=True):
- """Set the X axis limits adjusting behavior of :meth:`resetZoom`.
-
- :param bool flag: True to resize limits automatically,
- False to disable it.
- """
- self._xAutoScale = bool(flag)
- self.notify('setXAxisAutoScale', state=self._xAutoScale)
-
- def isYAxisAutoScale(self):
- """Return True if Y axes are automatically adjusting its limits."""
- return self._yAutoScale
-
- def setYAxisAutoScale(self, flag=True):
- """Set the Y axis limits adjusting behavior of :meth:`resetZoom`.
-
- :param bool flag: True to resize limits automatically,
- False to disable it.
- """
- self._yAutoScale = bool(flag)
- self.notify('setYAxisAutoScale', state=self._yAutoScale)
-
- def isKeepDataAspectRatio(self):
- """Returns whether the plot is keeping data aspect ratio or not."""
- return self._backend.isKeepDataAspectRatio()
-
- def setKeepDataAspectRatio(self, flag=True):
- """Set whether the plot keeps data aspect ratio or not.
-
- :param bool flag: True to respect data aspect ratio
- """
- flag = bool(flag)
- self._backend.setKeepDataAspectRatio(flag=flag)
- self._setDirtyPlot()
- self.resetZoom()
- self.notify('setKeepDataAspectRatio', state=flag)
-
- def getGraphGrid(self):
- """Return the current grid mode, either None, 'major' or 'both'.
-
- See :meth:`setGraphGrid`.
- """
- return self._grid
-
- def setGraphGrid(self, which=True):
- """Set the type of grid to display.
-
- :param which: None or False to disable the grid,
- 'major' or True for grid on major ticks (the default),
- 'both' for grid on both major and minor ticks.
- :type which: str of bool
- """
- assert which in (None, True, False, 'both', 'major')
- if not which:
- which = None
- elif which is True:
- which = 'major'
- self._grid = which
- self._backend.setGraphGrid(which)
- self._setDirtyPlot()
- self.notify('setGraphGrid', which=str(which))
-
- # Defaults
-
- def isDefaultPlotPoints(self):
- """Return True if default Curve symbol is 'o', False for no symbol."""
- return self._defaultPlotPoints == 'o'
-
- def setDefaultPlotPoints(self, flag):
- """Set the default symbol of all curves.
-
- When called, this reset the symbol of all existing curves.
-
- :param bool flag: True to use 'o' as the default curve symbol,
- False to use no symbol.
- """
- self._defaultPlotPoints = 'o' if flag else ''
-
- # Reset symbol of all curves
- curves = self.getAllCurves(just_legend=False, withhidden=True)
-
- if curves:
- for curve in curves:
- curve.setSymbol(self._defaultPlotPoints)
-
- def isDefaultPlotLines(self):
- """Return True for line as default line style, False for no line."""
- return self._plotLines
-
- def setDefaultPlotLines(self, flag):
- """Toggle the use of lines as the default curve line style.
-
- :param bool flag: True to use a line as the default line style,
- False to use no line as the default line style.
- """
- self._plotLines = bool(flag)
-
- linestyle = '-' if self._plotLines else ' '
-
- # Reset linestyle of all curves
- curves = self.getAllCurves(withhidden=True)
-
- if curves:
- for curve in curves:
- curve.setLineStyle(linestyle)
-
- def getDefaultColormap(self):
- """Return the default colormap used by :meth:`addImage` as a dict.
-
- See :mod:`Plot` for the documentation of the colormap dict.
- """
- return self._defaultColormap.copy()
-
- def setDefaultColormap(self, colormap=None):
- """Set the default colormap used by :meth:`addImage`.
-
- Setting the default colormap do not change any currently displayed
- image.
- It only affects future calls to :meth:`addImage` without the colormap
- parameter.
-
- :param dict colormap: The description of the default colormap, or
- None to set the colormap to a linear autoscale
- gray colormap.
- See :mod:`Plot` for the documentation
- of the colormap dict.
- """
- if colormap is None:
- colormap = {'name': 'gray', 'normalization': 'linear',
- 'autoscale': True, 'vmin': 0.0, 'vmax': 1.0}
- self._defaultColormap = colormap.copy()
-
- def getSupportedColormaps(self):
- """Get the supported colormap names as a tuple of str.
-
- The list should at least contain and start by:
- ('gray', 'reversed gray', 'temperature', 'red', 'green', 'blue')
- """
- default = ('gray', 'reversed gray',
- 'temperature',
- 'red', 'green', 'blue')
- if matplotlib_cm is None:
- return default
- else:
- maps = [m for m in matplotlib_cm.datad]
- maps.sort()
- return default + tuple(maps)
-
- def _getColorAndStyle(self):
- color = self.colorList[self._colorIndex]
- style = self._styleList[self._styleIndex]
-
- # Loop over color and then styles
- self._colorIndex += 1
- if self._colorIndex >= len(self.colorList):
- self._colorIndex = 0
- self._styleIndex = (self._styleIndex + 1) % len(self._styleList)
-
- # If color is the one of active curve, take the next one
- if color == self.getActiveCurveColor():
- color, style = self._getColorAndStyle()
-
- if not self._plotLines:
- style = ' '
-
- return color, style
-
- # Misc.
-
- def getWidgetHandle(self):
- """Return the widget the plot is displayed in.
-
- This widget is owned by the backend.
- """
- return self._backend.getWidgetHandle()
-
- def notify(self, event, **kwargs):
- """Send an event to the listeners.
-
- Event are passed to the registered callback as a dict with an 'event'
- key for backward compatibility with PyMca.
-
- :param str event: The type of event
- :param kwargs: The information of the event.
- """
- eventDict = kwargs.copy()
- eventDict['event'] = event
- self._callback(eventDict)
-
- def setCallback(self, callbackFunction=None):
- """Attach a listener to the backend.
-
- Limitation: Only one listener at a time.
-
- :param callbackFunction: function accepting a dictionary as input
- to handle the graph events
- If None (default), use a default listener.
- """
- # TODO allow multiple listeners, keep a weakref on it
- # allow register listener by event type
- if callbackFunction is None:
- callbackFunction = self.graphCallback
- self._callback = callbackFunction
-
- def graphCallback(self, ddict=None):
- """This callback is going to receive all the events from the plot.
-
- Those events will consist on a dictionary and among the dictionary
- keys the key 'event' is mandatory to describe the type of event.
- This default implementation only handles setting the active curve.
- """
-
- if ddict is None:
- ddict = {}
- _logger.debug("Received dict keys = %s", str(ddict.keys()))
- _logger.debug(str(ddict))
- if ddict['event'] in ["legendClicked", "curveClicked"]:
- if ddict['button'] == "left":
- self.setActiveCurve(ddict['label'])
-
- def saveGraph(self, filename, fileFormat=None, dpi=None, **kw):
- """Save a snapshot of the plot.
-
- Supported file formats: "png", "svg", "pdf", "ps", "eps",
- "tif", "tiff", "jpeg", "jpg".
-
- :param filename: Destination
- :type filename: str, StringIO or BytesIO
- :param str fileFormat: String specifying the format
- :return: False if cannot save the plot, True otherwise
- """
- if kw:
- _logger.warning('Extra parameters ignored: %s', str(kw))
-
- if fileFormat is None:
- if not hasattr(filename, 'lower'):
- _logger.warning(
- 'saveGraph cancelled, cannot define file format.')
- return False
- else:
- fileFormat = (filename.split(".")[-1]).lower()
-
- supportedFormats = ("png", "svg", "pdf", "ps", "eps",
- "tif", "tiff", "jpeg", "jpg")
-
- if fileFormat not in supportedFormats:
- _logger.warning('Unsupported format %s', fileFormat)
- return False
- else:
- self._backend.saveGraph(filename,
- fileFormat=fileFormat,
- dpi=dpi)
- return True
-
- def getDataMargins(self):
- """Get the default data margin ratios, see :meth:`setDataMargins`.
-
- :return: The margin ratios for each side (xMin, xMax, yMin, yMax).
- :rtype: A 4-tuple of floats.
- """
- return self._defaultDataMargins
-
- def setDataMargins(self, xMinMargin=0., xMaxMargin=0.,
- yMinMargin=0., yMaxMargin=0.):
- """Set the default data margins to use in :meth:`resetZoom`.
-
- Set the default ratios of margins (as floats) to add around the data
- inside the plot area for each side.
- """
- self._defaultDataMargins = (xMinMargin, xMaxMargin,
- yMinMargin, yMaxMargin)
-
- def getAutoReplot(self):
- """Return True if replot is automatically handled, False otherwise.
-
- See :meth`setAutoReplot`.
- """
- return self._autoreplot
-
- def setAutoReplot(self, autoreplot=True):
- """Set automatic replot mode.
-
- When enabled, the plot is redrawn automatically when changed.
- When disabled, the plot is not redrawn when its content change.
- Instead, it :meth:`replot` must be called.
-
- :param bool autoreplot: True to enable it (default),
- False to disable it.
- """
- self._autoreplot = bool(autoreplot)
-
- # If the plot is dirty before enabling autoreplot,
- # then _backend.postRedisplay will never be called from _setDirtyPlot
- if self._autoreplot and self._getDirtyPlot():
- self._backend.postRedisplay()
-
- def replot(self):
- """Redraw the plot immediately."""
- for item in self._contentToUpdate:
- item._update(self._backend)
- self._contentToUpdate.clear()
- self._backend.replot()
- self._dirty = False # reset dirty flag
-
- def resetZoom(self, dataMargins=None):
- """Reset the plot limits to the bounds of the data and redraw the plot.
-
- It automatically scale limits of axes that are in autoscale mode
- (See :meth:`setXAxisAutoScale`, :meth:`setYAxisAutoScale`).
- It keeps current limits on axes that are not in autoscale mode.
-
- Extra margins can be added around the data inside the plot area.
- Margins are given as one ratio of the data range per limit of the
- data (xMin, xMax, yMin and yMax limits).
- For log scale, extra margins are applied in log10 of the data.
-
- :param dataMargins: Ratios of margins to add around the data inside
- the plot area for each side (Default: no margins).
- :type dataMargins: A 4-tuple of float as (xMin, xMax, yMin, yMax).
- """
- if dataMargins is None:
- dataMargins = self._defaultDataMargins
-
- xLimits = self.getGraphXLimits()
- yLimits = self.getGraphYLimits(axis='left')
- y2Limits = self.getGraphYLimits(axis='right')
-
- xAuto = self.isXAxisAutoScale()
- yAuto = self.isYAxisAutoScale()
-
- if not xAuto and not yAuto:
- _logger.debug("Nothing to autoscale")
- else: # Some axes to autoscale
-
- # Get data range
- ranges = self.getDataRange()
- xmin, xmax = (1., 100.) if ranges.x is None else ranges.x
- ymin, ymax = (1., 100.) if ranges.y is None else ranges.y
- if ranges.yright is None:
- ymin2, ymax2 = None, None
- else:
- ymin2, ymax2 = ranges.yright
-
- # Add margins around data inside the plot area
- newLimits = list(_utils.addMarginsToLimits(
- dataMargins,
- self.isXAxisLogarithmic(),
- self.isYAxisLogarithmic(),
- xmin, xmax, ymin, ymax, ymin2, ymax2))
-
- if self.isKeepDataAspectRatio():
- # Use limits with margins to keep ratio
- xmin, xmax, ymin, ymax = newLimits[:4]
-
- # Compute bbox wth figure aspect ratio
- plotWidth, plotHeight = self.getPlotBoundsInPixels()[2:]
- plotRatio = plotHeight / plotWidth
-
- if plotRatio > 0.:
- dataRatio = (ymax - ymin) / (xmax - xmin)
- if dataRatio < plotRatio:
- # Increase y range
- ycenter = 0.5 * (ymax + ymin)
- yrange = (xmax - xmin) * plotRatio
- newLimits[2] = ycenter - 0.5 * yrange
- newLimits[3] = ycenter + 0.5 * yrange
-
- elif dataRatio > plotRatio:
- # Increase x range
- xcenter = 0.5 * (xmax + xmin)
- xrange_ = (ymax - ymin) / plotRatio
- newLimits[0] = xcenter - 0.5 * xrange_
- newLimits[1] = xcenter + 0.5 * xrange_
-
- self.setLimits(*newLimits)
-
- if not xAuto and yAuto:
- self.setGraphXLimits(*xLimits)
- elif xAuto and not yAuto:
- if y2Limits is not None:
- self.setGraphYLimits(
- y2Limits[0], y2Limits[1], axis='right')
- if yLimits is not None:
- self.setGraphYLimits(yLimits[0], yLimits[1], axis='left')
-
- self._setDirtyPlot()
-
- if (xLimits != self.getGraphXLimits() or
- yLimits != self.getGraphYLimits(axis='left') or
- y2Limits != self.getGraphYLimits(axis='right')):
- self._notifyLimitsChanged()
-
- # Coord conversion
-
- def dataToPixel(self, x=None, y=None, axis="left", check=True):
- """Convert a position in data coordinates to a position in pixels.
-
- :param float x: The X coordinate in data space. If None (default)
- the middle position of the displayed data is used.
- :param float y: The Y coordinate in data space. If None (default)
- the middle position of the displayed data is used.
- :param str axis: The Y axis to use for the conversion
- ('left' or 'right').
- :param bool check: True to return None if outside displayed area,
- False to convert to pixels anyway
- :returns: The corresponding position in pixels or
- None if the data position is not in the displayed area and
- check is True.
- :rtype: A tuple of 2 floats: (xPixel, yPixel) or None.
- """
- assert axis in ("left", "right")
-
- xmin, xmax = self.getGraphXLimits()
- ymin, ymax = self.getGraphYLimits(axis=axis)
-
- if x is None:
- x = 0.5 * (xmax + xmin)
- if y is None:
- y = 0.5 * (ymax + ymin)
-
- if check:
- if x > xmax or x < xmin:
- return None
-
- if y > ymax or y < ymin:
- return None
-
- return self._backend.dataToPixel(x, y, axis=axis)
-
- def pixelToData(self, x, y, axis="left", check=False):
- """Convert a position in pixels to a position in data coordinates.
-
- :param float x: The X coordinate in pixels. If None (default)
- the center of the widget is used.
- :param float y: The Y coordinate in pixels. If None (default)
- the center of the widget is used.
- :param str axis: The Y axis to use for the conversion
- ('left' or 'right').
- :param bool check: Toggle checking if pixel is in plot area.
- If False, this method never returns None.
- :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.
- """
- assert axis in ("left", "right")
- return self._backend.pixelToData(x, y, axis=axis, check=check)
-
- def getPlotBoundsInPixels(self):
- """Plot area bounds in widget coordinates in pixels.
-
- :return: bounds as a 4-tuple of int: (left, top, width, height)
- """
- return self._backend.getPlotBoundsInPixels()
-
- # Interaction support
-
- def setGraphCursorShape(self, cursor=None):
- """Set the cursor shape.
-
- :param str cursor: Name of the cursor shape
- """
- self._backend.setGraphCursorShape(cursor)
-
- def _pickMarker(self, x, y, test=None):
- """Pick a marker at the given position.
-
- To use for interaction implementation.
-
- :param float x: X position in pixels.
- :param float y: Y position in pixels.
- :param test: A callable to call for each picked marker to filter
- picked markers. If None (default), do not filter markers.
- """
- if test is None:
- def test(mark):
- return True
-
- markers = self._backend.pickItems(x, y)
- legends = [m['legend'] for m in markers if m['kind'] == 'marker']
-
- for legend in reversed(legends):
- marker = self._getMarker(legend)
- if marker is not None and test(marker):
- return marker
- return None
-
- def _getAllMarkers(self, just_legend=False):
- """Returns all markers' legend or objects
-
- :param bool just_legend: True to get the legend of the markers,
- False (the default) to get marker objects.
- :return: list of legend of list of marker objects
- :rtype: list of str or list of marker objects
- """
- return self._getItems(
- kind='marker', just_legend=just_legend, withhidden=True)
-
- def _getMarker(self, legend=None):
- """Get the object describing a specific marker.
-
- It returns None in case no matching marker is found
-
- :param str legend: The legend of the marker to retrieve
- :rtype: None of marker object
- """
- return self._getItem(kind='marker', legend=legend)
-
- def _pickImageOrCurve(self, x, y, test=None):
- """Pick an image or a curve at the given position.
-
- To use for interaction implementation.
-
- :param float x: X position in pixelsparam float y: Y position in pixels
- :param test: A callable to call for each picked item to filter
- picked items. If None (default), do not filter items.
- """
- if test is None:
- def test(i):
- return True
-
- allItems = self._backend.pickItems(x, y)
- allItems = [item for item in allItems
- if item['kind'] in ['curve', 'image']]
-
- for item in reversed(allItems):
- kind, legend = item['kind'], item['legend']
- if kind == 'curve':
- curve = self.getCurve(legend)
- if curve is not None and test(curve):
- return kind, curve, item['xdata'], item['ydata']
-
- elif kind == 'image':
- image = self.getImage(legend)
- if image is not None and test(image):
- return kind, image, None
-
- else:
- _logger.warning('Unsupported kind: %s', kind)
-
- return None
-
- # User event handling #
-
- def _isPositionInPlotArea(self, x, y):
- """Project position in pixel to the closest point in the plot area
-
- :param float x: X coordinate in widget coordinate (in pixel)
- :param float y: Y coordinate in widget coordinate (in pixel)
- :return: (x, y) in widget coord (in pixel) in the plot area
- """
- left, top, width, height = self.getPlotBoundsInPixels()
- xPlot = numpy.clip(x, left, left + width)
- yPlot = numpy.clip(y, top, top + height)
- return xPlot, yPlot
-
- def onMousePress(self, xPixel, yPixel, btn):
- """Handle mouse press event.
-
- :param float xPixel: X mouse position in pixels
- :param float yPixel: Y mouse position in pixels
- :param str btn: Mouse button in 'left', 'middle', 'right'
- """
- if self._isPositionInPlotArea(xPixel, yPixel) == (xPixel, yPixel):
- self._pressedButtons.append(btn)
- self._eventHandler.handleEvent('press', xPixel, yPixel, btn)
-
- def onMouseMove(self, xPixel, yPixel):
- """Handle mouse move event.
-
- :param float xPixel: X mouse position in pixels
- :param float yPixel: Y mouse position in pixels
- """
- inXPixel, inYPixel = self._isPositionInPlotArea(xPixel, yPixel)
- isCursorInPlot = inXPixel == xPixel and inYPixel == yPixel
-
- if self._cursorInPlot != isCursorInPlot:
- self._cursorInPlot = isCursorInPlot
- self._eventHandler.handleEvent(
- 'enter' if self._cursorInPlot else 'leave')
-
- if isCursorInPlot:
- # Signal mouse move event
- dataPos = self.pixelToData(inXPixel, inYPixel)
- assert dataPos is not None
-
- btn = self._pressedButtons[-1] if self._pressedButtons else None
- event = PlotEvents.prepareMouseSignal(
- 'mouseMoved', btn, dataPos[0], dataPos[1], xPixel, yPixel)
- self.notify(**event)
-
- # Either button was pressed in the plot or cursor is in the plot
- if isCursorInPlot or self._pressedButtons:
- self._eventHandler.handleEvent('move', inXPixel, inYPixel)
-
- def onMouseRelease(self, xPixel, yPixel, btn):
- """Handle mouse release event.
-
- :param float xPixel: X mouse position in pixels
- :param float yPixel: Y mouse position in pixels
- :param str btn: Mouse button in 'left', 'middle', 'right'
- """
- try:
- self._pressedButtons.remove(btn)
- except ValueError:
- pass
- else:
- xPixel, yPixel = self._isPositionInPlotArea(xPixel, yPixel)
- self._eventHandler.handleEvent('release', xPixel, yPixel, btn)
-
- def onMouseWheel(self, xPixel, yPixel, angleInDegrees):
- """Handle mouse wheel event.
-
- :param float xPixel: X mouse position in pixels
- :param float yPixel: Y mouse position in pixels
- :param float angleInDegrees: Angle corresponding to wheel motion.
- Positive for movement away from the user,
- negative for movement toward the user.
- """
- if self._isPositionInPlotArea(xPixel, yPixel) == (xPixel, yPixel):
- self._eventHandler.handleEvent(
- 'wheel', xPixel, yPixel, angleInDegrees)
-
- def onMouseLeaveWidget(self):
- """Handle mouse leave widget event."""
- if self._cursorInPlot:
- self._cursorInPlot = False
- self._eventHandler.handleEvent('leave')
-
- # Interaction modes #
-
- def getInteractiveMode(self):
- """Returns the current interactive mode as a dict.
-
- The returned dict contains at least the key 'mode'.
- Mode can be: 'draw', 'pan', 'select', 'zoom'.
- It can also contains extra keys (e.g., 'color') specific to a mode
- as provided to :meth:`setInteractiveMode`.
- """
- return self._eventHandler.getInteractiveMode()
-
- def setInteractiveMode(self, mode, color='black',
- shape='polygon', label=None,
- zoomOnWheel=True, source=None, width=None):
- """Switch the interactive mode.
-
- :param str mode: The name of the interactive mode.
- In 'draw', 'pan', 'select', 'zoom'.
- :param color: Only for 'draw' and 'zoom' modes.
- Color to use for drawing selection area. Default black.
- :type color: Color description: The name as a str or
- a tuple of 4 floats.
- :param str shape: Only for 'draw' mode. The kind of shape to draw.
- In 'polygon', 'rectangle', 'line', 'vline', 'hline',
- 'freeline'.
- Default is 'polygon'.
- :param str label: Only for 'draw' mode, sent in drawing events.
- :param bool zoomOnWheel: Toggle zoom on wheel support
- :param source: A user-defined object (typically the caller object)
- that will be send in the interactiveModeChanged event,
- to identify which object required a mode change.
- Default: None
- :param float width: Width of the pencil. Only for draw pencil mode.
- """
- self._eventHandler.setInteractiveMode(mode, color, shape, label, width)
- self._eventHandler.zoomOnWheel = zoomOnWheel
-
- self.notify(
- 'interactiveModeChanged', source=source)
-
- # Deprecated #
-
- def isDrawModeEnabled(self):
- """Deprecated, use :meth:`getInteractiveMode` instead.
-
- Return True if the current interactive state is drawing."""
- _logger.warning(
- 'isDrawModeEnabled deprecated, use getInteractiveMode instead')
- return self.getInteractiveMode()['mode'] == 'draw'
-
- def setDrawModeEnabled(self, flag=True, shape='polygon', label=None,
- color=None, **kwargs):
- """Deprecated, use :meth:`setInteractiveMode` instead.
-
- Set the drawing mode if flag is True and its parameters.
-
- If flag is False, only item selection is enabled.
-
- Warning: Zoom and drawing are not compatible and cannot be enabled
- simultaneously.
-
- :param bool flag: True to enable drawing and disable zoom and select.
- :param str shape: Type of item to be drawn in:
- hline, vline, rectangle, polygon (default)
- :param str label: Associated text for identifying draw signals
- :param color: The color to use to draw the selection area
- :type color: string ("#RRGGBB") or 4 column unsigned byte array or
- one of the predefined color names defined in Colors.py
- """
- _logger.warning(
- 'setDrawModeEnabled deprecated, use setInteractiveMode instead')
-
- if kwargs:
- _logger.warning('setDrawModeEnabled ignores additional parameters')
-
- if color is None:
- color = 'black'
-
- if flag:
- self.setInteractiveMode('draw', shape=shape,
- label=label, color=color)
- elif self.getInteractiveMode()['mode'] == 'draw':
- self.setInteractiveMode('select')
-
- def getDrawMode(self):
- """Deprecated, use :meth:`getInteractiveMode` instead.
-
- Return the draw mode parameters as a dict of None.
-
- It returns None if the interactive mode is not a drawing mode,
- otherwise, it returns a dict containing the drawing mode parameters
- as provided to :meth:`setDrawModeEnabled`.
- """
- _logger.warning(
- 'getDrawMode deprecated, use getInteractiveMode instead')
- mode = self.getInteractiveMode()
- return mode if mode['mode'] == 'draw' else None
-
- def isZoomModeEnabled(self):
- """Deprecated, use :meth:`getInteractiveMode` instead.
-
- Return True if the current interactive state is zooming."""
- _logger.warning(
- 'isZoomModeEnabled deprecated, use getInteractiveMode instead')
- return self.getInteractiveMode()['mode'] == 'zoom'
-
- def setZoomModeEnabled(self, flag=True, color=None):
- """Deprecated, use :meth:`setInteractiveMode` instead.
-
- Set the zoom mode if flag is True, else item selection is enabled.
-
- Warning: Zoom and drawing are not compatible and cannot be enabled
- simultaneously
-
- :param bool flag: If True, enable zoom and select mode.
- :param color: The color to use to draw the selection area.
- (Default: 'black')
- :param color: The color to use to draw the selection area
- :type color: string ("#RRGGBB") or 4 column unsigned byte array or
- one of the predefined color names defined in Colors.py
- """
- _logger.warning(
- 'setZoomModeEnabled deprecated, use setInteractiveMode instead')
- if color is None:
- color = 'black'
-
- if flag:
- self.setInteractiveMode('zoom', color=color)
- elif self.getInteractiveMode()['mode'] == 'zoom':
- self.setInteractiveMode('select')
-
- def insertMarker(self, *args, **kwargs):
- """Deprecated, use :meth:`addMarker` instead."""
- _logger.warning(
- 'insertMarker deprecated, use addMarker instead.')
- return self.addMarker(*args, **kwargs)
-
- def insertXMarker(self, *args, **kwargs):
- """Deprecated, use :meth:`addXMarker` instead."""
- _logger.warning(
- 'insertXMarker deprecated, use addXMarker instead.')
- return self.addXMarker(*args, **kwargs)
-
- def insertYMarker(self, *args, **kwargs):
- """Deprecated, use :meth:`addYMarker` instead."""
- _logger.warning(
- 'insertYMarker deprecated, use addYMarker instead.')
- return self.addYMarker(*args, **kwargs)
-
- def isActiveCurveHandlingEnabled(self):
- """Deprecated, use :meth:`isActiveCurveHandling` instead."""
- _logger.warning(
- 'isActiveCurveHandlingEnabled deprecated, '
- 'use isActiveCurveHandling instead.')
- return self.isActiveCurveHandling()
-
- def enableActiveCurveHandling(self, *args, **kwargs):
- """Deprecated, use :meth:`setActiveCurveHandling` instead."""
- _logger.warning(
- 'enableActiveCurveHandling deprecated, '
- 'use setActiveCurveHandling instead.')
- return self.setActiveCurveHandling(*args, **kwargs)
-
- def invertYAxis(self, *args, **kwargs):
- """Deprecated, use :meth:`setYAxisInverted` instead."""
- _logger.warning('invertYAxis deprecated, '
- 'use setYAxisInverted instead.')
- return self.setYAxisInverted(*args, **kwargs)
-
- def showGrid(self, flag=True):
- """Deprecated, use :meth:`setGraphGrid` instead."""
- _logger.warning("showGrid deprecated, use setGraphGrid instead")
- if flag in (0, False):
- flag = None
- elif flag in (1, True):
- flag = 'major'
- else:
- flag = 'both'
- return self.setGraphGrid(flag)
-
- def keepDataAspectRatio(self, *args, **kwargs):
- """Deprecated, use :meth:`setKeepDataAspectRatio`."""
- _logger.warning('keepDataAspectRatio deprecated,'
- 'use setKeepDataAspectRatio instead')
- return self.setKeepDataAspectRatio(*args, **kwargs)