diff options
Diffstat (limited to 'silx/gui/plot/items')
-rw-r--r-- | silx/gui/plot/items/__init__.py | 2 | ||||
-rw-r--r-- | silx/gui/plot/items/axis.py | 6 | ||||
-rw-r--r-- | silx/gui/plot/items/complex.py | 8 | ||||
-rw-r--r-- | silx/gui/plot/items/core.py | 37 | ||||
-rw-r--r-- | silx/gui/plot/items/curve.py | 5 | ||||
-rw-r--r-- | silx/gui/plot/items/histogram.py | 6 | ||||
-rw-r--r-- | silx/gui/plot/items/roi.py | 72 | ||||
-rw-r--r-- | silx/gui/plot/items/scatter.py | 5 | ||||
-rw-r--r-- | silx/gui/plot/items/shape.py | 45 |
9 files changed, 143 insertions, 43 deletions
diff --git a/silx/gui/plot/items/__init__.py b/silx/gui/plot/items/__init__.py index e7957ac..f829f78 100644 --- a/silx/gui/plot/items/__init__.py +++ b/silx/gui/plot/items/__init__.py @@ -36,7 +36,7 @@ from .core import (Item, LabelsMixIn, DraggableMixIn, ColormapMixIn, # noqa SymbolMixIn, ColorMixIn, YAxisMixIn, FillMixIn, # noqa AlphaMixIn, LineMixIn, ItemChangedType) # noqa from .complex import ImageComplexData # noqa -from .curve import Curve # noqa +from .curve import Curve, CurveStyle # noqa from .histogram import Histogram # noqa from .image import ImageBase, ImageData, ImageRgba, MaskImageData # noqa from .shape import Shape # noqa diff --git a/silx/gui/plot/items/axis.py b/silx/gui/plot/items/axis.py index 3d9fe14..8ea5c7a 100644 --- a/silx/gui/plot/items/axis.py +++ b/silx/gui/plot/items/axis.py @@ -27,16 +27,16 @@ __authors__ = ["V. Valls"] __license__ = "MIT" -__date__ = "06/12/2017" +__date__ = "22/11/2018" import datetime as dt +import enum import logging import dateutil.tz from ... import qt -from silx.third_party import enum _logger = logging.getLogger(__name__) @@ -448,6 +448,8 @@ class YAxis(Axis): False for Y axis going from bottom to top """ flag = bool(flag) + if self.isInverted() == flag: + return self._getBackend().setYAxisInverted(flag) self._getPlot()._setDirtyPlot() self.sigInvertedChanged.emit(flag) diff --git a/silx/gui/plot/items/complex.py b/silx/gui/plot/items/complex.py index 535b0a9..7fffd77 100644 --- a/silx/gui/plot/items/complex.py +++ b/silx/gui/plot/items/complex.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2017-2018 European Synchrotron Radiation Facility +# Copyright (c) 2017-2019 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 @@ -33,9 +33,9 @@ __date__ = "14/06/2018" import logging -import numpy +import enum -from silx.third_party import enum +import numpy from ...colors import Colormap from .core import ColormapMixIn, ItemChangedType @@ -137,7 +137,6 @@ class ImageComplexData(ImageBase, ColormapMixIn): name='hsv', vmin=-numpy.pi, vmax=numpy.pi) - phaseColormap.setEditable(False) self._colormaps = { # Default colormaps for all modes self.Mode.ABSOLUTE: colormap, @@ -180,7 +179,6 @@ class ImageComplexData(ImageBase, ColormapMixIn): colormap=colormap, alpha=self.getAlpha()) - def setVisualizationMode(self, mode): """Set the visualization mode to use. diff --git a/silx/gui/plot/items/core.py b/silx/gui/plot/items/core.py index e000751..bf3b719 100644 --- a/silx/gui/plot/items/core.py +++ b/silx/gui/plot/items/core.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2017-2018 European Synchrotron Radiation Facility +# Copyright (c) 2017-2019 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 @@ -27,20 +27,23 @@ __authors__ = ["T. Vincent"] __license__ = "MIT" -__date__ = "14/06/2018" +__date__ = "29/01/2019" import collections from copy import deepcopy import logging +import enum import warnings import weakref + import numpy -from silx.third_party import six, enum +import six from ... import qt from ... import colors from ...colors import Colormap +from silx import config _logger = logging.getLogger(__name__) @@ -82,6 +85,9 @@ class ItemChangedType(enum.Enum): COLOR = 'colorChanged' """Item's color changed flag.""" + LINE_BG_COLOR = 'lineBgColorChanged' + """Item's line background color changed flag.""" + YAXIS = 'yAxisChanged' """Item's Y axis binding changed flag.""" @@ -411,10 +417,12 @@ class ColormapMixIn(ItemMixInBase): return self._colormap def setColormap(self, colormap): - """Set the colormap of this image + """Set the colormap of this item :param silx.gui.colors.Colormap colormap: colormap description """ + if self._colormap is colormap: + return if isinstance(colormap, dict): colormap = Colormap._fromDict(colormap) @@ -433,10 +441,10 @@ class ColormapMixIn(ItemMixInBase): class SymbolMixIn(ItemMixInBase): """Mix-in class for items with symbol type""" - _DEFAULT_SYMBOL = '' + _DEFAULT_SYMBOL = None """Default marker of the item""" - _DEFAULT_SYMBOL_SIZE = 6.0 + _DEFAULT_SYMBOL_SIZE = config.DEFAULT_PLOT_SYMBOL_SIZE """Default marker size of the item""" _SUPPORTED_SYMBOLS = collections.OrderedDict(( @@ -451,8 +459,15 @@ class SymbolMixIn(ItemMixInBase): """Dict of supported symbols""" def __init__(self): - self._symbol = self._DEFAULT_SYMBOL - self._symbol_size = self._DEFAULT_SYMBOL_SIZE + if self._DEFAULT_SYMBOL is None: # Use default from config + self._symbol = config.DEFAULT_PLOT_SYMBOL + else: + self._symbol = self._DEFAULT_SYMBOL + + if self._DEFAULT_SYMBOL_SIZE is None: # Use default from config + self._symbol_size = config.DEFAULT_PLOT_SYMBOL_SIZE + else: + self._symbol_size = self._DEFAULT_SYMBOL_SIZE @classmethod def getSupportedSymbols(cls): @@ -892,14 +907,14 @@ class Points(Item, SymbolMixIn, AlphaMixIn): # use the getData class method because instance method can be # overloaded to return additional arrays data = Points.getData(self, copy=False, - displayed=True) + displayed=True) if len(data) == 5: # hack to avoid duplicating caching mechanism in Scatter # (happens when cached data is used, caching done using # Scatter._logFilterData) - x, y, xerror, yerror = data[0], data[1], data[3], data[4] + x, y, _xerror, _yerror = data[0], data[1], data[3], data[4] else: - x, y, xerror, yerror = data + x, y, _xerror, _yerror = data self._boundsCache[(xPositive, yPositive)] = ( numpy.nanmin(x), diff --git a/silx/gui/plot/items/curve.py b/silx/gui/plot/items/curve.py index 80d9dea..79def55 100644 --- a/silx/gui/plot/items/curve.py +++ b/silx/gui/plot/items/curve.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2017-2018 European Synchrotron Radiation Facility +# Copyright (c) 2017-2019 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 @@ -31,9 +31,10 @@ __date__ = "24/04/2018" import logging + import numpy +import six -from silx.third_party import six from ....utils.deprecation import deprecated from ... import colors from .core import (Points, LabelsMixIn, ColorMixIn, YAxisMixIn, diff --git a/silx/gui/plot/items/histogram.py b/silx/gui/plot/items/histogram.py index 389e8a6..a1d6586 100644 --- a/silx/gui/plot/items/histogram.py +++ b/silx/gui/plot/items/histogram.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2017 European Synchrotron Radiation Facility +# Copyright (c) 2017-2019 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 @@ -197,13 +197,13 @@ class Histogram(Item, AlphaMixIn, ColorMixIn, FillMixIn, values[clipped_values] = numpy.nan - if xPositive or yPositive: + if yPositive: return (numpy.nanmin(edges), numpy.nanmax(edges), numpy.nanmin(values), numpy.nanmax(values)) - else: # No log scale, include 0 in bounds + else: # No log scale on y axis, include 0 in bounds return (numpy.nanmin(edges), numpy.nanmax(edges), min(0, numpy.nanmin(values)), diff --git a/silx/gui/plot/items/roi.py b/silx/gui/plot/items/roi.py index f55ef91..0169439 100644 --- a/silx/gui/plot/items/roi.py +++ b/silx/gui/plot/items/roi.py @@ -65,7 +65,7 @@ class RegionOfInterest(qt.QObject): # Avoid circular dependancy from ..tools import roi as roi_tools assert parent is None or isinstance(parent, roi_tools.RegionOfInterestManager) - super(RegionOfInterest, self).__init__(parent) + qt.QObject.__init__(self, parent) self._color = rgba('red') self._items = WeakList() self._editAnchors = WeakList() @@ -108,7 +108,7 @@ class RegionOfInterest(qt.QObject): return qt.QColor.fromRgbF(*self._color) def _getAnchorColor(self, color): - """Returns the anchor color from the base ROI color + """Returns the anchor color from the base ROI color :param Union[numpy.array,Tuple,List]: color :rtype: Union[numpy.array,Tuple,List] @@ -209,7 +209,7 @@ class RegionOfInterest(qt.QObject): def setFirstShapePoints(self, points): """"Initialize the ROI using the points from the first interaction. - This interaction is constains by the plot API and only supports few + This interaction is constrained by the plot API and only supports few shapes. """ points = self._createControlPointsFromFirstShape(points) @@ -410,6 +410,13 @@ class RegionOfInterest(qt.QObject): plot._remove(item) self._labelItem = None + def _updated(self, event=None, checkVisibility=True): + """Implement Item mix-in update method by updating the plot items + + See :class:`~silx.gui.plot.items.Item._updated` + """ + self._createPlotItems() + def __str__(self): """Returns parameters of the ROI as a string.""" points = self._getControlPoints() @@ -417,7 +424,7 @@ class RegionOfInterest(qt.QObject): return "%s(%s)" % (self.__class__.__name__, params) -class PointROI(RegionOfInterest): +class PointROI(RegionOfInterest, items.SymbolMixIn): """A ROI identifying a point in a 2D plot.""" _kind = "Point" @@ -426,6 +433,10 @@ class PointROI(RegionOfInterest): _plotShape = "point" """Plot shape which is used for the first interaction""" + def __init__(self, parent=None): + items.SymbolMixIn.__init__(self) + RegionOfInterest.__init__(self, parent=parent) + def getPosition(self): """Returns the position of this ROI @@ -458,6 +469,8 @@ class PointROI(RegionOfInterest): marker.setPosition(points[0][0], points[0][1]) marker.setText(self.getLabel()) marker.setColor(rgba(self.getColor())) + marker.setSymbol(self.getSymbol()) + marker.setSymbolSize(self.getSymbolSize()) marker._setDraggable(False) return [marker] @@ -466,6 +479,8 @@ class PointROI(RegionOfInterest): marker.setPosition(points[0][0], points[0][1]) marker.setText(self.getLabel()) marker._setDraggable(self.isEditable()) + marker.setSymbol(self.getSymbol()) + marker.setSymbolSize(self.getSymbolSize()) return [marker] def __str__(self): @@ -474,7 +489,7 @@ class PointROI(RegionOfInterest): return "%s(%s)" % (self.__class__.__name__, params) -class LineROI(RegionOfInterest): +class LineROI(RegionOfInterest, items.LineMixIn): """A ROI identifying a line in a 2D plot. This ROI provides 1 anchor for each boundary of the line, plus an center @@ -487,6 +502,10 @@ class LineROI(RegionOfInterest): _plotShape = "line" """Plot shape which is used for the first interaction""" + def __init__(self, parent=None): + items.LineMixIn.__init__(self) + RegionOfInterest.__init__(self, parent=parent) + def _createControlPointsFromFirstShape(self, points): center = numpy.mean(points, axis=0) controlPoints = numpy.array([points[0], points[1], center]) @@ -535,6 +554,8 @@ class LineROI(RegionOfInterest): item.setColor(rgba(self.getColor())) item.setFill(False) item.setOverlay(True) + item.setLineStyle(self.getLineStyle()) + item.setLineWidth(self.getLineWidth()) return [item] def _createAnchorItems(self, points): @@ -582,7 +603,7 @@ class LineROI(RegionOfInterest): return "%s(%s)" % (self.__class__.__name__, params) -class HorizontalLineROI(RegionOfInterest): +class HorizontalLineROI(RegionOfInterest, items.LineMixIn): """A ROI identifying an horizontal line in a 2D plot.""" _kind = "HLine" @@ -591,6 +612,10 @@ class HorizontalLineROI(RegionOfInterest): _plotShape = "hline" """Plot shape which is used for the first interaction""" + def __init__(self, parent=None): + items.LineMixIn.__init__(self) + RegionOfInterest.__init__(self, parent=parent) + def _createControlPointsFromFirstShape(self, points): points = numpy.array([(float('nan'), points[0, 1])], dtype=numpy.float64) @@ -636,6 +661,8 @@ class HorizontalLineROI(RegionOfInterest): marker.setText(self.getLabel()) marker.setColor(rgba(self.getColor())) marker._setDraggable(False) + marker.setLineWidth(self.getLineWidth()) + marker.setLineStyle(self.getLineStyle()) return [marker] def _createAnchorItems(self, points): @@ -643,6 +670,8 @@ class HorizontalLineROI(RegionOfInterest): marker.setPosition(points[0][0], points[0][1]) marker.setText(self.getLabel()) marker._setDraggable(self.isEditable()) + marker.setLineWidth(self.getLineWidth()) + marker.setLineStyle(self.getLineStyle()) return [marker] def __str__(self): @@ -651,7 +680,7 @@ class HorizontalLineROI(RegionOfInterest): return "%s(%s)" % (self.__class__.__name__, params) -class VerticalLineROI(RegionOfInterest): +class VerticalLineROI(RegionOfInterest, items.LineMixIn): """A ROI identifying a vertical line in a 2D plot.""" _kind = "VLine" @@ -660,6 +689,10 @@ class VerticalLineROI(RegionOfInterest): _plotShape = "vline" """Plot shape which is used for the first interaction""" + def __init__(self, parent=None): + items.LineMixIn.__init__(self) + RegionOfInterest.__init__(self, parent=parent) + def _createControlPointsFromFirstShape(self, points): points = numpy.array([(points[0, 0], float('nan'))], dtype=numpy.float64) @@ -705,6 +738,8 @@ class VerticalLineROI(RegionOfInterest): marker.setText(self.getLabel()) marker.setColor(rgba(self.getColor())) marker._setDraggable(False) + marker.setLineWidth(self.getLineWidth()) + marker.setLineStyle(self.getLineStyle()) return [marker] def _createAnchorItems(self, points): @@ -712,6 +747,8 @@ class VerticalLineROI(RegionOfInterest): marker.setPosition(points[0][0], points[0][1]) marker.setText(self.getLabel()) marker._setDraggable(self.isEditable()) + marker.setLineWidth(self.getLineWidth()) + marker.setLineStyle(self.getLineStyle()) return [marker] def __str__(self): @@ -720,7 +757,7 @@ class VerticalLineROI(RegionOfInterest): return "%s(%s)" % (self.__class__.__name__, params) -class RectangleROI(RegionOfInterest): +class RectangleROI(RegionOfInterest, items.LineMixIn): """A ROI identifying a rectangle in a 2D plot. This ROI provides 1 anchor for each corner, plus an anchor in the @@ -733,6 +770,10 @@ class RectangleROI(RegionOfInterest): _plotShape = "rectangle" """Plot shape which is used for the first interaction""" + def __init__(self, parent=None): + items.LineMixIn.__init__(self) + RegionOfInterest.__init__(self, parent=parent) + def _createControlPointsFromFirstShape(self, points): point0 = points[0] point1 = points[1] @@ -838,6 +879,8 @@ class RectangleROI(RegionOfInterest): item.setColor(rgba(self.getColor())) item.setFill(False) item.setOverlay(True) + item.setLineStyle(self.getLineStyle()) + item.setLineWidth(self.getLineWidth()) return [item] def _createAnchorItems(self, points): @@ -894,7 +937,7 @@ class RectangleROI(RegionOfInterest): return "%s(%s)" % (self.__class__.__name__, params) -class PolygonROI(RegionOfInterest): +class PolygonROI(RegionOfInterest, items.LineMixIn): """A ROI identifying a closed polygon in a 2D plot. This ROI provides 1 anchor for each point of the polygon. @@ -906,6 +949,10 @@ class PolygonROI(RegionOfInterest): _plotShape = "polygon" """Plot shape which is used for the first interaction""" + def __init__(self, parent=None): + items.LineMixIn.__init__(self) + RegionOfInterest.__init__(self, parent=parent) + def getPoints(self): """Returns the list of the points of this polygon. @@ -948,6 +995,8 @@ class PolygonROI(RegionOfInterest): item.setColor(rgba(self.getColor())) item.setFill(False) item.setOverlay(True) + item.setLineStyle(self.getLineStyle()) + item.setLineWidth(self.getLineWidth()) return [item] def _createAnchorItems(self, points): @@ -967,7 +1016,7 @@ class PolygonROI(RegionOfInterest): return "%s(%s)" % (self.__class__.__name__, params) -class ArcROI(RegionOfInterest): +class ArcROI(RegionOfInterest, items.LineMixIn): """A ROI identifying an arc of a circle with a width. This ROI provides 3 anchors to control the curvature, 1 anchor to control @@ -986,6 +1035,7 @@ class ArcROI(RegionOfInterest): 'startAngle', 'endAngle']) def __init__(self, parent=None): + items.LineMixIn.__init__(self) RegionOfInterest.__init__(self, parent=parent) self._geometry = None @@ -1357,6 +1407,8 @@ class ArcROI(RegionOfInterest): item.setColor(rgba(self.getColor())) item.setFill(False) item.setOverlay(True) + item.setLineStyle(self.getLineStyle()) + item.setLineWidth(self.getLineWidth()) return [item] def _createAnchorItems(self, points): diff --git a/silx/gui/plot/items/scatter.py b/silx/gui/plot/items/scatter.py index acc74b4..707dd3d 100644 --- a/silx/gui/plot/items/scatter.py +++ b/silx/gui/plot/items/scatter.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2017-2018 European Synchrotron Radiation Facility +# Copyright (c) 2017-2019 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 @@ -46,9 +46,6 @@ class Scatter(Points, ColormapMixIn): _DEFAULT_SELECTABLE = True """Default selectable state for scatter plots""" - _DEFAULT_SYMBOL = 'o' - """Default symbol of the scatter plots""" - def __init__(self): Points.__init__(self) ColormapMixIn.__init__(self) diff --git a/silx/gui/plot/items/shape.py b/silx/gui/plot/items/shape.py index 65b26a1..9fc1306 100644 --- a/silx/gui/plot/items/shape.py +++ b/silx/gui/plot/items/shape.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2017 European Synchrotron Radiation Facility +# Copyright (c) 2017-2019 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 @@ -27,14 +27,16 @@ __authors__ = ["T. Vincent"] __license__ = "MIT" -__date__ = "17/05/2017" +__date__ = "21/12/2018" import logging import numpy +import six -from .core import (Item, ColorMixIn, FillMixIn, ItemChangedType) +from ... import colors +from .core import Item, ColorMixIn, FillMixIn, ItemChangedType, LineMixIn _logger = logging.getLogger(__name__) @@ -42,7 +44,7 @@ _logger = logging.getLogger(__name__) # TODO probably make one class for each kind of shape # TODO check fill:polygon/polyline + fill = duplicated -class Shape(Item, ColorMixIn, FillMixIn): +class Shape(Item, ColorMixIn, FillMixIn, LineMixIn): """Description of a shape item :param str type_: The type of shape in: @@ -53,10 +55,12 @@ class Shape(Item, ColorMixIn, FillMixIn): Item.__init__(self) ColorMixIn.__init__(self) FillMixIn.__init__(self) + LineMixIn.__init__(self) self._overlay = False assert type_ in ('hline', 'polygon', 'rectangle', 'vline', 'polylines') self._type = type_ self._points = () + self._lineBgColor = None self._handle = None @@ -71,7 +75,10 @@ class Shape(Item, ColorMixIn, FillMixIn): color=self.getColor(), fill=self.isFill(), overlay=self.isOverlay(), - z=self.getZValue()) + z=self.getZValue(), + linestyle=self.getLineStyle(), + linewidth=self.getLineWidth(), + linebgcolor=self.getLineBgColor()) def isOverlay(self): """Return true if shape is drawn as an overlay @@ -119,3 +126,31 @@ class Shape(Item, ColorMixIn, FillMixIn): """ self._points = numpy.array(points, copy=copy) self._updated(ItemChangedType.DATA) + + def getLineBgColor(self): + """Returns the RGBA color of the item + :rtype: 4-tuple of float in [0, 1] or array of colors + """ + return self._lineBgColor + + def setLineBgColor(self, color, copy=True): + """Set item color + :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 bool copy: True (Default) to get a copy, + False to use internal representation (do not modify!) + """ + if color is not None: + if isinstance(color, six.string_types): + color = colors.rgba(color) + else: + color = numpy.array(color, copy=copy) + # TODO more checks + improve color array support + if color.ndim == 1: # Single RGBA color + color = colors.rgba(color) + else: # Array of colors + assert color.ndim == 2 + + self._lineBgColor = color + self._updated(ItemChangedType.LINE_BG_COLOR) |