summaryrefslogtreecommitdiff
path: root/silx/gui/plot/items/axis.py
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/plot/items/axis.py')
-rw-r--r--silx/gui/plot/items/axis.py154
1 files changed, 122 insertions, 32 deletions
diff --git a/silx/gui/plot/items/axis.py b/silx/gui/plot/items/axis.py
index d7e6eff..3d9fe14 100644
--- a/silx/gui/plot/items/axis.py
+++ b/silx/gui/plot/items/axis.py
@@ -1,7 +1,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2017 European Synchrotron Radiation Facility
+# Copyright (c) 2017-2018 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
@@ -29,12 +29,24 @@ __authors__ = ["V. Valls"]
__license__ = "MIT"
__date__ = "06/12/2017"
+import datetime as dt
import logging
+
+import dateutil.tz
+
from ... import qt
+from silx.third_party import enum
+
_logger = logging.getLogger(__name__)
+class TickMode(enum.Enum):
+ """Determines if ticks are regular number or datetimes."""
+ DEFAULT = 0 # Ticks are regular numbers
+ TIME_SERIES = 1 # Ticks are datetime objects
+
+
class Axis(qt.QObject):
"""This class describes and controls a plot axis.
@@ -82,7 +94,23 @@ class Axis(qt.QObject):
# Store currently displayed labels
# Current label can differ from input one with active curve handling
self._currentLabel = ''
- self._plot = plot
+
+ def _getPlot(self):
+ """Returns the PlotWidget this Axis belongs to.
+
+ :rtype: PlotWidget
+ """
+ plot = self.parent()
+ if plot is None:
+ raise RuntimeError("Axis no longer attached to a PlotWidget")
+ return plot
+
+ def _getBackend(self):
+ """Returns the backend
+
+ :rtype: BackendBase
+ """
+ return self._getPlot()._backend
def getLimits(self):
"""Get the limits of this axis.
@@ -102,7 +130,7 @@ class Axis(qt.QObject):
return
self._internalSetLimits(vmin, vmax)
- self._plot._setDirtyPlot()
+ self._getPlot()._setDirtyPlot()
self._emitLimitsChanged()
@@ -110,7 +138,7 @@ class Axis(qt.QObject):
"""Emit axis sigLimitsChanged and PlotWidget limitsChanged event"""
vmin, vmax = self.getLimits()
self.sigLimitsChanged.emit(vmin, vmax)
- self._plot._notifyLimitsChanged(emitSignal=False)
+ self._getPlot()._notifyLimitsChanged(emitSignal=False)
def _checkLimits(self, vmin, vmax):
"""Makes sure axis range is not empty
@@ -172,7 +200,7 @@ class Axis(qt.QObject):
"""
self._defaultLabel = label
self._setCurrentLabel(label)
- self._plot._setDirtyPlot()
+ self._getPlot()._setDirtyPlot()
def _setCurrentLabel(self, label):
"""Define the label currently displayed.
@@ -207,6 +235,14 @@ class Axis(qt.QObject):
# For the backward compatibility signal
emitLog = self._scale == self.LOGARITHMIC or scale == self.LOGARITHMIC
+ self._scale = scale
+
+ # TODO hackish way of forcing update of curves and images
+ plot = self._getPlot()
+ for item in plot._getItems(withhidden=True):
+ item._updated()
+ plot._invalidateDataRange()
+
if scale == self.LOGARITHMIC:
self._internalSetLogarithmic(True)
elif scale == self.LINEAR:
@@ -214,13 +250,7 @@ class Axis(qt.QObject):
else:
raise ValueError("Scale %s unsupported" % scale)
- self._scale = scale
-
- # TODO hackish way of forcing update of curves and images
- for item in self._plot._getItems(withhidden=True):
- item._updated()
- self._plot._invalidateDataRange()
- self._plot._forceResetZoom()
+ plot._forceResetZoom()
self.sigScaleChanged.emit(self._scale)
if emitLog:
@@ -241,6 +271,40 @@ class Axis(qt.QObject):
flag = bool(flag)
self.setScale(self.LOGARITHMIC if flag else self.LINEAR)
+ def getTimeZone(self):
+ """Sets tzinfo that is used if this axis plots date times.
+
+ None means the datetimes are interpreted as local time.
+
+ :rtype: datetime.tzinfo of None.
+ """
+ raise NotImplementedError()
+
+ def setTimeZone(self, tz):
+ """Sets tzinfo that is used if this axis' tickMode is TIME_SERIES
+
+ The tz must be a descendant of the datetime.tzinfo class, "UTC" or None.
+ Use None to let the datetimes be interpreted as local time.
+ Use the string "UTC" to let the date datetimes be in UTC time.
+
+ :param tz: datetime.tzinfo, "UTC" or None.
+ """
+ raise NotImplementedError()
+
+ def getTickMode(self):
+ """Determines if axis ticks are number or datetimes.
+
+ :rtype: TickMode enum.
+ """
+ raise NotImplementedError()
+
+ def setTickMode(self, tickMode):
+ """Determines if axis ticks are number or datetimes.
+
+ :param TickMode tickMode: tick mode enum.
+ """
+ raise NotImplementedError()
+
def isAutoScale(self):
"""Return True if axis is automatically adjusting its limits.
@@ -271,7 +335,7 @@ class Axis(qt.QObject):
"""
updated = self._setLimitsConstraints(minPos, maxPos)
if updated:
- plot = self._plot
+ plot = self._getPlot()
xMin, xMax = plot.getXAxis().getLimits()
yMin, yMax = plot.getYAxis().getLimits()
y2Min, y2Max = plot.getYAxis('right').getLimits()
@@ -294,7 +358,7 @@ class Axis(qt.QObject):
"""
updated = self._setRangeConstraints(minRange, maxRange)
if updated:
- plot = self._plot
+ plot = self._getPlot()
xMin, xMax = plot.getXAxis().getLimits()
yMin, yMax = plot.getYAxis().getLimits()
y2Min, y2Max = plot.getYAxis('right').getLimits()
@@ -308,25 +372,51 @@ class XAxis(Axis):
# TODO With some changes on the backend, it will be able to remove all this
# specialised implementations (prefixel by '_internal')
+ def getTimeZone(self):
+ return self._getBackend().getXAxisTimeZone()
+
+ def setTimeZone(self, tz):
+ if isinstance(tz, str) and tz.upper() == "UTC":
+ tz = dateutil.tz.tzutc()
+ elif not(tz is None or isinstance(tz, dt.tzinfo)):
+ raise TypeError("tz must be a dt.tzinfo object, None or 'UTC'.")
+
+ self._getBackend().setXAxisTimeZone(tz)
+ self._getPlot()._setDirtyPlot()
+
+ def getTickMode(self):
+ if self._getBackend().isXAxisTimeSeries():
+ return TickMode.TIME_SERIES
+ else:
+ return TickMode.DEFAULT
+
+ def setTickMode(self, tickMode):
+ if tickMode == TickMode.DEFAULT:
+ self._getBackend().setXAxisTimeSeries(False)
+ elif tickMode == TickMode.TIME_SERIES:
+ self._getBackend().setXAxisTimeSeries(True)
+ else:
+ raise ValueError("Unexpected TickMode: {}".format(tickMode))
+
def _internalSetCurrentLabel(self, label):
- self._plot._backend.setGraphXLabel(label)
+ self._getBackend().setGraphXLabel(label)
def _internalGetLimits(self):
- return self._plot._backend.getGraphXLimits()
+ return self._getBackend().getGraphXLimits()
def _internalSetLimits(self, xmin, xmax):
- self._plot._backend.setGraphXLimits(xmin, xmax)
+ self._getBackend().setGraphXLimits(xmin, xmax)
def _internalSetLogarithmic(self, flag):
- self._plot._backend.setXAxisLogarithmic(flag)
+ self._getBackend().setXAxisLogarithmic(flag)
def _setLimitsConstraints(self, minPos=None, maxPos=None):
- constrains = self._plot._getViewConstraints()
+ constrains = self._getPlot()._getViewConstraints()
updated = constrains.update(xMin=minPos, xMax=maxPos)
return updated
def _setRangeConstraints(self, minRange=None, maxRange=None):
- constrains = self._plot._getViewConstraints()
+ constrains = self._getPlot()._getViewConstraints()
updated = constrains.update(minXRange=minRange, maxXRange=maxRange)
return updated
@@ -338,16 +428,16 @@ class YAxis(Axis):
# specialised implementations (prefixel by '_internal')
def _internalSetCurrentLabel(self, label):
- self._plot._backend.setGraphYLabel(label, axis='left')
+ self._getBackend().setGraphYLabel(label, axis='left')
def _internalGetLimits(self):
- return self._plot._backend.getGraphYLimits(axis='left')
+ return self._getBackend().getGraphYLimits(axis='left')
def _internalSetLimits(self, ymin, ymax):
- self._plot._backend.setGraphYLimits(ymin, ymax, axis='left')
+ self._getBackend().setGraphYLimits(ymin, ymax, axis='left')
def _internalSetLogarithmic(self, flag):
- self._plot._backend.setYAxisLogarithmic(flag)
+ self._getBackend().setYAxisLogarithmic(flag)
def setInverted(self, flag=True):
"""Set the axis orientation.
@@ -358,8 +448,8 @@ class YAxis(Axis):
False for Y axis going from bottom to top
"""
flag = bool(flag)
- self._plot._backend.setYAxisInverted(flag)
- self._plot._setDirtyPlot()
+ self._getBackend().setYAxisInverted(flag)
+ self._getPlot()._setDirtyPlot()
self.sigInvertedChanged.emit(flag)
def isInverted(self):
@@ -368,15 +458,15 @@ class YAxis(Axis):
:rtype: bool
"""
- return self._plot._backend.isYAxisInverted()
+ return self._getBackend().isYAxisInverted()
def _setLimitsConstraints(self, minPos=None, maxPos=None):
- constrains = self._plot._getViewConstraints()
+ constrains = self._getPlot()._getViewConstraints()
updated = constrains.update(yMin=minPos, yMax=maxPos)
return updated
def _setRangeConstraints(self, minRange=None, maxRange=None):
- constrains = self._plot._getViewConstraints()
+ constrains = self._getPlot()._getViewConstraints()
updated = constrains.update(minYRange=minRange, maxYRange=maxRange)
return updated
@@ -419,13 +509,13 @@ class YRightAxis(Axis):
return self.__mainAxis.sigAutoScaleChanged
def _internalSetCurrentLabel(self, label):
- self._plot._backend.setGraphYLabel(label, axis='right')
+ self._getBackend().setGraphYLabel(label, axis='right')
def _internalGetLimits(self):
- return self._plot._backend.getGraphYLimits(axis='right')
+ return self._getBackend().getGraphYLimits(axis='right')
def _internalSetLimits(self, ymin, ymax):
- self._plot._backend.setGraphYLimits(ymin, ymax, axis='right')
+ self._getBackend().setGraphYLimits(ymin, ymax, axis='right')
def setInverted(self, flag=True):
"""Set the Y axis orientation.