Source code for scan_framework.applets.plot_xy

#!/usr/bin/env python3.5
import scan_framework.applets.plot as parent
import pyqtgraph
import PyQt5
import numpy as np


[docs]class SimpleApplet(parent.SimpleApplet):
[docs] def add_datasets(self): # plot title self.add_dataset("title", 'plot title', required=False) self.add_dataset("rid", "RID for experiment", required=False) # x/y data self.add_dataset("y", "Y values") self.add_dataset("y2", "Y2 values", required=False) self.add_dataset("trigger", "", required=False) self.add_dataset('i_plot', "", required=False) self.add_dataset("x", "X values", required=False) # error self.add_dataset("error", "Error bars for each X value", required=False) # fit self.add_dataset("fit", "Fit values for each X value", required=False) # axes self.add_dataset("x_label", 'x-axis label', required=False) self.add_dataset("x_units", 'x-axis units', required=False) self.add_dataset("x_scale", 'x-axis scale', required=False) self.add_dataset("y_scale", 'y-axis scale', required=False) self.add_dataset("y_label", 'y-axis label', required=False) self.add_dataset("y_units", 'y-axis label', required=False)
[docs]class XYPlot(parent.Plot): """Applet for plotting X-Y data. A single trace is plotted by providing two 1D arrays for the x and y values. Multiple traces can be plotted by providing two 2D arrays for the x and y values. The plot style can be customized by modifiying the `style` attribute. When plotting multiple traces, each trace uses a separate symbol for its data points defined by :code:`style['plot']['symbol']`. **Default Styles** - **style['background']['color']** white - **style['foreground']['color']** black - **style['fit']['pen']** fitline color is red with a width of 4 - **style['plot']['symbol']** data point symbols for each trace. Order is o, t, d, s, d which repeats for >5 traces. - **style['plot']['size']** data point symbol sizes for each trace. Order is 10, 12, 15, 10, 10 which repeats for >5 traces. - **style['plot']['color']** data point symbol colors for each trace. Order is r, b, g, m, c, y which repeats for >5 traces. - **style['plot']['axes']['size']** axes size defaults to 15 - **style['plot']['axes']['tick_offset']** tick offset defaults to 30 - **style['plot']['axes']['label']['font-size']** axes label font sizes default to 15pt - **style['plot']['axes']['label']['bold']** axes label fonts are bold by default - **style['title']['size']** plot title size defaults to 20px """ style = { 'background': { 'color': 'w' }, 'foreground': { 'color': 'k' }, 'fit': { 'pen': pyqtgraph.mkPen(color='r', width=4) }, 'plot': { 'symbol': ['o', 't', 'd', 's', 'd'], 'size': [10, 12, 15, 10, 10], 'color': ['r', 'b', 'g', 'm', 'c', 'y'], 'pen': None, 'symbol2': ['t', 'd', 's', 'd', 'o'], 'size2': [12, 15, 10, 10, 10], 'color2': ['b', 'g', 'm', 'c', 'y', 'r'], 'pen2': None }, 'axes': { 'size': 15, 'tick_offset': 30, 'label': { 'font-size': '15pt', "font-bold": "True" } }, 'title': { 'size': '20px' } } #: Specifies the style of the plot. started = False
[docs] def load(self, data): # don't plot if not triggered self._load(data, 'trigger', default=1, ds_only=False) if self.started and not self.trigger: return False # defaults self.x_scale = 1 self.y_scale = 1 self.fit = None self.error = None self.x_label = None self.y_label = None self.i_plot = None """Load the data from datasets""" # load dataset values self._load(data, ['title', 'x_label', 'y_label', 'x_units', 'y_units']) self._load(data, 'x_scale', default=1) self._load(data, 'y_scale', default=1) self._load(data, ['x', 'y'], default=None) self._load(data, 'y2', default=None) self._load(data, 'i_plot', default=None) self._load(data, 'fit', default=None) self._load(data, 'error', default=None) self._load(data, 'rid', default=None) # don't plot if not triggered if self.started and not self.trigger: return False self.started = True
# default value for x is index values #if self.x is None: # self.x = [_ for _ in itertools.product(*[range(self.shape[0]), range(self.shape[1])])]
[docs] def clean(self): """Clean the data so it can be plotted""" # format data so plots also work with 1D arrays if len(self.y.shape) == 1: self.y = np.array([self.y]) self.y2 = np.array([self.y2]) self.x = np.array([self.x]) if self.fit is not None: self.fit = np.array([self.fit]) if self.error is not None: self.error = np.array([self.error]) if self.x_scale is not None: self.x = self.x / self.x_scale if self.y_scale is not None: self.y = self.y / self.y_scale if self.error is not None: self.error /= self.y_scale if self.fit is not None: self.fit = self.fit / self.y_scale
[docs] def validate(self): """Validate that the data can be plotted""" try: if not self.y.shape == self.x.shape: print("plot_xy applet: x and y shapes don't agree") return False if self.fit is not None and not self.fit.shape == self.x.shape: print("plot_xy applet: x and fit shapes don't agree") return False except AttributeError: return False
[docs] def draw(self): """Plot the data""" # dimensions of the data shape = self.y.shape # draw title style = self.style['title'] if self.rid is None: title = self.title else: title = "RID {}: {}".format(self.rid, self.title) self.setTitle(title, size=style['size']) # draw data if self.i_plot is not None: self.draw_series(self.i_plot) else: for i in range(shape[0]): self.draw_series(i) # draw axes axis_font = PyQt5.QtGui.QFont() axis_font.setPixelSize(self.get_style('axes.size')) # draw x axis x_axis = self.getAxis('bottom') x_axis.tickFont = axis_font # somehow tickTextOffset necessary to change tick font x_axis.setStyle(tickTextOffset=self.get_style('axes.tick_offset')) x_axis.enableAutoSIPrefix(False) if self.x_label is not None: self.setLabel('bottom', self.x_label, units=self.x_units, **self.get_style("axes.label")) # draw y axis y_axis = self.getAxis('left') y_axis.tickFont = axis_font y_axis.setStyle(tickTextOffset=self.get_style('axes.tick_offset')) if self.y_label is not None: self.setLabel('left', self.y_label, units=self.y_units, **self.get_style("axes.label"))
[docs] def draw_series(self, i): x = self.x[i] y = self.y[i] y2 = None if self.y2 is not None: y2 = self.y2[i] # don't draw if all values are nan if not np.isnan(y).all(): # style brush = pyqtgraph.mkBrush(color=self.get_style('plot.color', i)) symbol = self.get_style('plot.symbol', i) size = self.get_style('plot.size', i) pen = self.get_style('plot.pen') # plot self.plot(x, y, pen=pen, symbol=symbol, size=size, symbolBrush=brush) # style 2 brush2 = pyqtgraph.mkBrush(color=self.get_style('plot.color2', i)) symbol2 = self.get_style('plot.symbol2', i) size2 = self.get_style('plot.size2', i) pen2 = self.get_style('plot.pen2') # plot y2 if y2 is not None: self.plot(x, y2, pen=pen2, symbol=symbol2, size=size2, symbolBrush=brush2) # draw fit if self.fit is not None: fit = self.fit[i] if fit is not None: # style pen = self.get_style('fit.pen') self.plot(x, fit, pen=pen) # draw error if self.error is not None: error = self.error[i] if not np.isnan(error).all(): # See https://github.com/pyqtgraph/pyqtgraph/issues/211 if hasattr(error, "__len__") and not isinstance(error, np.ndarray): error = np.array(error) if len(error) == len(x): errbars = pyqtgraph.ErrorBarItem( x = np.array(x), y = np.array(y), height = 2 * error) self.addItem(errbars)
[docs]def main(): applet = SimpleApplet(XYPlot) applet.run()
if __name__ == "__main__": main()