summaryrefslogtreecommitdiff
path: root/silx/gui/plot/backends/BackendBase.py
blob: bcc93a568c16a6aabbd3b179a0dd72b78b04f29c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
# coding: utf-8
# /*##########################################################################
#
# Copyright (c) 2004-2020 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ############################################################################*/
"""Base class for Plot backends.

It documents the Plot backend API.

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

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

import weakref
from ... import qt


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


class BackendBase(object):
    """Class defining the API a backend of the Plot should provide."""

    def __init__(self, plot, parent=None):
        """Init.

        :param Plot plot: The Plot this backend is attached to
        :param parent: The parent widget of the plot widget.
        """
        self.__xLimits = 1., 100.
        self.__yLimits = {'left': (1., 100.), 'right': (1., 100.)}
        self.__yAxisInverted = False
        self.__keepDataAspectRatio = False
        self._xAxisTimeZone = None
        self._axesDisplayed = True
        # Store a weakref to get access to the plot state.
        self._setPlot(plot)

    @property
    def _plot(self):
        """The plot this backend is attached to."""
        if self._plotRef is None:
            raise RuntimeError('This backend is not attached to a Plot')

        plot = self._plotRef()
        if plot is None:
            raise RuntimeError('This backend is no more attached to a Plot')
        return plot

    def _setPlot(self, plot):
        """Allow to set plot after init.

        Use with caution, basically **immediately** after init.
        """
        self._plotRef = weakref.ref(plot)

    # Add methods

    def addCurve(self, x, y,
                 color, symbol, linewidth, linestyle,
                 yaxis,
                 xerror, yerror,
                 fill, alpha, symbolsize, baseline):
        """Add a 1D curve given by x an y to the graph.

        :param numpy.ndarray x: The data corresponding to the x axis
        :param numpy.ndarray y: The data corresponding to the y axis
        :param color: color(s) to be used
        :type color: string ("#RRGGBB") or (npoints, 4) unsigned byte array or
                     one of the predefined color names defined in colors.py
        :param str symbol: Symbol to be drawn at each (x, y) position::

            - ' ' or '' no symbol
            - 'o' circle
            - '.' point
            - ',' pixel
            - '+' cross
            - 'x' x-cross
            - 'd' diamond
            - 's' square

        :param float linewidth: The width of the curve in pixels
        :param str linestyle: Type of line::

            - ' ' or ''  no line
            - '-'  solid line
            - '--' dashed line
            - '-.' dash-dot line
            - ':'  dotted line

        :param str yaxis: The Y axis this curve belongs to in: 'left', 'right'
        :param xerror: Values with the uncertainties on the x values
        :type xerror: numpy.ndarray or None
        :param yerror: Values with the uncertainties on the y values
        :type yerror: numpy.ndarray or None
        :param bool fill: True to fill the curve, False otherwise
        :param float alpha: Curve opacity, as a float in [0., 1.]
        :param float symbolsize: Size of the symbol (if any) drawn
                                 at each (x, y) position.
        :returns: The handle used by the backend to univocally access the curve
        """
        return object()

    def addImage(self, data,
                 origin, scale,
                 colormap, alpha):
        """Add an image to the plot.

        :param numpy.ndarray data: (nrows, ncolumns) data or
                     (nrows, ncolumns, RGBA) ubyte array
        :param origin: (origin X, origin Y) of the data.
                       Default: (0., 0.)
        :type origin: 2-tuple of float
        :param scale: (scale X, scale Y) of the data.
                       Default: (1., 1.)
        :type scale: 2-tuple of float
        :param ~silx.gui.colors.Colormap colormap: Colormap object to use.
            Ignored if data is RGB(A).
        :param float alpha: Opacity of the image, as a float in range [0, 1].
        :returns: The handle used by the backend to univocally access the image
        """
        return object()

    def addTriangles(self, x, y, triangles,
                     color, alpha):
        """Add a set of triangles.

        :param numpy.ndarray x: The data corresponding to the x axis
        :param numpy.ndarray y: The data corresponding to the y axis
        :param numpy.ndarray triangles: The indices to make triangles
            as a (Ntriangle, 3) array
        :param numpy.ndarray color: color(s) as (npoints, 4) array
        :param float alpha: Opacity as a float in [0., 1.]
        :returns: The triangles' unique identifier used by the backend
        """
        return object()

    def addShape(self, x, y, shape, color, fill, overlay,
                 linestyle, linewidth, linebgcolor):
        """Add an item (i.e. a shape) to the plot.

        :param numpy.ndarray x: The X coords of the points of the shape
        :param numpy.ndarray y: The Y coords of the points of the shape
        :param str shape: Type of item to be drawn in
                          hline, polygon, rectangle, vline, polylines
        :param str color: Color of the item
        :param bool fill: True to fill the shape
        :param bool overlay: True if item is an overlay, False otherwise
        :param str linestyle: Style of the line.
            Only relevant for line markers where X or Y is None.
            Value in:

            - ' '  no line
            - '-'  solid line
            - '--' dashed line
            - '-.' dash-dot line
            - ':'  dotted line
        :param float linewidth: Width of the line.
            Only relevant for line markers where X or Y is None.
        :param str linebgcolor: Background color of the line, e.g., 'blue', 'b',
            '#FF0000'. It is used to draw dotted line using a second color.
        :returns: The handle used by the backend to univocally access the item
        """
        return object()

    def addMarker(self, x, y, text, color,
                  symbol, linestyle, linewidth, constraint, yaxis):
        """Add a point, vertical line or horizontal line marker to the plot.

        :param float x: Horizontal position of the marker in graph coordinates.
                        If None, the marker is a horizontal line.
        :param float y: Vertical position of the marker in graph coordinates.
                        If None, the marker is a vertical line.
        :param str text: Text associated to the marker (or None for no text)
        :param str color: Color to be used for instance 'blue', 'b', '#FF0000'
        :param str symbol: Symbol representing the marker.
            Only relevant for point markers where X and Y are not None.
            Value in:

            - 'o' circle
            - '.' point
            - ',' pixel
            - '+' cross
            - 'x' x-cross
            - 'd' diamond
            - 's' square
        :param str linestyle: Style of the line.
            Only relevant for line markers where X or Y is None.
            Value in:

            - ' '  no line
            - '-'  solid line
            - '--' dashed line
            - '-.' dash-dot line
            - ':'  dotted line
        :param float linewidth: Width of the line.
            Only relevant for line markers where X or Y is None.
        :param constraint: A function filtering marker displacement by
                           dragging operations or None for no filter.
                           This function is called each time a marker is
                           moved.
        :type constraint: None or a callable that takes the coordinates of
                          the current cursor position in the plot as input
                          and that returns the filtered coordinates.
        :param str yaxis: The Y axis this marker belongs to in: 'left', 'right'
        :return: Handle used by the backend to univocally access the marker
        """
        return object()

    # Remove methods

    def remove(self, item):
        """Remove an existing item from the plot.

        :param item: A backend specific item handle returned by a add* method
        """
        pass

    # Interaction methods

    def setGraphCursorShape(self, cursor):
        """Set the cursor shape.

        To override in interactive backends.

        :param str cursor: Name of the cursor shape or None
        """
        pass

    def setGraphCursor(self, flag, color, linewidth, linestyle):
        """Toggle the display of a crosshair cursor and set its attributes.

        To override in interactive backends.

        :param bool flag: Toggle the display of a crosshair cursor.
        :param color: The color to use for the crosshair.
        :type color: A string (either a predefined color name in colors.py
                    or "#RRGGBB")) or a 4 columns unsigned byte array.
        :param int linewidth: The width of the lines of the crosshair.
        :param linestyle: Type of line::

                - ' ' no line
                - '-' solid line
                - '--' dashed line
                - '-.' dash-dot line
                - ':' dotted line

        :type linestyle: None or one of the predefined styles.
        """
        pass

    def getItemsFromBackToFront(self, condition=None):
        """Returns the list of plot items order as rendered by the backend.

        This is the order used for rendering.
        By default, it takes into account overlays, z value and order of addition of items,
        but backends can override it.

        :param callable condition:
           Callable taking an item as input and returning False for items to skip.
           If None (default), no item is skipped.
        :rtype: List[~silx.gui.plot.items.Item]
        """
        # Sort items: Overlays first, then others
        # and in each category ordered by z and then by order of addition
        # as content keeps this order.
        content = self._plot.getItems()
        if condition is not None:
            content = [item for item in content if condition(item)]

        return sorted(
            content,
            key=lambda i: ((1 if i.isOverlay() else 0), i.getZValue()))

    def pickItem(self, x, y, item):
        """Return picked indices if any, or None.

        :param float x: The x pixel coord where to pick.
        :param float y: The y pixel coord where to pick.
        :param item: A backend item created with add* methods.
        :return: None if item was not picked, else returns
            picked indices information.
        :rtype: Union[None,List]
        """
        return None

    # Update curve

    def setCurveColor(self, curve, color):
        """Set the color of a curve.

        :param curve: The curve handle
        :param str color: The color to use.
        """
        pass

    # Misc.

    def getWidgetHandle(self):
        """Return the widget this backend is drawing to."""
        return None

    def postRedisplay(self):
        """Trigger a :meth:`Plot.replot`.

        Default implementation triggers a synchronous replot if plot is dirty.
        This method should be overridden by the embedding widget in order to
        provide an asynchronous call to replot in order to optimize the number
        replot operations.
        """
        # This method can be deferred and it might happen that plot has been
        # destroyed in between, especially with unittests

        plot = self._plotRef()
        if plot is not None and plot._getDirtyPlot():
            plot.replot()

    def replot(self):
        """Redraw the plot."""
        pass

    def saveGraph(self, fileName, fileFormat, dpi):
        """Save the graph to a file (or a StringIO)

        At least "png", "svg" are supported.

        :param fileName: Destination
        :type fileName: String or StringIO or BytesIO
        :param str fileFormat: String specifying the format
        :param int dpi: The resolution to use or None.
        """
        pass

    # Graph labels

    def setGraphTitle(self, title):
        """Set the main title of the plot.

        :param str title: Title associated to the plot
        """
        pass

    def setGraphXLabel(self, label):
        """Set the X axis label.

        :param str label: label associated to the plot bottom X axis
        """
        pass

    def setGraphYLabel(self, label, axis):
        """Set the left Y axis label.

        :param str label: label associated to the plot left Y axis
        :param str axis: The axis for which to get the limits: left or right
        """
        pass

    # Graph limits

    def setLimits(self, xmin, xmax, ymin, ymax, y2min=None, y2max=None):
        """Set the limits of the X and Y axes at once.

        :param float xmin: minimum bottom axis value
        :param float xmax: maximum bottom axis value
        :param float ymin: minimum left axis value
        :param float ymax: maximum left axis value
        :param float y2min: minimum right axis value
        :param float y2max: maximum right axis value
        """
        self.__xLimits = xmin, xmax
        self.__yLimits['left'] = ymin, ymax
        if y2min is not None and y2max is not None:
            self.__yLimits['right'] = y2min, y2max

    def getGraphXLimits(self):
        """Get the graph X (bottom) limits.

        :return:  Minimum and maximum values of the X axis
        """
        return self.__xLimits

    def setGraphXLimits(self, xmin, xmax):
        """Set the limits of X axis.

        :param float xmin: minimum bottom axis value
        :param float xmax: maximum bottom axis value
        """
        self.__xLimits = xmin, xmax

    def getGraphYLimits(self, axis):
        """Get the graph Y (left) limits.

        :param str axis: The axis for which to get the limits: left or right
        :return: Minimum and maximum values of the Y axis
        """
        return self.__yLimits[axis]

    def setGraphYLimits(self, ymin, ymax, axis):
        """Set the limits of the Y axis.

        :param float ymin: minimum left axis value
        :param float ymax: maximum left axis value
        :param str axis: The axis for which to get the limits: left or right
        """
        self.__yLimits[axis] = ymin, ymax

    # Graph axes


    def getXAxisTimeZone(self):
        """Returns tzinfo that is used if the X-Axis plots date-times.

        None means the datetimes are interpreted as local time.

        :rtype: datetime.tzinfo of None.
        """
        return self._xAxisTimeZone

    def setXAxisTimeZone(self, tz):
        """Sets tzinfo that is used if the X-Axis plots date-times.

        Use None to let the datetimes be interpreted as local time.

        :rtype: datetime.tzinfo of None.
        """
        self._xAxisTimeZone = tz

    def isXAxisTimeSeries(self):
        """Return True if the X-axis scale shows datetime objects.

        :rtype: bool
        """
        raise NotImplementedError()

    def setXAxisTimeSeries(self, isTimeSeries):
        """Set whether the X-axis is a time series

        :param bool flag: True to switch to time series, False for regular axis.
        """
        raise NotImplementedError()

    def setXAxisLogarithmic(self, flag):
        """Set the X axis scale between linear and log.

        :param bool flag: If True, the bottom axis will use a log scale
        """
        pass

    def setYAxisLogarithmic(self, flag):
        """Set the Y axis scale between linear and log.

        :param bool flag: If True, the left axis will use a log scale
        """
        pass

    def setYAxisInverted(self, flag):
        """Invert the Y axis.

        :param bool flag: If True, put the vertical axis origin on the top
        """
        self.__yAxisInverted = bool(flag)

    def isYAxisInverted(self):
        """Return True if left Y axis is inverted, False otherwise."""
        return self.__yAxisInverted

    def isKeepDataAspectRatio(self):
        """Returns whether the plot is keeping data aspect ratio or not."""
        return self.__keepDataAspectRatio

    def setKeepDataAspectRatio(self, flag):
        """Set whether to keep data aspect ratio or not.

        :param flag:  True to respect data aspect ratio
        :type flag: Boolean, default True
        """
        self.__keepDataAspectRatio = bool(flag)

    def setGraphGrid(self, which):
        """Set grid.

        :param which: None to disable grid, 'major' for major grid,
                     'both' for major and minor grid
        """
        pass

    # Data <-> Pixel coordinates conversion

    def dataToPixel(self, x, y, axis):
        """Convert a position in data space to a position in pixels
        in the widget.

        :param float x: The X coordinate in data space.
        :param float y: The Y coordinate in data space.
        :param str axis: The Y axis to use for the conversion
                         ('left' or 'right').
        :returns: The corresponding position in pixels or
                  None if the data position is not in the displayed area.
        :rtype: A tuple of 2 floats: (xPixel, yPixel) or None.
        """
        raise NotImplementedError()

    def pixelToData(self, x, y, axis):
        """Convert a position in pixels in the widget to a position in
        the data space.

        :param float x: The X coordinate in pixels.
        :param float y: The Y coordinate in pixels.
        :param str axis: The Y axis to use for the conversion
                         ('left' or 'right').
        :returns: The corresponding position in data space or
                  None if the pixel position is not in the plot area.
        :rtype: A tuple of 2 floats: (xData, yData) or None.
        """
        raise NotImplementedError()

    def getPlotBoundsInPixels(self):
        """Plot area bounds in widget coordinates in pixels.

        :return: bounds as a 4-tuple of int: (left, top, width, height)
        """
        raise NotImplementedError()

    def setAxesDisplayed(self, displayed):
        """Display or not the axes.

        :param bool displayed: If `True` axes are displayed. If `False` axes
            are not anymore visible and the margin used for them is removed.
        """
        self._axesDisplayed = displayed

    def isAxesDisplayed(self):
        """private because in some case it is possible that one of the two axes
        are displayed and not the other.
        This only check status set to axes from the public API
        """
        return self._axesDisplayed

    def setForegroundColors(self, foregroundColor, gridColor):
        """Set foreground and grid colors used to display this widget.
        
        :param List[float] foregroundColor: RGBA foreground color of the widget
        :param List[float] gridColor: RGBA grid color of the data view
        """
        pass

    def setBackgroundColors(self, backgroundColor, dataBackgroundColor):
        """Set background colors used to display this widget.

        :param List[float] backgroundColor: RGBA background color of the widget
        :param Union[Tuple[float],None] dataBackgroundColor:
            RGBA background color of the data view
        """
        pass