Source code for otestpoint.labtools.views.line

#
# Copyright (c) 2016-2017 - Adjacent Link LLC, Bridgewater, New Jersey
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
#  * Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#  * Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in
#    the documentation and/or other materials provided with the
#    distribution.
#  * Neither the name of Adjacent Link LLC nor the names of its
#    contributors may be used to endorse or promote products derived
#    from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#

import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
import pandas as pd
import traceback
import time

class Line(object):
[docs] class Plot(object): def __init__(self,*columns,**kwargs): """Creates a Line Plot. A line plot is a strip chart of one or more time-series measurments. Args: columns ([str]): One or more DataFrame column names to plot. Keyword Args: title (str): Plot title. Default: ''. ylim ((min,max)): Plot y-axis limit plot limit. Default: (0,50000). ylabel (str): Plot y-axis label. Default: ''. markers ([str]): One or more DataFrame columns to use as markers. A marker is a vertical line that indicates an event occurred. A marker is drawn any time the indicated DataFrame column value is non-zero and non-nan. Default: []. indicators ([str]): One or more DataFrame columns to use as indicators. An indicator is a horizontal bar that indicates a condition is met. An indicator is continuously drawn for time stamps where the indicated DataFrame column value is non-zero and non-nan. Default: []. legend (bool): Flag indicating whether a plot legend should be displayed. Default: True. Raises: KeyError: If an invalid keyword is found. """ self.columns = columns self.title = kwargs.pop('title','') self.ylim = kwargs.pop('ylim',(0,50000)) self.yticks = kwargs.pop('yticks',None) self.ylabel = kwargs.pop('ylabel','') self.markers = kwargs.pop('markers',[]) self.indicators = kwargs.pop('indicators',[]) self.legend = kwargs.pop('legend',True) if not hasattr(self.markers ,'__iter__'): self.markers = [self.markers] if not hasattr(self.indicators ,'__iter__'): self.indicators = [self.indicators] if kwargs: raise KeyError("%s unknown key(s): %s" % (self.__class__.__name__, ", ".join(kwargs.keys())))
def __init__(self,model,**kwargs): self._model = model self._last_events = 0 self._title = kwargs.pop('title','') self._interval = kwargs.pop('interval',1000) self._model_data_kwargs = {} self._last_update_time = None for kwarg in kwargs.keys(): if kwarg.startswith('model_'): self._model_data_kwargs[kwarg[6:]] = kwargs.pop(kwarg) if kwargs: raise KeyError("%s unknown key(s): %s" % (self.__class__.__name__, ", ".join(kwargs.keys()))) def show(self,*plots,**kwargs): self.plot(*plots,**kwargs) ani = self.animate() plt.show() def plot(self,*plots,**kwargs): plots = [Line.Plot(*p.columns,**p.kwargs) for p in plots] self._fig = kwargs.pop('figure',None) tight = kwargs.pop('tight',False) self._stale_timeout = kwargs.pop('stale_timeout',10) if kwargs: raise KeyError("%s unknown key(s): %s" % (self.__class__.__name__, ", ".join(kwargs.keys()))) if self._fig == None: self._fig = plt.gcf() self._fig.canvas.set_window_title(self._title) self._fig.patch.set_facecolor('white') plt.style.use('ggplot') self._fig_face_color = self._fig.patch.get_facecolor() # indicate no updates yet self._fig.patch.set_facecolor('#ff8080') self._axes = [] for i in range(1,len(plots)+1): self._axes.append(self._fig.add_subplot(len(plots),1,i)) self._lines = [] self._columns = [] self._mark_columns = [] self._marks = [] self._indicator_columns = [] self._indicators = [] self._indicator_locations = [] for plot,ax in zip(plots,self._axes): ax.set_title(plot.title) ax.set_xlim(0, self._model.stream().cache() - 1) ax.set_ylim(*plot.ylim) if plot.yticks != None: ax.yaxis.set_ticks(plot.yticks) ax.set_ylabel(plot.ylabel) self._mark_columns.append(plot.markers) for column in plot.columns: self._columns.append(column) self._lines.append(ax.plot([],[],label=column,animated=True)[0]) # add markers to the legend using blank plots for index,mcol in enumerate(plot.markers): ax.plot([],[], label=mcol, ls='--', color=plt.rcParams['axes.color_cycle'][index], animated=True) y1,y2 = plot.ylim y_quarter = (y2 - y1) / 4.0 y_locs = np.linspace(y_quarter*2, y_quarter * 3,len(plot.indicators)) for index,column in enumerate(plot.indicators): self._indicator_locations.append( y_locs[index]) self._indicator_columns.append(column) self._indicators.append(ax.plot([],[], label=column, lw=4, color=plt.rcParams['axes.color_cycle'][index], animated=True)[0]) ax.set_xticks(range(0, self._model.stream().cache())) ax.set_xticklabels([""] * self._model.stream().cache()) if plot.legend: ax.legend() if tight: self._fig.tight_layout() def animate(self): def _init(): for line in self._lines: line.set_data([], []) return self._lines def _animate(i): data,events,_ = self._model.data(**self._model_data_kwargs) if self._last_events != events: if self._last_update_time == None: self._fig.patch.set_facecolor(self._fig_face_color) self._fig.canvas.draw() self._last_update_time = time.time() else: if self._last_update_time != None and \ time.time() - self._last_update_time > self._stale_timeout: self._fig.patch.set_facecolor('#ff8080') self._fig.canvas.draw() self._last_update_time = None if not data.empty and events != self._last_events: try: while self._marks: self._marks.pop(0).remove() for line,column in zip(self._lines,self._columns): y = data[column] x = range(0,len(y)) line.set_data(x, y) for ax,mcols in zip(self._axes,self._mark_columns): for index,mcol in enumerate(mcols): for x,y in enumerate(data[mcol]): if pd.notnull(y) and y > 0: self._marks.append(ax.axvline(x, ls='--', animated=True, label=mcol, color=plt.rcParams['axes.color_cycle'][index])) for indicator,column,location in zip(self._indicators,self._indicator_columns,self._indicator_locations): y = [location if (pd.notnull(val) and val > 0) else np.nan for val in data[column]] x = range(0,len(y)) indicator.set_data(x, y) except: traceback.print_exc() self._last_events = events return self._lines + self._marks + self._indicators return animation.FuncAnimation(self._fig, _animate, init_func=_init, interval=self._interval, blit=True)