Source code for src.gui.timeseries_canvas

# ============================================================================#
# File: timeseries_canvas.py                                                  #
# Author: Pfesesani V. van Zyl                                                #
# ============================================================================#

# Standard library imports
# --------------------------------------------------------------------------- #
import sys, os
from matplotlib.backends.backend_qt5agg import (
    FigureCanvasQTAgg as FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
from matplotlib.figure import Figure
from matplotlib.lines import Line2D
#import matplotlib.style as mplstyle
#mplstyle.use('fast')
import matplotlib.pyplot as plt
import webbrowser
import numpy as np
import pandas as pd
import datetime as dt
import matplotlib.dates as mdates ## Import required library

# Local imports
# --------------------------------------------------------------------------- #
# sys.path.append("src/")
from common.msgConfiguration import msg_wrapper
sys.path.append("plots/")
# =========================================================================== #

[docs] class TimeCanvas(FigureCanvas): def __init__(self, parent=None, wid=10, hgt=8,dpi=100,plotName="",log=""): """ Initialize an empty canvas for all plots. """ if log == "": self.log = print else: self.log = log self.previous_point=[] # Create a figure object fig = Figure(figsize=(wid, hgt), facecolor='w', edgecolor='k',dpi=dpi) # A canvas must be manually attached to the figure (pyplot would automatically # do it). This is done by instantiating the canvas with the figure as # argument. # Create an axes object in the figure self.ax = fig.add_subplot(111) # Initialize the figure onto the canvas FigureCanvas.__init__(self, fig) self.fig=fig self.setParent(parent) # Setup clicks array to hold selected positions self.reset_lists() # set x,y self.x=[] self.y=[] self.xlab=[] self.ylab=[]
[docs] def reset_lists(self): """ Setup lists to store data for the click_index and fit_points. click_index : the indeces of the points clicked on the figure. fit_points: the values of the click_index """ msg_wrapper("debug", self.log.debug, "Reset fit points") self.click_index = [] # Indeces of clicked points along x-axis self.fit_points = [] # Points to be fit/modelled
[docs] def plot_fig_errs(self, x=[], y=[], xlab="", ylab="", title="Main database plot window", col="C0.", errs=[],data=""): """ Plot a figure with errorbars. A helper function to make a graph Parameters ---------- ax : Axes The axes to draw to data1 : array The x data data2 : array The y data param_dict : dict Dictionary of kwargs to pass to ax.plot Returns ------- out : list list of artists added """ self.data=data # drift scan data if len(x) == 0: self.clear_figure() self.setLabelsNoLegend(self.ax, title) else: self.clear_figure() plt.xticks(rotation=70) self.x=x self.y=y self.xlab=xlab self.ylab=ylab self.ax.errorbar(x, y, yerr=errs,fmt=col, label="data", picker=5) self.setLabels(self.ax, xlab, ylab, title) self.mpl_connect('pick_event', self.onpick) self.draw()
[docs] def replaceitem(self,x): '''replace an item in a list of items''' if x == None: # print('*',x) return np.nan elif x == 'None': # print('%',x) return np.nan elif x == 'NaT': # print('+',x) return np.nan elif x =='': return np.nan else: # print('&',x) return float(x)
[docs] def replaceDateItem(self,x): '''replace an item in a list of items''' if type(x).__name__ == 'NaTType': # print('*',x) return np.nan elif x == 'None': # print('%',x) return np.nan elif x == 'NaT': # print('+',x) return np.nan else: # print('&',x) return float(x)
[docs] def plot_fig(self, x=[], y=[], xlab="", ylab="", title="Main database plot window", col="C0.", data="",yerr=[]): # plot_fig(self.df[xCol],self.df[yCol],xCol,yCol,data=self.df,yerr=yerr) """ Plot a figure. A helper function to make a graph Parameters ---------- ax : Axes The axes to draw to data1 : array The x data data2 : array The y data param_dict : dict Dictionary of kwargs to pass to ax.plot Returns ------- out : list list of artists added """ # self.x self.data=data # drift scan data # print(type(x)) # sys.exit() if len(x) == 0: self.clear_figure() self.setLabelsNoLegend(self.ax, title) else: self.clear_figure() plt.xticks(rotation=45) self.x=x self.y=y self.xlab=xlab self.ylab=ylab if len(yerr)==0 : self.ax.plot(self.x, self.y, col, label="data", picker=5) else: # print('yerr - ',list(yerr)) yerr=list(map(self.replaceitem,yerr)) self.y=list(map(self.replaceitem,self.y)) try: self.ax.errorbar(self.x, self.y, yerr,fmt=col, label="data", picker=5) # self.ax.errorbar(data[xlab], data[ylab], data[yerr],fmt=col, label="data", picker=5) except ValueError as e: print(e) # self.ax.errorbar(self.x, self.y, yerr,fmt=col, label="data", picker=5) self.setLabels(self.ax, xlab, ylab, title) # self.ax.tick_params(axis='x',labelrotation=45) ## Added code here # now = dt.datetime.now() # for i, d in enumerate([360, 30, 7, 1]): # # self.ax = axes.flatten()[i] # # earlycut = now - relativedelta(days=d) # # data = df.loc[df.index>=earlycut, :] # # self.ax.plot(data.index, data['value']) # self.ax.xaxis.set_minor_locator() get_xaxis().set_minor_locator(self.mpl.ticker.AutoMinorLocator()) # self.ax.get_yaxis().set_minor_locator(self.mpl.ticker.AutoMinorLocator()) # self.ax.grid( )#which='major', color='w', linewidth=1.5) # self.ax.grid(b=True, which='minor', color='w', linewidth=0.75) # plt.setp(self.ax.get_xticklabels(), rotation=30, horizontalalignment='right') # if len(self.y) <=30: # pass # else: months = mdates.MonthLocator(bymonth=(1,7))#interval=6, bymonthday=-1) ## 6 months apart & show last date self.ax.xaxis.set_major_locator(months) ## Set months as major locator self.ax.xaxis.set_minor_locator(mdates.MonthLocator()) # TODO: create a button to toggle grid on/off # TODO: create button to change grid color, line width etc # TODO: Button to toggle step change locations on/off self.ax.grid(True,alpha=0.2)## Set months as minor locator # self.ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d')) ##Display format - update here to change # self.ax.tick_params(axis='x',labelrotation=20) ##Adjust angle and horizontal align right # # plt.show() connection_id_0=self.mpl_connect('pick_event', self.onpick) connection_id = self.mpl_connect('button_press_event', self.onclick) #self.mpl_connect('pick_event', self.onpick) # rotate ticks self.fig.autofmt_xdate() self.fig.tight_layout() self.draw() self.draw()
[docs] def plot_dual_fig(self, x=[], y=[],x1=[],y1=[], xlab="", ylab="", title="Main database plot window", col="C0.", data=""): """ Plot a figure. A helper function to make a graph Parameters ---------- ax : Axes The axes to draw to data1 : array The x data data2 : array The y data param_dict : dict Dictionary of kwargs to pass to ax.plot Returns ------- out : list list of artists added """ self.data=data # drift scan data if len(x) == 0: self.clear_figure() self.setLabelsNoLegend(self.ax, title) else: self.clear_figure() plt.xticks(rotation=45) self.x=x self.y=y self.xlab=xlab self.ylab=ylab self.ax.plot(x, y, col, label="data", picker=5) self.ax.plot(x1, y1, 'r', label="fit") self.setLabels(self.ax, xlab, ylab, title) #connection_id_0=self.mpl_connect('pick_event', self.onpick) #connection_id = self.mpl_connect('button_press_event', self.onclick) #self.mpl_connect('pick_event', self.onpick) self.draw()
[docs] def clear_figure(self): """ Clear the current figure """ self.ax.cla() # .gcf().clear() # which clears data and axes self.draw()
[docs] def onclick(self,event): #print(event, "\n") if event.xdata==None: print('\nNothing selected, aim for the tiny blue dots ;)\n') else: #print(len(self.fit_points)) if len(self.fit_points) == 0 or len(self.click_index) == 0: print("\nNo points selected, try aiming for the tiny blue dots\n") else: print("in\n") if len(self.click_index) == 1 and len(self.fit_points) == 1: print(f'Clicked the {event.button} button') if event.dblclick: print('double clicked - ') print(self.click_index) print(f'Clicked: {event.button}\n') print(f'x: {self.fit_points[0][0]}, \ny: {self.fit_points[0][1]}') print('ind: ',self.click_index,'\n') elif event.button == 1 or event.button == "MouseButton.LEFT": print(self.previous_point, self.fit_points[0][0], self.fit_points[0][1]) if len(self.previous_point)==1: print('test') print(self.previous_point[0][0]==self.fit_points[0][0], self.previous_point[0][1]==self.fit_points[0][1]) cond=(self.previous_point[0][0]==self.fit_points[0][0]) if cond == True: print('\naim for blue dots\n') self.fit_points=[] self.click_index=[] else: print('left - ') #self.fitpoints=[] #self.click_index=[] print(f'Clicked: {event.button}\n') print(f'x: {self.fit_points[0][0]}, \ny: {self.fit_points[0][1]}') print('ind: ',self.click_index,'\n') self.previous_point=[[self.fit_points[0][0],self.fit_points[0][1]]] else: #self.previous_point=[[self.fit_points[0][0],self.fit_points[0][1]]] print('left - first time') #self.fitpoints=[] #self.click_index=[] print(f'Clicked: {event.button}\n') print(f'x: {self.fit_points[0][0]}, \ny: {self.fit_points[0][1]}') print('ind: ',self.click_index,'\n') self.previous_point=[[self.fit_points[0][0],self.fit_points[0][1]]] elif event.button == 3: if self.click_index==[]: print('No click index') else: print(f'Clicked: {event.button}\n') # Points clicked to plot self.ax.plot(self.fit_points[0][0], self.fit_points[0][1], 'r.') if len(self.click_index)==1: self.show_plots(self.click_index[0]) else: self.show_plots(self.click_index[-1]) #sys.exit() else: print("Not sure what happend\n") else: print('click index: ',len(self.click_index),self.click_index)
[docs] def onpick(self, event): """ Click event handler.""" if isinstance(event.artist, Line2D): thisline = event.artist xdata = thisline.get_xdata() ydata = thisline.get_ydata() ind = event.ind #print(xdata[ind],ydata[ind],ind,event) #sys.exit() pointsx = xdata[ind] pointsy = ydata[ind] # Points clicked to plot #self.ax.plot(pointsx[0], pointsy[0], 'r.') try: print("\n","-"*30) print(f'picked: {str(pointsx[0])[:10]},{pointsy[0]} @ index: {ind[0]}') except: print("Require a numerical value to print points picked\n") self.fit_points=[] self.click_index=[] self.fit_points.append([pointsx[0],pointsy[0]]) self.click_index.append(ind[0]) self.draw()
[docs] def show_plots(self,ind): """ Show plots on click event in html browser. """ obj=self.data.iloc[ind,]['OBJECT'] obj=obj.replace(' ','') freq=int(self.data.iloc[ind,]["CENTFREQ"]) imgDir = f"plots/{obj}/{freq}" print(imgDir) # sys.exit() # plotFolderList = os.listdir(imgDir) # plotFolderName = self.data.iloc[ind,]["FILEPATH"] plotFileName = (self.data.iloc[ind,]["FILENAME"]) plotMJD = f'{(self.data.iloc[ind,]["MJD"]):.1f}' plotDATE = str(self.data.iloc[ind,]["OBSDATE"])[:10] imgPaths=[] imgTags=[] imageNames = sorted(os.listdir(imgDir)) if len(imageNames) == 0: print("We are missing images for this source, try automatically processing the data first") else: print() print('Showing: ') for i in range(len(imageNames)): if imageNames[i][:18] == plotFileName[:18]: print("- ",imgDir+"/"+imageNames[i]) imgPaths.append(imgDir+"/"+imageNames[i]) imgTags.append((imageNames[i][19:-4])) print('-'*30,'\n') # print(imgPaths) htmlstart = '<html> <head>\ <meta charset = "utf-8" >\ <meta name = "viewport" content = "width=device-width, initial-scale=1" > <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous"> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/js/bootstrap.bundle.min.js" integrity="sha384-b5kHyXgcpbZJO/tY9Ul7kGkf1S0CWuKcCD38l8YkeH8z8QjE0GmW1gYU5S9FOnJ0" crossorigin="anonymous"></script> <style> img {border: 2px solid #ddd; /* Gray border */border-radius: 4px; /* Rounded border */padding: 5px; /* Some padding */width: 400px; /* Set a small width */}/* Add a hover effect (blue shadow) */img:hover {box-shadow: 0 0 2px 1px rgba(0, 140, 186, 0.5);}</style> \ <title>Plots</title>\ </head>\ <div class="container-fluid"> \ <div class="row">\ <hr>\ <h3> Plots of '+plotFileName[:18]+' ( MJD '+str(plotMJD)+' or '+plotDATE+') for '+plotFileName + ' at '+str(freq)+ ' </h3> <p>' htmlmid='' for i in range(len(imgPaths)): pathtoimg=imgPaths[i] img = '<h5 class="card-title">'+imgTags[i]+'</h5><br/>\ <a target="_blank" href="'+pathtoimg + \ '"><img src="'+pathtoimg + \ '" class="card-img-top" alt="image goes here"></a>' imglink ='<div class = "card" style = "width: 18rem;" >\ '+img+'\ </div>' htmlmid=htmlmid+imglink htmlmid=htmlmid+'</p></div>' htmlend = '</div></html>' html = htmlstart+htmlmid+htmlend # create the html file path = os.path.abspath('temp.html') url = 'file://' + path with open(path, 'w') as f: f.write(html) webbrowser.open(url)
#---------------------------------------------------------------------------
[docs] def pop(self): """ """ print("Ask to remove: ", self.img_loc[self.click_index[-1]])
#img = Image.open("plots/"+self.img_loc[self.click_index[-1]]) #plt.imshow(img) # show image of plot #pl.plot() #fig = Figure(figsize=(100, 100), facecolor='w', edgecolor='k') # A canvas must be manually attached to the figure (pyplot would automatically # do it). This is done by instantiating the canvas with the figure as # argument. # Create an axes object in the figure #ax = fig.add_subplot(111) #app = QApplication(sys.argv) #sys.exit(app.exec_()) #img = Image.open("plots/"+self.img_loc[self.click_index[-1]]) #plt.imshow(img) # Initialize the figure onto the canvas #self.toolbar = NavigationToolbar(self,fig)
[docs] def dday(self, x, y): """ Date handler """ #d1 = x[0] #d2 = x[-1] #print(d1,d2) #string = d1#"19 Nov 2015 18:45:00.000" #date = datetime.datetime.strptime(string, "%Y-%m-%d")# %H:%M:%S.%f") #print(date, date.year, date.month, date.day) #sys.exit() #date1 = datetime.date(2002, 1, 5) #date2 = datetime.date(2003, 12, 1) # every monday mondays = WeekdayLocator(MONDAY) # every 3rd month months = MonthLocator(range(1, 13), bymonthday=1, interval=3) monthsFmt = DateFormatter("%b '%y") dates = x#[q[0] for q in quotes] opens = y#[q[1] for q in quotes] self.clear_figure() #fig, ax = plt.subplots() self.ax.plot_date(dates, opens, '-') self.ax.xaxis.set_major_locator(months) self.ax.xaxis.set_major_formatter(monthsFmt) self.ax.xaxis.set_minor_locator(mondays) self.ax.autoscale_view() #ax.xaxis.grid(False, 'major') #ax.xaxis.grid(True, 'minor') #ax.grid(True) #self.fig.autofmt_xdate() #plt.show() self.draw()
[docs] def setLabelsNoLegend(self, ax, title="My title"): """ Set labels for basic plot. """ ax.set_title(title, fontsize='x-large') # pad=3 ax.set_ylabel("Y-axis", fontsize="medium") ax.set_xlabel("X-axis", fontsize="medium")
[docs] def setLabels(self, ax, xlab, ylab,title="My title"): """ Set labels for basic plot. """ ax.set_title(title, fontsize='x-large') # pad=3 ax.set_ylabel(ylab, fontsize="medium") ax.set_xlabel(xlab, fontsize="medium") ax.legend(loc="best", fontsize="small", fancybox=True, framealpha=0.5)
#if __name__ == '__main__': '''def plotRawData(self, x=[], y=[]): """ Plot raw data """ #zero = np.zeros(len(x)) if len(x) == 0: pass else: self.clear_figure(self.ax2) self.ax2.plot(x, y, 'k', label="Raw data") #plotRawplotRawDataself.ax2.plot(x, zero, 'c') self.ax2.legend(loc="best") self.ax2.set_xlabel("Dist [deg]") self.draw() def resetLists(self): """ Setup lists to store the following data of: """ self.click_index = [] # Indeces of picked points along x-axis self.fit_points = [] # Points to be fit/modelled def plotRes(self, x=[], title="Residuals"): """ Plot the residuals """ self.clear_figure(self.ax1) if len(x) == 0: self.ax1.set_title(title) else: pass # get resid min/max res_min = max(x) res_max = min(x) res_mean = np.mean(x) sd = 3*np.std(x) res_3sdmax = res_mean + sd res_3sdmin = res_mean - sd sdd = np.zeros_like(x) sdmx = np.ones_like(x)*res_3sdmax sdmn = np.ones_like(x)*res_3sdmin # label="res mean +- 3 std" self.ax1.plot(sdd, sdmx) # , fontsize="medium") self.ax1.plot(sdd, sdmn) self.ax1.plot(x) self.ax1.set_xlabel("N", fontsize="medium") self.ax1.set_ylabel("res", fontsize="medium") self.draw() def plotBaseLocs(self, ax, left_locs, right_locs, x, y): """ Add baseline correction locations """ ax.plot(x[left_locs], y[left_locs], "m.") ax.plot(x[right_locs], y[right_locs], "m.") self.draw() def addLineToAxis(self, ax, x, y, lab): """ Add data to axis """ #if t=="--": # ax.plot(x,y,c="b", label=lab) # ax.legend(loc="best") # self.draw() #else: ax.plot(x, y, 'm.', label=lab, lw=1) ax.legend(loc="best") self.draw() #========================================================== def setLegendParms(self): """ Legend parameters. """ legend_parms_fontsize = ['xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large'] legend_parms_loc = ['best', 'upper right', 'upper left', 'lower left', 'lower right', 'right', 'center left', 'center right', 'lower center', 'center'] _facecolor = [] _edgecolor = [] _title = [] _title_fontsize = [] #from matplotlib import rcParams #rcParams['axes.titlepad'] = 20 def setMainFigurecustomization(self): """ Customize the main figure look. """ pass """plt.rc('font', family = 'serif') plt.rc('legend', fontsize='small') plt.rc('xtick', color='red', labelsize = 'x-small') plt.rc('xtick', color='r', labelsize='medium', direction='out') plt.rc('xtick.major', size=4, pad=4) plt.rc('xtick.minor', size=2, pad=4)""" #xtick.major.size : 4 # major tick size in points #xtick.minor.size : 2 # minor tick size in points #xtick.major.pad : 4 # distance to major tick label in points #xtick.minor.pad : 4 # distance to the minor tick label in points #xtick.color : r # color of the tick labels #xtick.labelsize : medium # fontsize of the tick labels #xtick.direction : out # direction: in or out #plt.rcdefaults # To reset rc parameters def setResidualFigurecustomization(self): """ Customize the residual figure look. """ pass def setTimeseriesFigurecustomization(self): """ Customize the residual figure look. """ pass def setTicks(self): """ Set tick properties for the figure. """ for tick in self.ax.xaxis.get_ticklabels(): tick.set_fontsize('medium') tick.set_fontname('serif') # Times New Roman') tick.set_color('blue') tick.set_weight('bold') def plotPeak(self, xp, yp, x=[], y=[], title="Basic plot window"): self.fit_points = [] # List of points selected for fitting self.click_index = [] # List of Index of point selected for fitting if len(x) == 0: print("\n-- This plot has no data") #data = [random.random() for i in range(25)] #self.ax.plot(data, 'b') else: self.ax.plot(x, y, 'b', label="data", picker=1) self.ax.plot(xp, yp, 'r', label='fit') # self.ax.set_title(title) self.ax.set_ylabel("Ta [K]") self.ax.set_xlabel("Dist [Deg]") self.ax.legend(loc="best") #Cursor(self.ax, useblit=True, color='red', linewidth=1) #self.mpl_connect('pick_event', self.onpick1) self.draw() def plotNoPick(self, ax, res): """ Plot the residual. """ #ax.set_ylabel("Res") #ax.set_xlabel("N") ax.plot(res) ax.draw()'''