#!/usr/bin/env python
# ###########################################################################
#
# This file is part of Taurus
#
# http://taurus-scada.org
#
# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain
#
# Taurus is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Taurus is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Taurus. If not, see <http://www.gnu.org/licenses/>.
#
# ###########################################################################
"""
taurustrend.py: Generic trend widget for Taurus
"""
from guiqwt.plot import ImageDialog
from taurus.external.qt import Qt
import taurus.core
from taurus.qt.qtcore.configuration import BaseConfigurableClass
from taurus.qt.qtgui.base import TaurusBaseWidget
from taurus.qt.qtgui.extra_guiqwt.image import TaurusTrend2DItem
from taurus.qt.qtgui.extra_guiqwt.tools import (
TaurusModelChooserTool,
TimeAxisTool,
AutoScrollTool,
AutoScaleXTool,
AutoScaleYTool,
AutoScaleZTool,
)
import taurus.cli.common
[docs]
class TaurusTrend2DDialog(ImageDialog, TaurusBaseWidget):
"""
This is a widget for displaying trends from 1D Taurus attributes (i.e.,
representing the variation over time of a 1D array). Sometimes this kind of
plots are also known as "spectrograms".
The widget shows a 3D plot (Z represented with colors) where the values in
the 1D array are plotted in the Y-Z plane and are stacked along the X axis.
"""
_modifiableByUser = True
def __init__(
self,
parent=None,
designMode=False,
toolbar=True,
stackMode="deltatime",
buffersize=512,
options=None,
autoscale="xyz",
**kwargs
):
"""
:param param: param to be passed to XYImageItem constructor
:param buffersize: size of the stack
:type buffersize: int
:param stackMode: can be 'datetime', 'timedelta' or 'event'
:type stackMode: str
:param autoscale: if autscale string contains 'x', the x axis will be
autoscaled. Same with 'y' and 'z'. Defaults to 'xyz'
:type autoscale: str
"""
"""see :class:`guiqwt.plot.ImageDialog` for other valid initialization
parameters
"""
defaultOptions = dict(lock_aspect_ratio=False)
if options is not None:
defaultOptions.update(options)
ImageDialog.__init__(
self,
parent=parent,
toolbar=toolbar,
options=defaultOptions,
**kwargs,
)
TaurusBaseWidget.__init__(self, "TaurusTrend2DDialog")
# support x_axis_mode values (map them to stackMode values)
stackMode = dict(
t="datetime", d="deltatime", e="event", n="event"
).get(stackMode, stackMode)
self.trendItem = None
self.buffersize = buffersize
self._useArchiving = False
self._stackMode = stackMode
self.setStackMode(stackMode)
self.setWindowFlags(Qt.Qt.Widget)
# add some tools
for toolklass in (
TaurusModelChooserTool,
AutoScrollTool,
AutoScaleXTool,
AutoScaleYTool,
AutoScaleZTool,
):
self.add_tool(toolklass)
self.get_tool(TaurusModelChooserTool).singleModel = True
if "x" in autoscale.lower():
self.get_tool(AutoScaleXTool).setChecked(True)
if "y" in autoscale.lower():
self.get_tool(AutoScaleYTool).setChecked(True)
if "z" in autoscale.lower():
self.get_tool(AutoScaleZTool).setChecked(True)
self.setModifiableByUser(self._modifiableByUser)
self.setContextMenuPolicy(Qt.Qt.CustomContextMenu)
# Config properties
self.setModelInConfig(True)
self.registerConfigDelegate(
self.trendItem or BaseConfigurableClass(), name="trendItem"
)
self.registerConfigDelegate(
self.get_tool(AutoScrollTool), name="AutoScrollTool"
)
self.registerConfigDelegate(
self.get_tool(AutoScaleXTool), name="AutoScaleXTool"
)
self.registerConfigDelegate(
self.get_tool(AutoScaleYTool), name="AutoScaleYTool"
)
self.registerConfigDelegate(
self.get_tool(AutoScaleZTool), name="AutoScaleZTool"
)
self.registerConfigProperty(
self.getStackMode, self.setStackMode, "stackMode"
)
self.registerConfigProperty(
self._get_axes_conf, self._set_axes_conf, "axes_confs"
)
def _get_axes_conf(self):
p = self.get_plot()
conf = {}
for a in p.AXIS_IDS:
c = conf[a] = {}
c["limits"] = p.get_axis_limits(a)
c["title"] = p.get_axis_title(a)
c["color"] = p.get_axis_color(a)
c["scale"] = p.get_axis_scale(a)
c["unit"] = p.get_axis_unit(a)
return conf
def _set_axes_conf(self, axes_conf):
p = self.get_plot()
for a in p.AXIS_IDS:
c = axes_conf[a]
if "limits" in c:
p.set_axis_limits(a, *c["limits"])
if "title" in c:
p.set_axis_title(a, c["title"])
if "color" in c:
p.set_axis_color(a, c["color"])
if "scale" in c:
p.set_axis_scale(a, c["scale"], autoscale=False)
if "unit" in c:
p.set_axis_unit(a, c["unit"])
[docs]
def keyPressEvent(self, event):
if event.key() == Qt.Qt.Key_Escape:
event.ignore()
else:
ImageDialog.keyPressEvent(self, event)
[docs]
def setStackMode(self, mode):
"""set the type of stack to be used. This determines how X values are
interpreted:
- as timestamps ('datetime' or 't')
- as time deltas ('deltatime' or 'd')
- as event numbers ('event' or 'e')
:param mode:
:type mode: one of 'datetime', 'timedelta' or 'event'
"""
mode = str(mode)
if mode == "datetime":
self.add_tool(TimeAxisTool)
timetool = self.get_tool(TimeAxisTool)
timetool.set_scale_y_t(True)
elif mode == "deltatime":
from taurus.qt.qtgui.extra_guiqwt.scales import (
DeltaTimeScaleEngine,
)
plot = self.get_plot()
DeltaTimeScaleEngine.enableInAxis(plot, plot.xBottom, rotation=-45)
elif mode == "event":
plot = self.get_plot()
scaleEngine = plot.axisScaleEngine(plot.xBottom)
if hasattr(scaleEngine, "disableInAxis"):
scaleEngine.disableInAxis(plot, plot.xBottom)
else:
self.error('Unknown stack mode "%s"' % repr(mode))
return
self._stackMode = mode
if hasattr(self.trendItem, "stackMode"):
self.trendItem.stackMode = mode
[docs]
def getStackMode(self):
return self._stackMode
[docs]
def resetStackMode(self):
self.setStackMode("datetime")
[docs]
def getModelClass(self, **kwargs):
"""reimplemented from :class:`TaurusBaseWidget`"""
return taurus.core.taurusattribute.TaurusAttribute
[docs]
def setModel(self, model, **kwargs):
"""reimplemented from :class:`TaurusBaseWidget`"""
plot = self.get_plot()
if self.trendItem is not None:
plot.del_item(self.trendItem)
self.trendItem = TaurusTrend2DItem(
stackMode=self.getStackMode(), buffersize=self.buffersize
)
self.trendItem.setModel(model)
plot.add_item(self.trendItem, autoscale=False)
self.trendItem.set_readonly(not self.isModifiableByUser())
plot.set_axis_title(plot.colormap_axis, "value")
plot.set_axis_unit("left", "index")
try:
plot.set_axis_title(
"left", self.trendItem.getModelObj().getSimpleName()
)
except Exception:
self.debug("cannot set title for left axis")
self.traceback()
try:
unit = self.trendItem.getModelObj().rvalue.unit or ""
plot.set_axis_unit(plot.colormap_axis, unit)
except Exception:
self.debug("cannot set units for colormap axis")
self.traceback()
self.trendItem.dataChanged.connect(self.update_cross_sections)
# unregister old trendItem and register the new one as config delegate
self.unregisterConfigurableItem("trendItem", raiseOnError=False)
if self.getModelInConfig():
self.registerConfigDelegate(self.trendItem, name="trendItem")
[docs]
def getModel(self, **kwargs):
"""reimplemented from :class:`TaurusBaseWidget`"""
if self.trendItem is None:
return None
else:
return self.trendItem.getModel()
[docs]
def setUseArchiving(self, enable):
"""enables/disables looking up in the archiver for data stored before
the Trend was started
:param enable: if True, archiving values will be used if available
:type enable: bool
"""
if not self._stackMode == "datetime":
self.info("ignoring setUseArchiving. Reason: not in X time scale")
self._useArchiving = enable
[docs]
def getUseArchiving(self):
"""whether TaurusTrend is looking for data in the archiver when needed
:return:
:rtype: bool
.. seealso:: :meth:`setUseArchiving`
"""
return self._useArchiving
[docs]
def resetUseArchiving(self):
"""Same as setUseArchiving(False)"""
self.setUseArchiving(False)
[docs]
def setMaxDataBufferSize(self, maxSize):
"""sets the maximum number of events that will be stacked
:param maxSize: the maximum limit
:type maxSize: int
.. seealso:: :class:`TaurusTrendSet`
"""
if self.trendItem is not None:
self.trendItem.setBufferSize(maxSize)
self.buffersize = maxSize
[docs]
def getMaxDataBufferSize(self):
"""returns the maximum number of events that can be plotted in the
trend
:return:
:rtype: int
"""
return self.buffersize
[docs]
def resetMaxDataBufferSize(self):
"""Same as setMaxDataBufferSize(512) (i.e. 512 events)"""
self.setMaxDataBufferSize(512)
[docs]
@classmethod
def getQtDesignerPluginInfo(cls):
"""reimplemented from :class:`TaurusBaseWidget`"""
ret = TaurusBaseWidget.getQtDesignerPluginInfo()
ret["module"] = "taurus.qt.qtgui.extra_guiqwt"
ret["group"] = "Taurus Display"
ret["icon"] = "designer:qwtplot.png"
return ret
[docs]
def setModifiableByUser(self, modifiable):
"""reimplemented from :class:`TaurusBaseWidget`"""
self.get_tool(TaurusModelChooserTool).action.setEnabled(modifiable)
self.get_plot().set_items_readonly(not modifiable)
TaurusBaseWidget.setModifiableByUser(self, modifiable)
model = Qt.pyqtProperty(
"QString", getModel, setModel, TaurusBaseWidget.resetModel
)
# @todo uncomment this when archiving is supported
useArchiving = Qt.pyqtProperty(
"bool", getUseArchiving, setUseArchiving, resetUseArchiving
)
maxDataBufferSize = Qt.pyqtProperty(
"int",
getMaxDataBufferSize,
setMaxDataBufferSize,
resetMaxDataBufferSize,
)
stackMode = Qt.pyqtProperty(
"QString", getStackMode, setStackMode, resetStackMode
)
modifiableByUser = Qt.pyqtProperty(
"bool",
TaurusBaseWidget.isModifiableByUser,
setModifiableByUser,
TaurusBaseWidget.resetModifiableByUser,
)