From 2a3f48c56d7de55e429640f4f3ce7ccbdc77268c Mon Sep 17 00:00:00 2001 From: Elena Shoushpanova Date: Wed, 20 May 2020 19:34:53 -0700 Subject: [PATCH 1/2] Added a "zoom" window functionality that will show interactive plotly graph --- OPV_wx_GUI.py | 114 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 98 insertions(+), 16 deletions(-) diff --git a/OPV_wx_GUI.py b/OPV_wx_GUI.py index a22126c..83d863a 100644 --- a/OPV_wx_GUI.py +++ b/OPV_wx_GUI.py @@ -4,6 +4,9 @@ from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas from matplotlib.figure import Figure from os import listdir +from PyQt5 import QtWebEngineWidgets +from PyQt5.QtCore import QUrl +from PyQt5.QtWidgets import QApplication from scipy import stats from scipy.interpolate import interp1d from scipy.optimize import fmin @@ -21,6 +24,7 @@ import os import pandas as pd import plotly.graph_objs as go +import plotly.offline import sys import wx import wx.html @@ -28,6 +32,7 @@ import wx.lib.scrolledpanel as scrolled matplotlib.use('WXAgg') + # This is a text to be used in a "About" menu button: aboutText = "

This program is a part of Luscombe Group OPV Analysis project \ in University of Washington, Seattle. It is running on version %(wxpy)s \ @@ -46,7 +51,10 @@ def __init__(self, parent, data, vals, number): self.SetMinSize((500, 350)) self.SetBackgroundColour("#b7a57a") - self.num = number # This will serve as an index for our plots + self.number = number # This will serve as an index for our plots + global panel_data + panel_data = data + self.vals = vals # Defining data for plots: PCE = vals[0] @@ -79,7 +87,8 @@ def __init__(self, parent, data, vals, number): plt.subplots_adjust(bottom=0.18) # This is the JV curve drawn from the actual data: - self.axes.plot(data[:, 0], data[:, 2], linewidth=2, zorder=4) + self.axes.plot(data[:, 0], data[:, 2], linewidth=2, + marker='o', markersize=5, zorder=4) # This is to highlight x-Axis on the plot area: self.axes.plot([-0.2, 0.8], [0, 0], color='k', linestyle='-', @@ -109,25 +118,68 @@ def __init__(self, parent, data, vals, number): # Defining sizer: self.sizer = wx.BoxSizer(wx.VERTICAL) + self.hsizer = wx.BoxSizer(wx.HORIZONTAL) # Defining the whole plotting area: self.canvas = FigureCanvas(self, -1, self.figure) self.sizer.Add(self.canvas, 1, wx.ALL | wx.ALIGN_TOP | wx.EXPAND) + # Defining a plotly plot for zoomed wendow: + self.fig = go.Figure() + self.fig.add_scatter(x=data[:, 0], + y=data[:, 2], + mode='lines+markers', + name='JV Curve', + marker_color='rgba(51, 0, 111, 0.2)', + marker_size=10, + marker_line_width=2) + self.fig.update_layout(title={'text': "JV Curve", + 'x': 0.5, + 'y': 0.95, + 'xanchor': 'center', + 'yanchor': 'top'}, + xaxis=dict(range=[-0.2, 0.8], + showgrid=True, + zeroline=True, + zerolinewidth=2, + zerolinecolor='#4b2e83', + title='Voltage [V]', + linecolor='#4b2e83', + linewidth=2, + gridwidth=1, + gridcolor='#b7a57a'), + yaxis=dict(range=[-5, 20], + showgrid=True, + zeroline=True, + zerolinewidth=2, + zerolinecolor='#4b2e83', + title='Current Density [mA/cm^2]', + linecolor='#4b2e83', + linewidth=2, + gridwidth=1, + gridcolor='#b7a57a'), + font=dict(family='Rockwell', + size=18, + color='#4b2e83'), + paper_bgcolor='gainsboro', + plot_bgcolor='#ffffff') + # Added a checkbox for future including/excluding from calculation # self.checkbox = wx.CheckBox(self, label='Check Box') # self.Bind(wx.EVT_CHECKBOX, self.onChecked) # self.sizer.Add(self.checkbox, 0, wx.ALIGN_RIGHT | wx.ALIGN_BOTTOM) # Added a button for excluding the plot from average: - # self.button = wx.Button(self, -1, "Exclude from average") - # self.button.Bind(wx.EVT_BUTTON, self.OnClicked) - # self.sizer.Add(self.button, 0, wx.ALIGN_RIGHT | wx.ALIGN_BOTTOM) + self.zbutton = wx.Button(self, -1, "Zoom-in") + self.zbutton.Bind(wx.EVT_BUTTON, self.OnZoom) + self.hsizer.Add(self.zbutton, 0, wx.LEFT, 5) # Added a toggle button for including/excluding the plot from average: self.button = wx.ToggleButton(self, -1, "Exclude from average") self.button.Bind(wx.EVT_TOGGLEBUTTON, self.OnClicked) - self.sizer.Add(self.button, 0, wx.ALIGN_RIGHT | wx.ALIGN_BOTTOM) + self.hsizer.Add(self.button, 0, wx.LEFT, 5) + + self.sizer.Add(self.hsizer, 0, wx.ALIGN_RIGHT | wx.ALIGN_BOTTOM) # Finishing the sizer setup: self.SetSizer(self.sizer) @@ -136,6 +188,7 @@ def __init__(self, parent, data, vals, number): # Defined a button def OnClicked(self, event): + print("event is", event) global flags if flags[self.num] == 1: flags[self.num] = 0 @@ -149,6 +202,43 @@ def OnClicked(self, event): # checkbox = event.GetEventObject() # print(checkbox.GetLabel(), ' is clicked', checkbox.GetValue()) + def OnZoom(self, event): + self.win = PlotlyViewer(self.fig) + + +# Genetal class for a pop-up window: +class HtmlWindow(wx.html.HtmlWindow): + def __init__(self, parent, id, size=(600, 400)): + wx.html.HtmlWindow.__init__(self, parent, id, size=size) + if "gtk2" in wx.PlatformInfo: + self.SetStandardFonts() + + def OnLinkClicked(self, link): + wx.LaunchDefaultBrowser(link.GetHref()) + + +# Class that makes the plotly zoom-in chart to appear: +class PlotlyViewer(QtWebEngineWidgets.QWebEngineView): + def __init__(self, fig, exec=True): + # Create a QApplication instance or use the existing one if it exists + self.app = QApplication.instance() if QApplication.instance() \ + else QApplication(sys.argv) + + super().__init__() + + self.file_path = os.path.abspath(os.path.join(os.path.dirname( + __file__), "temp.html")) + plotly.offline.plot(fig, filename=self.file_path, auto_open=False) + self.load(QUrl.fromLocalFile(self.file_path)) + self.setWindowTitle("Zoom-in") + self.show() + + if exec: + self.app.exec_() + + def closeEvent(self, event): + os.remove(self.file_path) + # Created a scrollable panel of 8 panels: class ScrolledPanel(scrolled.ScrolledPanel): @@ -318,15 +408,6 @@ def onClick(self, vals): header='Device, PCE, Voc, Jsc, FF') -# Genetal class for a pop-up window: -class HtmlWindow(wx.html.HtmlWindow): - def __init__(self, parent, id, size=(600, 400)): - wx.html.HtmlWindow.__init__(self, parent, id, size=size) - if "gtk2" in wx.PlatformInfo: - self.SetStandardFonts() - - def OnLinkClicked(self, link): - wx.LaunchDefaultBrowser(link.GetHref()) # A class for a "about" message box: @@ -418,7 +499,8 @@ def __init__(self): topsizer = wx.BoxSizer(wx.HORIZONTAL) # Define "Welcome message" text: - m_text = wx.StaticText(self, -1, "This can be a welcome message") + m_text = wx.StaticText(self, -1, + "Welcome to the OPV JV-curve analyzer!") m_text.SetFont(wx.Font(14, wx.SWISS, wx.NORMAL, wx.BOLD)) m_text.SetSize(m_text.GetBestSize()) m_text.SetForegroundColour('#ffffff') From 27a9ed9e8ebdaa3413ae4c386415583e3784b06b Mon Sep 17 00:00:00 2001 From: Elena Shoushpanova Date: Wed, 20 May 2020 21:35:32 -0700 Subject: [PATCH 2/2] Added functionality for zoom-in plotly graph and fixed average calculation and output file --- OPV_wx_GUI.py | 81 ++++++++++++++++++++++++++------------------------- 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/OPV_wx_GUI.py b/OPV_wx_GUI.py index 83d863a..6e852de 100644 --- a/OPV_wx_GUI.py +++ b/OPV_wx_GUI.py @@ -32,7 +32,6 @@ import wx.lib.scrolledpanel as scrolled matplotlib.use('WXAgg') - # This is a text to be used in a "About" menu button: aboutText = "

This program is a part of Luscombe Group OPV Analysis project \ in University of Washington, Seattle. It is running on version %(wxpy)s \ @@ -51,10 +50,7 @@ def __init__(self, parent, data, vals, number): self.SetMinSize((500, 350)) self.SetBackgroundColour("#b7a57a") - self.number = number # This will serve as an index for our plots - global panel_data - panel_data = data - self.vals = vals + self.num = number # This will serve as an index for our plots # Defining data for plots: PCE = vals[0] @@ -87,8 +83,7 @@ def __init__(self, parent, data, vals, number): plt.subplots_adjust(bottom=0.18) # This is the JV curve drawn from the actual data: - self.axes.plot(data[:, 0], data[:, 2], linewidth=2, - marker='o', markersize=5, zorder=4) + self.axes.plot(data[:, 0], data[:, 2], linewidth=2, zorder=4) # This is to highlight x-Axis on the plot area: self.axes.plot([-0.2, 0.8], [0, 0], color='k', linestyle='-', @@ -124,7 +119,7 @@ def __init__(self, parent, data, vals, number): self.canvas = FigureCanvas(self, -1, self.figure) self.sizer.Add(self.canvas, 1, wx.ALL | wx.ALIGN_TOP | wx.EXPAND) - # Defining a plotly plot for zoomed wendow: + # Defining a plotly plot for zoomed window: self.fig = go.Figure() self.fig.add_scatter(x=data[:, 0], y=data[:, 2], @@ -188,7 +183,6 @@ def __init__(self, parent, data, vals, number): # Defined a button def OnClicked(self, event): - print("event is", event) global flags if flags[self.num] == 1: flags[self.num] = 0 @@ -206,17 +200,6 @@ def OnZoom(self, event): self.win = PlotlyViewer(self.fig) -# Genetal class for a pop-up window: -class HtmlWindow(wx.html.HtmlWindow): - def __init__(self, parent, id, size=(600, 400)): - wx.html.HtmlWindow.__init__(self, parent, id, size=size) - if "gtk2" in wx.PlatformInfo: - self.SetStandardFonts() - - def OnLinkClicked(self, link): - wx.LaunchDefaultBrowser(link.GetHref()) - - # Class that makes the plotly zoom-in chart to appear: class PlotlyViewer(QtWebEngineWidgets.QWebEngineView): def __init__(self, fig, exec=True): @@ -347,9 +330,9 @@ def onClick(self, vals): filename = "output.csv" global flags print(flags) - # Calculating the average of only "included" plots using the - # "numpy dot product" finction to find a dot product between a - # given column and flag values (either 0 or 1): + # Calculating the average of only "included" plots + # Way # 1: using the "numpy dot product" finction to find a dot product + # between a given column and flag values (either 0 or 1): self.vals[8][0] = np.dot([item[0] for item in self.vals][0:8], flags)/sum(flags) self.vals[8][1] = np.dot([item[1] for item in self.vals][0:8], @@ -358,25 +341,18 @@ def onClick(self, vals): flags)/sum(flags) self.vals[8][3] = np.dot([item[3] for item in self.vals][0:8], flags)/sum(flags) - # In order to add a column of device index including the 'average' I am - # converting the data type to string and self.vals to array: - self.vals = np.array(self.vals, dtype=str) - self.vals = np.insert(self.vals, [0], - [['1'], ['2'], ['3'], ['4'], ['5'], ['6'], - ['7'], ['8'], ['Average']], - axis=1) - - ''' - Second option for the same calculation (currently commented out: + + # Way # 2: # First creating zero values for the average. Without this step after # each click and un-click of the "include/exlude" button and export, # the new everage will incorporate not only charts with flag=1, # but also the previous average value: - + """ self.vals[8][0] = 0 self.vals[8][1] = 0 self.vals[8][2] = 0 self.vals[8][3] = 0 + for i in range(0, 8): if flags[i] == 1: self.vals[8][0] += self.vals[i][0] @@ -387,7 +363,19 @@ def onClick(self, vals): self.vals[8][1] /= sum(flags) self.vals[8][2] /= sum(flags) self.vals[8][3] /= sum(flags) - ''' + """ + + # In order to add a column of device index including the 'average' I am + # converting the data type to string and self.vals to array: + self.vals = np.array(self.vals) + self.vals = np.array(self.vals) + if self.vals.shape[1] == 4: + self.vals = np.append(self.vals, + [[1], [2], [3], [4], [5], [6], + [7], [8], [0]], + axis=1) + else: + pass # if sum(flags) != 8: # self.vals[8][0] *= 8 @@ -404,10 +392,24 @@ def onClick(self, vals): # self.vals[8][1] /= sum(flags) # self.vals[8][2] /= sum(flags) # self.vals[8][3] /= sum(flags) - np.savetxt(filename, self.vals, delimiter=",", fmt='%s', - header='Device, PCE, Voc, Jsc, FF') + output = pd.DataFrame(self.vals, + columns=['PCE', 'Voc', 'Jsc', 'FF', 'Device'], + dtype=str) + output['Device'].iloc[8] = 'Average' + output.to_csv(filename) + # np.savetxt(filename, output, delimiter=",", fmt='%s', + # header='PCE, Voc, Jsc, FF, Device') +# Genetal class for a pop-up window: +class HtmlWindow(wx.html.HtmlWindow): + def __init__(self, parent, id, size=(600, 400)): + wx.html.HtmlWindow.__init__(self, parent, id, size=size) + if "gtk2" in wx.PlatformInfo: + self.SetStandardFonts() + + def OnLinkClicked(self, link): + wx.LaunchDefaultBrowser(link.GetHref()) # A class for a "about" message box: @@ -499,8 +501,7 @@ def __init__(self): topsizer = wx.BoxSizer(wx.HORIZONTAL) # Define "Welcome message" text: - m_text = wx.StaticText(self, -1, - "Welcome to the OPV JV-curve analyzer!") + m_text = wx.StaticText(self, -1, "This can be a welcome message") m_text.SetFont(wx.Font(14, wx.SWISS, wx.NORMAL, wx.BOLD)) m_text.SetSize(m_text.GetBestSize()) m_text.SetForegroundColour('#ffffff') @@ -561,4 +562,4 @@ def OnFiles(self, event): app = wx.App(redirect=False) frame = Main() frame.Show() -app.MainLoop() +app.MainLoop() \ No newline at end of file