Source code for PyExpLabSys.common.plotters_backend_qwt

# pylint: disable=W0142,R0903,R0913

"""This module contains plotting backends that use the PyQwt library"""

from random import choice
import collections
import os

import numpy as np
try:
    from PyQt4 import Qt, QtGui, QtCore
    import PyQt4.Qwt5 as Qwt
except ImportError:
    # If we are building docs for read the docs, make fake version else re-raise
    import sys
    if os.environ.get('READTHEDOCS', None) == 'True' or 'sphinx' in sys.modules:
        class Qwt:
            QwtPlot = list
    else:
        raise

[docs]class Colors: """Class that gives plot colors"""
[docs] def __init__(self): self.current = -1 self.predefined = [] self.colors = QtGui.QColor.colorNames() for name in ['blue', 'red', 'black', 'green', 'magenta']: index = self.colors.indexOf(QtCore.QString(name)) self.predefined.append(self.colors[index]) self.colors.removeAt(index)
[docs] def get_color(self): """Return a color""" self.current += 1 if self.current < len(self.predefined): return self.predefined[self.current] else: out = choice(self.colors) self.colors.removeAt(self.colors.indexOf(out)) return out
[docs]class QwtPlot(Qwt.QwtPlot): """Class that represents a Qwt plot"""
[docs] def __init__(self, parent, left_plotlist, right_plotlist=None, left_log=False, right_log=False, **kwargs): """Initialize the plot and local setting :param parent: The parent GUI object, then that should be supplied here :type parent: GUI object :param left_plotlist: Codenames for the plots that should go on the left y-axis :type left_plotlist: iterable with strs :param right_plotlist: Codenames for the plots that should go in the right y-axis :type left_plotlist: iterable with strs :param left_log: Left y-axis should be log :type left_log: bool :param right_log: Right y-axis should be log :type right_log: bool Kwargs: :param title: The title of the plot :type title: str :param xaxis_label: Label for the x axis :type xaxis_label: str :param yaxis_left_label: Label for the left y axis :type yaxis_left_label: str :param yaxis_right_label: Label for the right y axis :type yaxis_right_label: str :param left_labels: Labels for the plots on the left y-axis. If none are given the codenames will be used. :type left_labels: iterable with strs :param right_labels: Labels for the plots on the right y-axis. If none are given the codenames will be used. :type right_labels: iterable with strs :param legend: Position of the legend. Possible values are: 'left', 'right', 'bottom', 'top'. If no argument is given, the legend will not be shown. :type legend: str :param left_colors: Colors for the left curves (see background_color for details) :type left_colors: iterable of strs :param right_colors: Colors for the right curves (see background_color for details) :type right_colors: iterable of strs :param left_thickness: Line thickness. Either an integer to apply for all left lines or a iterable of integers, one for each line. :type left_thickness: int or iterable of ints :param right_thickness: Line thickness. Either an integer to apply for all right lines or a iterable of integers, one for each line. :type right_thickness: int or iterable of ints :param background_color: The name in a str (as understood by QtGui.QColor(), see :ref:`colors-section` section for possible values) or a string with a hex value e.g. '#101010' that should be used as the background color. :type background_color: str """ super(QwtPlot, self).__init__(parent) self.colors = Colors() self._curves = {} # Gather all plots all_plots = list(left_plotlist) if right_plotlist is not None: all_plots += list(right_plotlist) # Input checks message = self._init_check_left(left_plotlist, kwargs) # Check number of right labels, colors and thickness message = message or self._init_check_right(right_plotlist, kwargs) # Check legend message = message or self._init_check_legends_plots(all_plots, kwargs) if message is not None: raise ValueError(message) # Set background color self._init_background(kwargs) # Form curves for the left axis self._init_left_curves(left_plotlist, kwargs) # Init the right axis and curves on it if right_plotlist is not None: self._init_right_curves(right_plotlist, kwargs) # Set log scales self._init_logscales(left_log, right_log, right_plotlist) # Title, xis labels and legends self._init_title_label_legend(right_plotlist, kwargs)
@staticmethod def _init_check_left(left_plotlist, kwargs): """Check input related to the left curves""" message = None if not len(left_plotlist) > 0: message = 'At least one item in left_plotlist is required' # Check number of left labels and colors for kwarg in ['left_labels', 'left_colors']: if kwargs.get(kwarg) is not None and\ len(left_plotlist) != len(kwargs.get(kwarg)): message = 'There must be as many items in \'{}\' as there '\ 'are left plots'.format(kwarg) # Check left thickness if it is a list if kwargs.get('left_thickness') is not None and\ isinstance(kwargs['left_thickness'], collections.Iterable) and\ len(left_plotlist) != len(kwargs['left_thickness']): message = '\'left_thickness\' must either be an int or a iterable'\ ' with as many ints as there are left plots' return message @staticmethod def _init_check_right(right_plotlist, kwargs): """Check input related to the right curves""" message = None if right_plotlist is not None: for kwarg in ['right_labels', 'right_colors']: if kwargs.get(kwarg) is not None and\ len(right_plotlist) != len(kwargs.get(kwarg)): message = 'There must be as many items in \'{}\' as '\ 'there are right plots'.format(kwarg) if kwargs.get('right_thickness') is not None and\ isinstance(kwargs['right_thickness'], collections.Iterable)\ and len(right_plotlist) != len(kwargs['right_thickness']): message = '\'right_thickness\' must either be an int or a'\ ' iterable with as many ints as there are left plots' return message @staticmethod def _init_check_legends_plots(all_plots, kwargs): """Check legend name and for duplicate plot names""" message = None if kwargs.get('legend') is not None and not kwargs['legend'] in\ ['left', 'right', 'bottom', 'top']: message = 'legend must be one of: \'left\', \'right\', '\ '\'bottom\', \'top\'' # Check for duplicate plot names for plot in all_plots: if all_plots.count(plot) > 1: message = 'Duplicate codename {} not allowed'.format(plot) return message def _init_background(self, kwargs): """Init the background color of the graph""" if kwargs.get('background_color') is not None: self.setCanvasBackground( QtGui.QColor(kwargs['background_color'])) def _init_left_curves(self, left_plotlist, kwargs): """Init the curves on the left axis""" for index, plot in enumerate(left_plotlist): label = plot if kwargs.get('left_labels') is not None: label = kwargs['left_labels'][index] curve = Qwt.QwtPlotCurve(label) if kwargs.get('left_colors') is not None: color = kwargs['left_colors'][index] else: color = self.colors.get_color() if kwargs.get('left_thickness') is not None: if isinstance(kwargs['left_thickness'], collections.Iterable): thickness = kwargs['left_thickness'][index] else: thickness = kwargs['left_thickness'] curve.setPen(Qt.QPen(QtGui.QColor(color), thickness)) else: curve.setPen(Qt.QPen(QtGui.QColor(color))) curve.attach(self) self._curves[plot] = curve def _init_right_curves(self, right_plotlist, kwargs): """Init the right y axis and the curves on it""" self.enableAxis(QwtPlot.yRight) # Form the right axis curves for index, plot in enumerate(right_plotlist): label = plot if kwargs.get('right_labels') is not None: label = kwargs['right_labels'][index] curve = Qwt.QwtPlotCurve(label) curve.setYAxis(QwtPlot.yRight) if kwargs.get('right_colors') is not None: color = kwargs['right_colors'][index] else: color = self.colors.get_color() curve.setPen(Qt.QPen(QtGui.QColor(color))) if kwargs.get('right_thickness') is not None: if isinstance(kwargs['right_thickness'], collections.Iterable): thickness = kwargs['right_thickness'][index] else: thickness = kwargs['right_thickness'] curve.setPen(Qt.QPen(QtGui.QColor(color), thickness)) else: curve.setPen(Qt.QPen(QtGui.QColor(color))) curve.attach(self) self._curves[plot] = curve def _init_logscales(self, left_log, right_log, right_plotlist): """Init log scales""" if left_log: self.setAxisScaleEngine(QwtPlot.yLeft, Qwt.QwtLog10ScaleEngine()) if right_log and (right_plotlist is not None): self.setAxisScaleEngine(QwtPlot.yRight, Qwt.QwtLog10ScaleEngine()) def _init_title_label_legend(self, right_plotlist, kwargs): """Init the title, axis labels and legends""" if kwargs.get('legend') is not None: legend_name = kwargs['legend'].title() + 'Legend' self.insertLegend(Qwt.QwtLegend(), getattr(Qwt.QwtPlot, legend_name)) if kwargs.get('title') is not None: self.setTitle(kwargs['title']) if kwargs.get('xaxis_label') is not None: self.setAxisTitle(Qwt.QwtPlot.xBottom, kwargs['xaxis_label']) if kwargs.get('yaxis_left_label') is not None: self.setAxisTitle(Qwt.QwtPlot.yLeft, kwargs['yaxis_left_label']) if kwargs.get('yaxis_right_label') is not None and \ right_plotlist is not None: self.setAxisTitle(Qwt.QwtPlot.yRight, kwargs['yaxis_right_label'])
[docs] def update(self, data): """Update the plot with new values and possibly move the xaxis :param data: The data to plot. Should be a dict, where keys are plot code names and values are data series as an iterable of (x, y) iterables. E.g. {'plot1': [(1, 1), (2, 2)]} :type data: dict """ for key, dataseries in data.items(): if len(dataseries) > 0: values_array = np.array(dataseries) self._curves[key].setData(values_array[:, 0], values_array[:, 1]) self.replot()