class VisualizerWindow: root = [] dataMgr = [] #graphs fig = [] ax1 = [] ax2 = [] ax3 = [] ax4 = [] ylim_ax1 = [] ylim_ax2 = [] ylim_ax3 = [] #data x = [] usage_manual = [] #debug show_debug_msg = False #autosave (-1 for disable) autosave_period = 30 load_autosave_file = False def __init__(self, filename): self.root = Tk.Tk() self.root.wm_title("EMG-Visualization-Tool") self.root.protocol("WM_DELETE_WINDOW", self._quit) if not filename: filename = askopenfilename() if not filename: sys.exit(0) while not os.path.isfile(filename): showerror("File not found", "Sorry, file '{}' was not found, try again".format(filename)) filename = askopenfilename() if not filename: sys.exit(0) if filename[-19:] == '_usage_autosave.pkl': self.autosave_file = filename print('Input file is an Auto-Save file "{}"'.format(self.autosave_file)) filename = filename[:-19] + '.csv' if not os.path.isfile(filename): showerror('Could not find file "{}" (matching to provided auto-save file "{}")'.format(filename, self.autosave_file)) print('Error: matching file not found (expected "{}")'.format(filename)) print('EXIT') sys.exit(0) else: self.load_autosave_file = True else: self.autosave_file = filename.rsplit(".", 1)[0] + "_usage_autosave.pkl" if os.path.isfile(self.autosave_file): print('Auto-Save file "{}" already exists'.format(self.autosave_file)) if askyesno('Auto-Save file found', 'Auto-Save file "{}" found. Load it instead? ("No" will result in automatical overwriting of the file when auto-saving)'.format(self.autosave_file)): self.load_autosave_file = True self.root.wm_title("EMG-Visualization-Tool: {}".format(filename)) self.dataMgr = DataManager(filename) self.csv_out_file = filename.rsplit(".", 1)[0] + "_usage.csv" while os.path.isfile(self.csv_out_file): print('File "{}" already exists'.format(self.csv_out_file)) if askyesno('Overwrite File?', 'File "{}" already exists. Overwrite?'.format(self.csv_out_file)): print('overwriting "{}"'.format(self.csv_out_file)) break else: new_out_file = asksaveasfilename(initialfile=self.csv_out_file) if new_out_file: self.csv_out_file = new_out_file print('New Output file "{}"'.format(self.csv_out_file)) else: sys.exit(0) print("csv-out-file: {}".format(self.csv_out_file)) self.configFrame = Tk.Frame(self.root, height=500, width=400) Tk.Label(self.configFrame, text="\r\nPlot 1").pack() self.plot1_select = ttk.Combobox(self.configFrame, values=self.dataMgr.plot_columns, state="readonly") self.plot1_select.pack() if '"f"' in self.dataMgr.plot_columns: self.plot1_select.current(self.dataMgr.plot_columns.index('"f"')) else: self.plot1_select.current(0) Tk.Label(self.configFrame, text="Plot 2").pack() self.plot2_select = ttk.Combobox(self.configFrame, values=self.dataMgr.plot_columns, state="readonly") self.plot2_select.pack() if '"rmsd"' in self.dataMgr.plot_columns: self.plot2_select.current(self.dataMgr.plot_columns.index('"rmsd"')) else: self.plot2_select.current(0) Tk.Label(self.configFrame, text="Plot 3").pack() self.plot3_select = ttk.Combobox(self.configFrame, values=self.dataMgr.plot_columns, state="readonly") self.plot3_select.pack() if '"beat"' in self.dataMgr.plot_columns: self.plot3_select.current(self.dataMgr.plot_columns.index('"beat"')) else: self.plot3_select.current(0) Tk.Label(self.configFrame, text="\r\nUsage Plot").pack() self.usage_plots = {} for usg in self.dataMgr.usage_columns: if not usg == '"usage_manual"': chkbx_var = Tk.IntVar() chkbx_var.set(1) usg_check = ttk.Checkbutton(self.configFrame, text=usg, variable=chkbx_var) usg_check.pack() self.usage_plots[usg] = chkbx_var Tk.Label(self.configFrame, text="\r\nLoad/copy \"usage_manual\" from").pack() if self.load_autosave_file: Tk.Label(self.configFrame, text="provided Auto-Save file").pack() else: self.usg_man_select = ttk.Combobox(self.configFrame, values=self.dataMgr.usage_columns, state="readonly") self.usg_man_select.pack() if '"usage_manual"' in self.dataMgr.usage_columns: self.usg_man_select.current(self.dataMgr.usage_columns.index('"usage_manual"')) elif '"usage_total"' in self.dataMgr.usage_columns: self.usg_man_select.current(self.dataMgr.usage_columns.index('"usage_total"')) else: if len(self.dataMgr.usage_columns) > 0: self.usg_man_select.current(0) Tk.Label(self.configFrame, text="\r\nJump Column").pack() if len(self.dataMgr.jump_columns) == 0: self.jmp_select = ttk.Combobox(self.configFrame, values=['--none--'], state="readonly") self.jmp_select.current(0) else: self.jmp_select = ttk.Combobox(self.configFrame, values=self.dataMgr.jump_columns, state="readonly") if '"jump_ibi"' in self.dataMgr.jump_columns: self.jmp_select.current(self.dataMgr.jump_columns.index('"jump_ibi"')) else: self.jmp_select.current(0) self.jmp_select.pack() Tk.Label(self.configFrame, text="").pack() button_continue = Tk.Button(self.configFrame, text='Continue', command=self._CfgContinue) button_continue.pack() Tk.Label(self.configFrame, text="").pack() self.loading_label = Tk.Label(self.configFrame, text="") self.loading_label.pack() self.configFrame.pack() self.visualizerFrame = Tk.Frame(self.root) # Figure with Subplots self.fig = plt.figure(figsize=(17,8), dpi=80, tight_layout=True) gs = gridspec.GridSpec(4,1, height_ratios=[3,2,2,1]) self.ax1 = plt.subplot(gs[0]) self.ax2 = plt.subplot(gs[1], sharex=self.ax1) self.ax3 = plt.subplot(gs[2], sharex=self.ax1) self.ax4 = plt.subplot(gs[3], sharex=self.ax1) canvas = FigureCanvasTkAgg(self.fig, master=self.visualizerFrame) canvas.show() canvas.mpl_connect('key_press_event', self.on_key_event) canvas.mpl_connect('button_press_event', self.on_button_event) # GUI Elements self.mode_label = Tk.Label(self.visualizerFrame, text="Mode: ADD", background="green") self.progress_label = Tk.Label(self.visualizerFrame, text="Page {}/{}".format(self.dataMgr.current_page, self.dataMgr.num_pages)) button_prev = Tk.Button(master=self.visualizerFrame, text='Prev', command=self._prev) button_next = Tk.Button(master=self.visualizerFrame, text='Next', command=self._next) button_zoom_in = Tk.Button(master=self.visualizerFrame, text='Zoom In', command=self._zoom_in) button_zoom_out = Tk.Button(master=self.visualizerFrame, text='Zoom Out', command=self._zoom_out) button_add = Tk.Button(master=self.visualizerFrame, text='Add', command=self._add) button_del = Tk.Button(master=self.visualizerFrame, text='Del', command=self._del) saveFrame = Tk.Frame(self.visualizerFrame) button_autosave = Tk.Button(master=saveFrame, text='Auto-Save', command=self._autosave) button_save = Tk.Button(master=saveFrame, text='Save', command=self._save) button_autosave.grid(column=0, row=0) button_save.grid(column=1, row=0) button_quit = Tk.Button(master=self.visualizerFrame, text='Quit', command=self._quit) # Selection self.multi_cursor = MultiCursor(self.fig.canvas, (self.ax1, self.ax2, self.ax3, self.ax4), useblit=True, horizOn=False, vertOn=True, color='g', lw=1) self.span1 = SpanSelector(self.ax1, self.onselectAdd, 'horizontal', useblit=True, rectprops=dict(alpha=0.5, facecolor='green') ) self.span2 = SpanSelector(self.ax2, self.onselectAdd, 'horizontal', useblit=True, rectprops=dict(alpha=0.5, facecolor='green') ) self.span3 = SpanSelector(self.ax3, self.onselectAdd, 'horizontal', useblit=True, rectprops=dict(alpha=0.5, facecolor='green') ) self.span4 = SpanSelector(self.ax4, self.onselectAdd, 'horizontal', useblit=True, rectprops=dict(alpha=0.5, facecolor='green') ) # GUI Layout button_zoom_in.grid(column=0, row=0) button_zoom_out.grid(column=1, row=0) button_prev.grid(column=3, row=0) self.progress_label.grid(column=4, row=0) button_next.grid(column=5, row=0) canvas.get_tk_widget().grid(column=0, row=1, columnspan=6) canvas._tkcanvas.grid(column=0, row=1, columnspan=6) button_add.grid(column=0, row=2) button_del.grid(column=1, row=2) self.mode_label.grid(column=2, row=2, columnspan=2) saveFrame.grid(column=4, row=2) button_quit.grid(column=5, row=2) Tk.mainloop() def _CfgContinue(self): self.loading_label['text'] = 'loading ...' self.loading_label.update() self.plot_names = [self.plot1_select.get(), self.plot2_select.get(), self.plot3_select.get()] self.plot_cols = [] for pn in self.plot_names: self.plot_cols.append(self.dataMgr.header_columns.index(pn)) self.usage_names = [] self.usage_cols = [] for k,v in self.usage_plots.items(): if v.get() == 1: self.usage_names.append(k) self.usage_cols.append(self.dataMgr.header_columns.index(k)) if self.load_autosave_file: usg_manual_col = -1 else: if self.usg_man_select.get() in self.dataMgr.header_columns: usg_manual_col = self.dataMgr.header_columns.index(self.usg_man_select.get()) else: usg_manual_col = -1 if self.jmp_select.get() in self.dataMgr.jump_columns: jmp_col = self.dataMgr.header_columns.index(self.jmp_select.get()) else: jmp_col = -1 [self.x, self.plot_data, self.usage_data, self.usage_manual, self.jump_positions] = self.dataMgr.readData(self.plot_cols,self.usage_cols,self.loading_label, usg_manual_col, jmp_col) if self.load_autosave_file: infile = open(self.autosave_file, 'rb') self.usage_manual = pickle.load(infile) infile.close() self.configFrame.pack_forget() self.visualizerFrame.pack() print("displaying plots") self.ylim_ax1 = self.calc_ylims(self.plot_data[0]) self.ylim_ax2 = self.calc_ylims(self.plot_data[1]) self.ylim_ax3 = self.calc_ylims(self.plot_data[2]) self.loadPage(1) def HMS(seconds, pos): """Customized x-axis ticks Keyword arguments: seconds -- input in secs pos -- somehow required argument (matplotlib) """ seconds = int(seconds) hours = seconds / 3600 seconds -= 3600 * hours minutes = seconds / 60 seconds -= 60 * minutes if hours == 0: if minutes == 0: return "%ds" % (seconds) return "%dm%02ds" % (minutes, seconds) return "%dh%02dm" % (hours, minutes) def calc_ylims(self, data): [cnt, edge] = np.histogram(data, 25) s = len(data) thres = 0.975*s i = 0 j = len(cnt)-1 while True: if cnt[i] < cnt[j]: if s-cnt[i] < thres: break else: s -= cnt[i] i += 1 else: if s-cnt[j] < thres: break else: s -= cnt[j] j -= 1 return [min(0, edge[i]), max(1, edge[j+1])] def plotPage(self): index_low = self.dataMgr.low_Idx() index_high = self.dataMgr.high_Idx() if self.show_debug_msg: print("index_low: {} | index_high: {}".format(index_low, index_high)) self.ax1.clear() self.ax1.xaxis.set_major_formatter(plt.FuncFormatter(self.HMS)) self.ax1.plot(self.x[index_low:index_high], np.array(self.plot_data[0][index_low:index_high])) self.ax1.set_ylim(self.ylim_ax1) self.ax1.set_title(self.plot_names[0]) self.ax2.clear() self.ax2.plot(self.x[index_low:index_high], self.plot_data[1][index_low:index_high]) self.ax2.set_ylim(self.ylim_ax2) self.ax2.set_title(self.plot_names[1]) self.ax3.clear() self.ax3.plot(self.x[index_low:index_high], self.plot_data[2][index_low:index_high]) self.ax3.set_ylim(self.ylim_ax3) self.ax3.set_title(self.plot_names[2]) self.plotUsages() self.fig.canvas.draw() def plotUsages(self): index_low = self.dataMgr.low_Idx() index_high = self.dataMgr.high_Idx() self.ax4.clear() self.ax4.set_ylim(0,len(self.usage_names)+2) self.ax4.set_yticks([], minor=False) colors = ['#483D8B', '#228B22', '#B22222', '#8A2BE2', '#808000', '#FF4500', '#DA70D6', '#FFA500'] self.ax4.fill_between(self.x[index_low:index_high], 0, (len(self.usage_names)+2)*np.array(self.usage_manual[index_low:index_high]), facecolor='#7fbf7f', edgecolor='None') self.ax4.plot(self.jump_positions, [len(self.usage_names)+1]*len(self.jump_positions), 'r*') for u in range(0,len(self.usage_data)): self.ax4.fill_between(self.x[index_low:index_high], u, u+np.array(self.usage_data[u][index_low:index_high]), facecolor=colors[u], edgecolor='None') patches = [mpatches.Patch(color='green', alpha=0.5, label='usage_manual')] for i in range(0,len(self.usage_names)): patches.append(mpatches.Patch(color=colors[i], label=self.usage_names[i])) plt.legend(bbox_to_anchor=(0., 1.0, 1., .102), loc=3, ncol=5, mode="expand", borderaxespad=0., handles=patches) self.ax1.set_xlim(self.dataMgr.low_Xlimit(),self.dataMgr.high_Xlimit()) def onselectAdd(self, xmin, xmax): minIdx = max(0, round(xmin*500)) maxIdx = min(self.dataMgr.file_length-1, round(xmax*500)) if self.show_debug_msg: print("ADD: xmin: {} | xmax: {}".format(xmin, xmax)) print("ADD: minIdx: {} | maxIdx: {}".format(minIdx, maxIdx)) self.usage_manual[minIdx:maxIdx] = 1 self.plotUsages() self.fig.canvas.draw() def onselectDel(self, xmin, xmax): minIdx = max(0, round(xmin*500)) maxIdx = min(self.dataMgr.file_length-1, round(xmax*500)) if self.show_debug_msg: print("DEL: xmin: {} | xmax: {}".format(xmin, xmax)) print("DEL: minIdx: {} | maxIdx: {}".format(minIdx, maxIdx)) self.usage_manual[minIdx:maxIdx] = 0 self.plotUsages() self.fig.canvas.draw() def onselectZoom(self, xmin, xmax): if self.show_debug_msg: print("ZOOM: xmin: {} | xmax: {}".format(xmin, xmax)) self.plotUsages() self.ax1.set_xlim(xmin,xmax) self.fig.canvas.draw() def on_key_event(self, event): if self.show_debug_msg: print('you pressed %s'%event.key) if event.key == 'a': self._prev() elif event.key == 'd': self._next() elif event.key == 'w': self._add() elif event.key == 's': self._del() elif event.key == 'q': self._zoom_in() elif event.key == 'e': self._zoom_out() elif event.key == 'r': self._save() elif event.key == 'f': self._autosave() elif event.key == 'x': self._prevJump() elif event.key == 'c': self._nextJump() elif event.key == 'left': self._prev() elif event.key == 'right': self._next() elif event.key == 'up': self._add() elif event.key == 'down': self._del() def on_button_event(self, event): if self.show_debug_msg: print('you clicked %s'%event.button) if event.button == 3: #right mouse button self._zoom_out() elif event.button == 2: #middle mouse button (scroll wheel) self._zoom_in() def _quit(self): self.root.quit() # stops mainloop self.root.destroy() # this is necessary on Windows to prevent # Fatal Python Error: PyEval_RestoreThread: NULL tstate def _prev(self): if self.show_debug_msg: print('_prev()') if self.dataMgr.current_page > 1: self.loadPage(self.dataMgr.current_page-1) def _next(self): if self.show_debug_msg: print('next()') if self.dataMgr.current_page < self.dataMgr.num_pages: self.loadPage(self.dataMgr.current_page+1) def _prevJump(self): if self.show_debug_msg: print('_prevJump()') if self.dataMgr.current_page > 1: for p in reversed(self.jump_positions): num = self.dataMgr.getPageNumByX(p) if num < self.dataMgr.current_page: self.loadPage(num) break def _nextJump(self): if self.show_debug_msg: print('nextJump()') if self.dataMgr.current_page < self.dataMgr.num_pages: for p in self.jump_positions: num = self.dataMgr.getPageNumByX(p) if num > self.dataMgr.current_page: self.loadPage(num) break def loadPage(self, page_num): if self.autosave_period > -1 and page_num % self.autosave_period == 0: if self.show_debug_msg: print('autosaving on page {}'.format(page_num)) self._autosave() self.dataMgr.current_page = min(max(1, page_num), self.dataMgr.num_pages) if self.show_debug_msg: print('loadPage(): {}'.format(self.dataMgr.current_page)) self.plotPage() self.progress_label["text"] ="Page {}/{}".format(self.dataMgr.current_page, self.dataMgr.num_pages) def _add(self): if self.show_debug_msg: print('_add()') if float(matplotlib.__version__[0:3])>=1.4: self.multi_cursor.disconnect() self.multi_cursor = MultiCursor(self.fig.canvas, (self.ax1, self.ax2, self.ax3, self.ax4), useblit=True, horizOn=False, vertOn=True, color='g', lw=1) self.span1.disconnect_events() self.span2.disconnect_events() self.span3.disconnect_events() self.span4.disconnect_events() self.span1 = SpanSelector(self.ax1, self.onselectAdd, 'horizontal', useblit=True, rectprops=dict(alpha=0.5, facecolor='green') ) self.span2 = SpanSelector(self.ax2, self.onselectAdd, 'horizontal', useblit=True, rectprops=dict(alpha=0.5, facecolor='green') ) self.span3 = SpanSelector(self.ax3, self.onselectAdd, 'horizontal', useblit=True, rectprops=dict(alpha=0.5, facecolor='green') ) self.span4 = SpanSelector(self.ax4, self.onselectAdd, 'horizontal', useblit=True, rectprops=dict(alpha=0.5, facecolor='green') ) self.mode_label['text'] = 'Mode: ADD' self.mode_label['bg'] = 'green' self.fig.canvas.draw() def _del(self): if self.show_debug_msg: print('_del()') if float(matplotlib.__version__[0:3])>=1.4: self.multi_cursor.disconnect() self.multi_cursor = MultiCursor(self.fig.canvas, (self.ax1, self.ax2, self.ax3, self.ax4), useblit=True, horizOn=False, vertOn=True, color='r', lw=1) self.span1.disconnect_events() self.span1.disconnect_events() self.span2.disconnect_events() self.span3.disconnect_events() self.span4.disconnect_events() self.span1 = SpanSelector(self.ax1, self.onselectDel, 'horizontal', useblit=True, rectprops=dict(alpha=0.5, facecolor='red') ) self.span2 = SpanSelector(self.ax2, self.onselectDel, 'horizontal', useblit=True, rectprops=dict(alpha=0.5, facecolor='red') ) self.span3 = SpanSelector(self.ax3, self.onselectDel, 'horizontal', useblit=True, rectprops=dict(alpha=0.5, facecolor='red') ) self.span4 = SpanSelector(self.ax4, self.onselectDel, 'horizontal', useblit=True, rectprops=dict(alpha=0.5, facecolor='red') ) self.mode_label['text'] = 'Mode: DEL' self.mode_label['bg'] = 'red' self.fig.canvas.draw() def _zoom_in(self): if self.show_debug_msg: print('_zoom_in()') if float(matplotlib.__version__[0:3])>=1.4: self.multi_cursor.disconnect() self.multi_cursor = MultiCursor(self.fig.canvas, (self.ax1, self.ax2, self.ax3, self.ax4), useblit=True, horizOn=False, vertOn=True, color='b', lw=1) self.span1.disconnect_events() self.span1.disconnect_events() self.span2.disconnect_events() self.span3.disconnect_events() self.span4.disconnect_events() self.span1 = SpanSelector(self.ax1, self.onselectZoom, 'horizontal', useblit=True, rectprops=dict(alpha=0.5, facecolor='blue') ) self.span2 = SpanSelector(self.ax2, self.onselectZoom, 'horizontal', useblit=True, rectprops=dict(alpha=0.5, facecolor='blue') ) self.span3 = SpanSelector(self.ax3, self.onselectZoom, 'horizontal', useblit=True, rectprops=dict(alpha=0.5, facecolor='blue') ) self.span4 = SpanSelector(self.ax4, self.onselectZoom, 'horizontal', useblit=True, rectprops=dict(alpha=0.5, facecolor='blue') ) self.mode_label['text'] = 'Mode: ZOOM' self.mode_label['bg'] = 'blue' self.fig.canvas.draw() def _zoom_out(self): if self.show_debug_msg: print('_zoom_out()') self.plotUsages() self.fig.canvas.draw() def _save(self): if self.show_debug_msg: print('_save()') savetxt = plt.text(20, 20, 'saving...', fontsize=46, color='r', weight='bold', ha='center', va='top') self.fig.canvas.draw() self.dataMgr.writeCSV(self.csv_out_file, self.usage_manual) savetxt.remove() self.fig.canvas.draw() if os.path.isfile(self.autosave_file): archive_filename = self.autosave_file.rsplit(".", 1)[0] + "_old.pkl" print('renaming autosave file from "{}" to "{}"'.format(self.autosave_file, archive_filename)) if os.path.isfile(archive_filename): os.unlink(archive_filename) os.rename(self.autosave_file, archive_filename) def _autosave(self): if self.show_debug_msg: print('_autosave()') savetxt = plt.text(20, 20, 'autosaving...', fontsize=46, color='r', weight='bold', ha='center', va='top') self.fig.canvas.draw() self.dataMgr.writeAutoSave(self.autosave_file, self.usage_manual) savetxt.remove() self.fig.canvas.draw()
class MyFrame(matplotlib_GUI.MyFrame): # set up datastructure used by get_axes() method ------------------------------------------------------------------- # where to place plots? map user choice to subplot: (row, col, position) # e.g. "top left" for 2x2 plots -> (2,2,1) # this will be the argument to self.canvas.figure.add_subplot(...) _SUBPLOT_CHOICES = [ ((1, 1), ("center", )), ((1, 2), ("left", "right")), ((2, 1), ("top", "bottom")), ((2, 2), ("top left", "top right", "bottom left", "bottom right")), ((2, 3), ("top left", "top center", "top right", "bottom left", "bottom center", "bottom right")) ] _SUBPLOT_POSITIONS = {} for ((rows, cols), choices) in _SUBPLOT_CHOICES: _SUBPLOT_POSITIONS[rows, cols] = {} i = 0 for row in range(1, rows + 1): for col in range(1, cols + 1): if choices: _SUBPLOT_POSITIONS[rows, cols][choices[i]] = (rows, cols, i + 1) i += 1 del rows, cols, choices, i # ------------------------------------------------------------------------------------------------------------------ def __init__(self, *args, **kwargs): matplotlib_GUI.MyFrame.__init__(self, *args, **kwargs) self.figure = self.canvas.figure self.init_toolmanager() self.init_events() # where to save figures by default? matplotlib.rcParams[ 'savefig.directory'] = "" # defaults to current directory self.Bind(wx.EVT_CLOSE, self.OnClose) # initialize values from control values; so the .wxg file needs to have correct initialization values! self.on_choice_line_style() self.on_combo_colour() self.on_combo_line_width() self.on_choice_subplots() self.set_history_buttons() self.on_button_plot() self.multicursor = None def init_toolmanager(self): self.toolmanager = matplotlib.backend_managers.ToolManager( self.canvas.figure) self.toolmanager.add_tool( 'viewpos', 'ToolViewsPositions') # required for pan/zoom/home/back/forward self.toolmanager.add_tool( 'pan', 'ToolPan') # pan w. mouse and zoom w. wheel with 'p' key self.toolmanager.add_tool('zoom', 'ToolZoom') # zoom to rect with 'o' key self.toolmanager.add_tool('home', 'ToolHome') # 'h', 'r', 'home' self.toolmanager.add_tool('back', 'ToolBack') # 'left', 'c', 'backspace' self.toolmanager.add_tool('forward', 'ToolForward') # 'right', 'v' self.toolmanager.add_tool('save', 'ToolSaveFigure') # 's', 'ctrl+s' self.toolmanager.add_tool( 'grid', 'ToolGrid') # toggle throug major h/v grids with 'g' key self.toolmanager.add_tool( 'grid_minor', 'ToolMinorGrid') # toggle throug major/minor grids with 'G' key self.toolmanager.add_tool( 'yscale', 'ToolYScale') # toggle lin/log scaling with 'l' key self.toolmanager.add_tool( 'xscale', 'ToolXScale') # toggle lin/log scaling with 'k','L' keys # some tools will only be available with matplotlib 3.0: if hasattr(matplotlib.backend_managers, "ToolCopyToClipboard"): self.toolmanager.add_tool('copy', 'ToolCopyToClipboard') self.toolmanager.add_tool('rubberband', 'ToolRubberband') self.toolmanager.add_tool('setcursor', 'ToolSetCursor') self.toolmanager.toolmanager_connect("tool_trigger_home", self.set_history_buttons) self.toolmanager.toolmanager_connect("tool_trigger_back", self.set_history_buttons) self.toolmanager.toolmanager_connect("tool_trigger_forward", self.set_history_buttons) def init_events(self): # connect to matplotlib events # for picking elements self.canvas.mpl_connect('pick_event', self.on_pick) self._last_pick_mouseevent = None # store info, as we will act only once per pick event # for displaying cursor position self.canvas.mpl_connect('motion_notify_event', self.on_mouse_move) # button_release_event may be the end of a zoom; use CallAfter as we may receive the event too early self.canvas.mpl_connect( "button_release_event", lambda event: wx.CallAfter(self.set_history_buttons)) # register a handler for all canvas events except 'motion_notify_event', which would cause too much traffic for event in ('button_press_event', 'button_release_event', 'key_press_event', 'key_release_event', 'draw_event', 'pick_event', 'resize_event', 'scroll_event', 'figure_enter_event', 'figure_leave_event', 'axes_enter_event', 'axes_leave_event'): self.canvas.mpl_connect(event, self.on_canvas_event) # in addition, there are events from the axes: 'xlim_changed', 'ylim_changed' (see self.get_axes()) ################################################################################################################### # print messages def on_canvas_event(self, event): print("canvas event:", event) ################################################################################################################### # global actions: add plot or clear plots or all def get_axes(self): # return the axes that are selected acc. to "Subplots" and "Subplot position" subplots = self._SUBPLOT_POSITIONS[self.subplots][self.subplot] # subplots = (row, col, position), e.g. (2,2,1) for the top left of 2x2 plots ret = self.figure.add_subplot(*subplots) # we could register event handlers when x or y limits are changing: #ret.callbacks.connect( 'xlim_changed', lambda evt: print("callback xlim_changed:", evt) ) return ret def on_button_clear(self, types): if self.multicursor: self.multicursor.disconnect() self.multicursor = None self.button_multicursor.SetValue(False) if types == "plots": for axes in self.figure.axes: axes.clear() elif types in ("figures", "all"): self.figure.clear() self.canvas.draw() if types in ("plots", "all"): # reset zoom history self.toolmanager.get_tool("viewpos").clear(self.canvas.figure) self.set_history_buttons() def on_button_multicursor(self, event): from matplotlib.widgets import MultiCursor axes = self.canvas.figure.axes if event.IsChecked() and not self.multicursor and axes: # add multicursor self.multicursor = MultiCursor(self.canvas, axes, color='r', lw=1, horizOn=True, vertOn=True) elif self.multicursor: # remove multicursor self.multicursor.disconnect() self.multicursor = None self.canvas.draw() event.Skip() #################################################################################################################### # draw elements on canvas plot areas / axes (by coordinates) def on_button_plot(self, event=None): # plot a function xmin, xmax, step = self._get_floats("text_plot_", ("xmin", "xmax", "xstep")) if None in (xmin, xmax, step): # one of the values is invalid return x = numpy.arange(xmin, xmax, step) # build globals with some functions for eval(...) g = {} for name in ["sin", "cos", "tan", "ufunc", "square"]: g[name] = getattr(numpy, name) y = eval(self.text_function.GetValue(), g, {"numpy": numpy, "x": x}) axes = self.get_axes() colour, width, style = self._get_styles() axes.plot( x, y, picker= 5, # enable picking, i.e. the user can select this with the mouse color=colour, linewidth=width, linestyle=style) # show the updates self.canvas.draw() self.set_history_buttons() def on_button_plot_line(self, event): # draw a line; axis coordinates x0, y0, x1, y1 = self._get_floats("text_plot_line_", ("x0", "y0", "x1", "y1")) if None in (x0, y0, x1, y1): return None axes = self.get_axes() colour, width, style = self._get_styles() line = matplotlib.lines.Line2D((x0, x1), (y0, y1), pickradius=5, color=colour, linewidth=width, linestyle=style) line.set_picker( 5) # enable picking, i.e. the user can select this with the mouse axes.add_line(line) axes.autoscale(True, 'both', True) # ensure that it is visible # show the updates self.canvas.draw() self.set_history_buttons() def on_button_plot_rect(self, event): # draw a rectangle; axis coordinates x, y, width, height, angle = self._get_floats( "text_plot_rect_", ("x", "y", "width", "height", "angle")) if None in (x, y, width, height, angle): return None axes = self.get_axes() colour, width, style = self._get_styles() patch = matplotlib.patches.Rectangle((x, y), width, height, angle, figure=self.canvas.figure, color=colour, linewidth=width, linestyle=style, fill=False) patch.set_picker( 5) # enable picking, i.e. the user can select this with the mouse axes.add_patch(patch) axes.autoscale(True, 'both', True) # ensure that it is visible # show the updates self.canvas.draw() self.set_history_buttons() def on_button_plot_circle(self, event): # draw a circle; canvas coordinates / pixels # https://matplotlib.org/api/_as_gen/matplotlib.patches.Circle.html x, y, radius = self._get_floats("text_plot_circle_", ("x", "y", "radius")) if None in (x, y, radius): return None axes = self.get_axes() colour, width, style = self._get_styles() circle = matplotlib.patches.Circle((x, y), radius, figure=self.canvas.figure, color=colour, linewidth=width, linestyle=style) circle.set_picker( 5) # enable picking, i.e. the user can select this with the mouse axes.add_patch(circle) axes.autoscale(True, 'both', True) # ensure that it is visible # show the updates self.canvas.draw() self.set_history_buttons() #################################################################################################################### # draw elements on canvas (by pixels) # see https://matplotlib.org/api/artist_api.html # rectangles, arrows, circles: https://matplotlib.org/api/patches_api.html def on_button_draw_line(self, event): # draw a line; canvas coordinates / pixels x0, y0, x1, y1 = self._get_floats("text_line_", ("x0", "y0", "x1", "y1")) if None in (x0, y0, x1, y1): return None colour, width, style = self._get_styles() # both figure and pickradius are required here; otherwise picking will not work line = matplotlib.lines.Line2D((x0, x1), (y0, y1), figure=self.canvas.figure, pickradius=5, color=colour, linewidth=width, linestyle=style) line.set_picker( 5) # enable picking, i.e. the user can select this with the mouse self.canvas.figure.lines.append(line) # show the updates self.canvas.draw() self.set_history_buttons() def on_button_draw_rect(self, event): # draw a rectangle; canvas coordinates / pixels x, y, w, h, angle = self._get_floats( "text_rect_", ("x", "y", "width", "height", "angle")) if None in (x, y, w, h, angle): return None colour, width, style = self._get_styles() # implement method? self.matplotlib_canvas.draw_rectangle( (x,y), width, height, angle ) patch = matplotlib.patches.Rectangle((x, y), w, h, angle, figure=self.canvas.figure, color=colour, linewidth=width, linestyle=style) patch.set_picker( 5) # enable picking, i.e. the user can select this with the mouse self.canvas.figure.patches.append(patch) # show the updates self.canvas.draw() self.set_history_buttons() def on_button_draw_circle(self, event): # draw a circle; canvas coordinates / pixels # https://matplotlib.org/api/_as_gen/matplotlib.patches.Circle.html x, y, radius = self._get_floats("text_circle_", ("x", "y", "radius")) if None in (x, y, radius): return None colour, width, style = self._get_styles() # implement method? self.matplotlib_canvas.draw_rectangle( (x,y), width, height, angle ) patch = matplotlib.patches.Circle((x, y), radius, figure=self.canvas.figure, color=colour, linewidth=width, linestyle=style) patch.set_picker( 5) # enable picking, i.e. the user can select this with the mouse self.canvas.figure.patches.append(patch) # show the updates self.canvas.draw() self.set_history_buttons() #################################################################################################################### # helpers def _get_floats(self, prefix, names): ret = [] for name in names: full_name = prefix + name f = self._get_float(getattr(self, full_name)) ret.append(f) return ret def _get_float(self, control): # returns a float or None if not a valid float try: ret = float(control.GetValue()) colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW) control.SetBackgroundColour(colour) except: control.SetBackgroundColour(wx.RED) wx.Bell() ret = None control.Refresh() return ret #################################################################################################################### # mouse actions def on_pick(self, event): # pick: when the user clicked an item on the canvas that has picking enabled ( see above: line.set_picker(5) ) if id(event.mouseevent) == self._last_pick_mouseevent: # it's reasonable that we have received this one already for another element print("skipping pick event") return self._last_pick_mouseevent = id(event.mouseevent) delete = self.checkbox_pick_delete.GetValue() artist = event.artist s = "" if isinstance(artist, matplotlib.lines.Line2D): s = "line" elif isinstance(artist, matplotlib.patches.Rectangle): s = "rectangle" elif isinstance(artist, matplotlib.patches.Circle): s = "circle" if artist.axes: # data plotted on axes if hasattr(artist, "get_data"): xdata, ydata = artist.get_data() ind = event.ind[0] s = 'plot: %s, %s' % (xdata[ind], ydata[ind]) elif hasattr(artist, "get_xy"): xdata, ydata = artist.get_xy() if delete: artist.remove() else: # no axes -> drawn directly on figure by pixel coordinates if delete: self.canvas.figure.patches.remove(artist) self.text_picked.SetValue(s) self.canvas.draw() def on_mouse_move(self, event): # display mouse pointer coordinates; based on code from NavigationToolbar2 s = "" if event.inaxes and event.inaxes.get_navigate(): try: xs = event.inaxes.format_xdata(event.xdata).strip() ys = event.inaxes.format_ydata(event.ydata).strip() s = "%s / %s" % (xs, ys) except (ValueError, OverflowError): pass else: artists = [ a for a in event.inaxes.mouseover_set if a.contains(event) and a.get_visible() ] if artists: a = cbook._topmost_artist(artists) if a is not event.inaxes.patch: data = a.get_cursor_data(event) if data is not None: s += ' [%s]' % a.format_cursor_data(data) self.text_cursor_xy_value.SetValue(s) self.text_cursor_xy_pixel.SetValue("%s / %s px" % (event.x, event.y)) #################################################################################################################### # canvas size and layout (number of plots) def on_choice_canvas_size(self, event): # this partially relies on a modification of FigureCanvas in matplotlib_compat.py value = event.GetString() sz = canvas.GetParent().Sizer si = sz.GetItem(canvas) if "x" in value: si.SetProportion(0) si.SetFlag(si.GetFlag() & ~wx.EXPAND) # clear the expand bit size = value.split("x") size = int(size[0]), int(size[1]) si.SetMinSize(size) canvas.SetSize(size) canvas.SetMinSize(size) else: # variable size si.SetProportion(3) si.SetFlag(si.GetFlag() | wx.EXPAND) # clear the expand bit si.SetMinSize((100, 100)) sz.Layout() def on_choice_subplots(self, event=None): # clear the canvas if required; set the "Subplot" choices if event is None: rows_cols = self.choice_subplots.GetStringSelection() update_subplot_choices = True else: rows_cols = event.GetString() update_subplot_choices = False rows, cols = rows_cols.split("x") # e.g. "2x1" subplots = int(rows), int(cols) if hasattr(self, "subplots") and subplots != self.subplots: # changed -> clear existing plots #self.on_button_clear("plots") self.on_button_clear("all") update_subplot_choices = True if update_subplot_choices: for SUBPLOTS, CHOICES in self._SUBPLOT_CHOICES: if SUBPLOTS == subplots: if CHOICES is None: self.choice_subplot.Clear() self.choice_subplot.Disable() self.subplot = (1, 1) else: self.choice_subplot.Set(CHOICES) self.choice_subplot.SetSelection(0) self.choice_subplot.Enable(len(CHOICES) > 1) self.subplot = CHOICES[0] self.subplots = subplots def on_choice_subplot(self, event): self.subplot = event.GetString() #################################################################################################################### # plot styles: maintain properties for colour, line width, line style def _get_styles(self): ret = (self._line_colour, self._line_width, self._line_style) n = self.combo_box_colour.GetSelection() + 1 n = n % self.combo_box_colour.GetCount() self.combo_box_colour.Select(n) self.on_combo_colour() return ret def on_combo_colour(self, event=None): self._line_colour = self.combo_box_colour.GetValue() or "black" def on_combo_line_width(self, event=None): self._line_width = float(self.combo_box_line_width.GetValue()) def on_choice_line_style(self, event=None): line_style = self.choice_line_style.GetItems()[ self.choice_line_style.GetSelection()] self._line_style = line_style.split()[0].strip() #################################################################################################################### # some actions to be forwarded to the tool manager def on_button_zoom_history(self, action): if action == "home": self.toolmanager.trigger_tool("home") elif action == "back": self.toolmanager.trigger_tool("back") elif action == "forward": self.toolmanager.trigger_tool("forward") self.canvas.draw() self.set_history_buttons() def on_button_autoscale(self, event): if not self.canvas.figure.axes: return for axes in self.canvas.figure.axes: axes.autoscale(True, 'both', True) self.canvas.draw() viewpos_tool = self.toolmanager.get_tool("viewpos") viewpos_tool.clear(self.canvas.figure) if self.canvas.figure in viewpos_tool.views: self.toolmanager.get_tool("viewpos").push_current() self.set_history_buttons() def set_history_buttons(self, event=None): # enable or disable buttons: zoom/scale, clear vp = self.toolmanager.get_tool("viewpos") if vp.views: view_stack = vp.views[self.canvas.figure] view_stack._pos can_backward = view_stack._pos > 0 can_forward = view_stack._pos < len(view_stack._elements) - 1 else: can_backward = can_forward = False self.button_zoom_hist_back.Enable(can_backward) self.button_zoom_hist_forward.Enable(can_forward) # enable the autoscale button as well can_autoscale = bool(self.canvas.figure.axes) self.button_autoscale.Enable(can_autoscale) # and the clear buttons can_clear_plots = False for axes in self.canvas.figure.axes: if axes.has_data(): can_clear_plots = True break can_clear_all = len(self.canvas.figure.get_children()) > 1 self.button_clear_plots.Enable(can_clear_plots) self.button_clear_all.Enable(can_clear_all) def on_choice_mouse_action(self, event): selection = self.choice_mouse_action.GetStringSelection() if selection == "Pan/Zoom": self.toolmanager.trigger_tool("pan") elif selection == "Zoom": self.toolmanager.trigger_tool("zoom") else: # deactivate by triggering the currently selected tool # when pan and zoom are inactive, the mouse will pick elements toggled = self.toolmanager.active_toggle.get("default") if toggled == "pan": self.toolmanager.trigger_tool("pan") elif toggled == "zoom": self.toolmanager.trigger_tool("zoom") def on_file_save(self, event): # save figure as bitmap or PDF self.toolmanager.trigger_tool("save") #################################################################################################################### def on_file_exit(self, event): self.Close() def OnClose(self, event): # delete graphics context to avoid crash self.canvas.cleanup() event.Skip()
class MyFrame(matplotlib_GUI.MyFrame): # set up datastructure used by get_axes() method ------------------------------------------------------------------- # where to place plots? map user choice to subplot: (row, col, position) # e.g. "top left" for 2x2 plots -> (2,2,1) # this will be the argument to self.canvas.figure.add_subplot(...) _SUBPLOT_CHOICES = [ ((1, 1), ("center", )), ((1, 2), ("left", "right")), ((2, 1), ("top", "bottom")), ((2, 2), ("top left", "top right", "bottom left", "bottom right")), ((2, 3), ("top left", "top center", "top right", "bottom left", "bottom center", "bottom right")) ] _SUBPLOT_POSITIONS = {} for ((rows, cols), choices) in _SUBPLOT_CHOICES: _SUBPLOT_POSITIONS[rows, cols] = {} i = 0 for row in range(1, rows + 1): for col in range(1, cols + 1): if choices: _SUBPLOT_POSITIONS[rows, cols][choices[i]] = (rows, cols, i + 1) i += 1 del rows, cols, choices, i # ------------------------------------------------------------------------------------------------------------------ def __init__(self, *args, **kwargs): self.result = {} matplotlib_GUI.MyFrame.__init__(self, *args, **kwargs) self.figure = self.canvas.figure self.init_toolmanager() self.init_events() # where to save figures by default? matplotlib.rcParams[ 'savefig.directory'] = "" # defaults to current directory self.Bind(wx.EVT_CLOSE, self.OnClose) # initialize values from control values; so the .wxg file needs to have correct initialization values! self.on_choice_line_style() self.on_combo_colour() self.on_combo_line_width() self.on_choice_subplots() self.set_history_buttons() self.on_button_plot() self.multicursor = None def init_toolmanager(self): self.toolmanager = matplotlib.backend_managers.ToolManager( self.canvas.figure) self.toolmanager.add_tool( 'viewpos', 'ToolViewsPositions') # required for pan/zoom/home/back/forward self.toolmanager.add_tool( 'pan', 'ToolPan') # pan w. mouse and zoom w. wheel with 'p' key self.toolmanager.add_tool('zoom', 'ToolZoom') # zoom to rect with 'o' key self.toolmanager.add_tool('home', 'ToolHome') # 'h', 'r', 'home' if hasattr(matplotlib.backend_managers, "ToolHelp"): self.toolmanager.add_tool('help', 'ToolHelp') # 'F1' self.toolmanager.add_tool('back', 'ToolBack') # 'left', 'c', 'backspace' self.toolmanager.add_tool('forward', 'ToolForward') # 'right', 'v' self.toolmanager.add_tool('save', 'ToolSaveFigure') # 's', 'ctrl+s' self.toolmanager.add_tool( 'grid', 'ToolGrid') # toggle throug major h/v grids with 'g' key self.toolmanager.add_tool( 'grid_minor', 'ToolMinorGrid') # toggle throug major/minor grids with 'G' key self.toolmanager.add_tool( 'yscale', 'ToolYScale') # toggle lin/log scaling with 'l' key self.toolmanager.add_tool( 'xscale', 'ToolXScale') # toggle lin/log scaling with 'k','L' keys # some tools will only be available with matplotlib 3.0: if hasattr(matplotlib.backend_managers, "ToolCopyToClipboard"): self.toolmanager.add_tool('copy', 'ToolCopyToClipboard') self.toolmanager.add_tool('rubberband', 'ToolRubberband') self.toolmanager.add_tool('setcursor', 'ToolSetCursor') self.toolmanager.toolmanager_connect("tool_trigger_home", self.set_history_buttons) self.toolmanager.toolmanager_connect("tool_trigger_back", self.set_history_buttons) self.toolmanager.toolmanager_connect("tool_trigger_forward", self.set_history_buttons) def init_events(self): # connect to matplotlib events # for picking elements self.canvas.mpl_connect('pick_event', self.on_pick) self._last_pick_mouseevent = None # store info, as we will act only once per pick event # for displaying cursor position self.canvas.mpl_connect('motion_notify_event', self.on_mouse_move) # button_release_event may be the end of a zoom; use CallAfter as we may receive the event too early self.canvas.mpl_connect( "button_release_event", lambda event: wx.CallAfter(self.set_history_buttons)) # register a handler for all canvas events except 'motion_notify_event', which would cause too much traffic for event in ('button_press_event', 'button_release_event', 'key_press_event', 'key_release_event', 'draw_event', 'pick_event', 'resize_event', 'scroll_event', 'figure_enter_event', 'figure_leave_event', 'axes_enter_event', 'axes_leave_event'): self.canvas.mpl_connect(event, self.on_canvas_event) # in addition, there are events from the axes: 'xlim_changed', 'ylim_changed' (see self.get_axes()) ################################################################################################################### # print messages def on_canvas_event(self, event): print("canvas event:", event) ################################################################################################################### # global actions: add plot or clear plots or all def get_axes(self): # return the axes that are selected acc. to "Subplots" and "Subplot position" subplots = self._SUBPLOT_POSITIONS[self.subplots][self.subplot] # subplots = (row, col, position), e.g. (2,2,1) for the top left of 2x2 plots ret = self.figure.add_subplot(*subplots) # we could register event handlers when x or y limits are changing: #ret.callbacks.connect( 'xlim_changed', lambda evt: print("callback xlim_changed:", evt) ) return ret def on_button_clear(self, types): if self.multicursor: self.multicursor.disconnect() self.multicursor = None self.button_multicursor.SetValue(False) if types == "plots": for axes in self.figure.axes: axes.clear() elif types in ("figures", "all"): self.figure.clear() self.canvas.draw() if types in ("plots", "all"): # reset zoom history self.toolmanager.get_tool("viewpos").clear(self.canvas.figure) self.set_history_buttons() def on_button_multicursor(self, event): from matplotlib.widgets import MultiCursor axes = self.canvas.figure.axes if event.IsChecked() and not self.multicursor and axes: # add multicursor self.multicursor = MultiCursor(self.canvas, axes, color='r', lw=1, horizOn=True, vertOn=True) elif self.multicursor: # remove multicursor self.multicursor.disconnect() self.multicursor = None self.canvas.draw() event.Skip() #################################################################################################################### # draw elements on canvas plot areas / axes (by coordinates) def on_button_plot(self, event=None): # plot a function xmin, xmax, step = self._get_floats("text_plot_", ("xmin", "xmax", "xstep")) if None in (xmin, xmax, step): # one of the values is invalid return x = numpy.arange(xmin, xmax, step) # build globals with some functions for eval(...) g = {} for name in ["sin", "cos", "tan", "ufunc", "square"]: g[name] = getattr(numpy, name) y = eval(self.text_function.GetValue(), g, {"numpy": numpy, "x": x}) axes = self.get_axes() colour, width, style = self._get_styles() axes.plot( x, y, picker= 5, # enable picking, i.e. the user can select this with the mouse color=colour, linewidth=width, linestyle=style) # show the updates self.canvas.draw() self.set_history_buttons() #################################################################################################################### # helpers def _get_floats(self, prefix, names): ret = [] for name in names: full_name = prefix + name f = self._get_float(getattr(self, full_name)) ret.append(f) return ret def _get_float(self, control): # returns a float or None if not a valid float try: ret = float(control.GetValue()) colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW) control.SetBackgroundColour(colour) except: control.SetBackgroundColour(wx.RED) wx.Bell() ret = None control.Refresh() return ret #################################################################################################################### # mouse actions def on_pick(self, event): # pick: when the user clicked an item on the canvas that has picking enabled ( see above: line.set_picker(5) ) if id(event.mouseevent) == self._last_pick_mouseevent: # it's reasonable that we have received this one already for another element print("skipping pick event") return self._last_pick_mouseevent = id(event.mouseevent) delete = self.checkbox_pick_delete.GetValue() artist = event.artist s = "" if isinstance(artist, matplotlib.lines.Line2D): s = "line" elif isinstance(artist, matplotlib.patches.Rectangle): s = "rectangle" elif isinstance(artist, matplotlib.patches.Circle): s = "circle" if artist.axes: # data plotted on axes if hasattr(artist, "get_data"): xdata, ydata = artist.get_data() ind = event.ind[0] s = 'plot: %s, %s' % (xdata[ind], ydata[ind]) elif hasattr(artist, "get_xy"): xdata, ydata = artist.get_xy() if delete: artist.remove() else: # no axes -> drawn directly on figure by pixel coordinates if delete: self.canvas.figure.patches.remove(artist) self.text_picked.SetValue(s) self.canvas.draw() def on_mouse_move(self, event): # display mouse pointer coordinates; based on code from NavigationToolbar2 s = "" if event.inaxes and event.inaxes.get_navigate(): try: xs = event.inaxes.format_xdata(event.xdata).strip() ys = event.inaxes.format_ydata(event.ydata).strip() s = "%s / %s" % (xs, ys) except (ValueError, OverflowError): pass else: artists = [ a for a in event.inaxes.mouseover_set if a.contains(event) and a.get_visible() ] if artists: a = cbook._topmost_artist(artists) if a is not event.inaxes.patch: data = a.get_cursor_data(event) if data is not None: s += ' [%s]' % a.format_cursor_data(data) self.text_cursor_xy_value.SetValue(s) self.text_cursor_xy_pixel.SetValue("%s / %s px" % (event.x, event.y)) #################################################################################################################### # canvas size and layout (number of plots) def on_choice_canvas_size(self, event): # this partially relies on a modification of FigureCanvas in matplotlib_compat.py value = event.GetString() sz = canvas.GetParent().Sizer si = sz.GetItem(canvas) if "x" in value: si.SetProportion(0) si.SetFlag(si.GetFlag() & ~wx.EXPAND) # clear the expand bit size = value.split("x") size = int(size[0]), int(size[1]) si.SetMinSize(size) canvas.SetSize(size) canvas.SetMinSize(size) else: # variable size si.SetProportion(3) si.SetFlag(si.GetFlag() | wx.EXPAND) # clear the expand bit si.SetMinSize((100, 100)) sz.Layout() def on_choice_subplots(self, event=None): # clear the canvas if required; set the "Subplot" choices if event is None: rows_cols = self.choice_subplots.GetStringSelection() update_subplot_choices = True else: rows_cols = event.GetString() update_subplot_choices = False rows, cols = rows_cols.split("x") # e.g. "2x1" subplots = int(rows), int(cols) if hasattr(self, "subplots") and subplots != self.subplots: # changed -> clear existing plots #self.on_button_clear("plots") self.on_button_clear("all") update_subplot_choices = True if update_subplot_choices: for SUBPLOTS, CHOICES in self._SUBPLOT_CHOICES: if SUBPLOTS == subplots: if CHOICES is None: self.choice_subplot.Clear() self.choice_subplot.Disable() self.subplot = (1, 1) else: self.choice_subplot.Set(CHOICES) self.choice_subplot.SetSelection(0) self.choice_subplot.Enable(len(CHOICES) > 1) self.subplot = CHOICES[0] self.subplots = subplots def on_choice_subplot(self, event): self.subplot = event.GetString() #################################################################################################################### # plot styles: maintain properties for colour, line width, line style def _get_styles(self): ret = (self._line_colour, self._line_width, self._line_style) n = self.combo_box_colour.GetSelection() + 1 n = n % self.combo_box_colour.GetCount() self.combo_box_colour.Select(n) self.on_combo_colour() return ret def on_combo_colour(self, event=None): self._line_colour = self.combo_box_colour.GetValue() or "black" def on_combo_line_width(self, event=None): self._line_width = float(self.combo_box_line_width.GetValue()) def on_choice_line_style(self, event=None): line_style = self.choice_line_style.GetItems()[ self.choice_line_style.GetSelection()] self._line_style = line_style.split()[0].strip() #################################################################################################################### # some actions to be forwarded to the tool manager def on_button_zoom_history(self, action): if action == "home": self.toolmanager.trigger_tool("home") elif action == "back": self.toolmanager.trigger_tool("back") elif action == "forward": self.toolmanager.trigger_tool("forward") self.canvas.draw() self.set_history_buttons() def on_button_autoscale(self, event): if not self.canvas.figure.axes: return for axes in self.canvas.figure.axes: axes.autoscale(True, 'both', True) self.canvas.draw() viewpos_tool = self.toolmanager.get_tool("viewpos") viewpos_tool.clear(self.canvas.figure) if self.canvas.figure in viewpos_tool.views: self.toolmanager.get_tool("viewpos").push_current() self.set_history_buttons() def set_history_buttons(self, event=None): # enable or disable buttons: zoom/scale, clear vp = self.toolmanager.get_tool("viewpos") if vp.views: view_stack = vp.views[self.canvas.figure] view_stack._pos can_backward = view_stack._pos > 0 can_forward = view_stack._pos < len(view_stack._elements) - 1 else: can_backward = can_forward = False self.button_zoom_hist_back.Enable(can_backward) self.button_zoom_hist_forward.Enable(can_forward) # enable the autoscale button as well can_autoscale = bool(self.canvas.figure.axes) self.button_autoscale.Enable(can_autoscale) # and the clear buttons can_clear_plots = False for axes in self.canvas.figure.axes: if axes.has_data(): can_clear_plots = True break can_clear_all = len(self.canvas.figure.get_children()) > 1 self.button_clear_plots.Enable(can_clear_plots) self.button_clear_all.Enable(can_clear_all) def on_choice_mouse_action(self, event): selection = self.choice_mouse_action.GetStringSelection() if selection == "Pan/Zoom": self.toolmanager.trigger_tool("pan") elif selection == "Zoom": self.toolmanager.trigger_tool("zoom") else: # deactivate by triggering the currently selected tool # when pan and zoom are inactive, the mouse will pick elements toggled = self.toolmanager.active_toggle.get("default") if toggled == "pan": self.toolmanager.trigger_tool("pan") elif toggled == "zoom": self.toolmanager.trigger_tool("zoom") #################################################################################################################### #################################################################################################################### #################################################################################################################### def readfun(self, f, bar): global prog_i bar.update(prog_i) prog_i = prog_i + 1 return pandas.read_csv(f, index_col=False) def on_button_adddata(self, event): global prog_i prog_i = 0 self.list_box_currentdata.Append( self.list_box_data_folder.GetStringSelection()) self.list_box_currentdata.SetSelection( self.list_box_currentdata.FindString( self.list_box_data_folder.GetStringSelection())) picklefolder = "F:\\A7data\\p\\" folder = self.list_box_data_folder.GetStringSelection() files = glob.glob("D:\Project\ODEA7\PTrun\\" + folder + '\\a\\allMyNumbers*.csv') bar = progressbar.ProgressBar(max_value=len(files)) if os.path.exists(picklefolder + folder): print("read pickle") self.result[folder] = pandas.read_pickle(picklefolder + folder) else: print("read raw csv") self.result[folder] = pandas.concat( [self.readfun(f, bar) for f in files], ignore_index=True) self.result[folder].to_pickle(picklefolder + folder) self.slider_length.SetRange( 1, self.result[ self.list_box_data_folder.GetStringSelection()].shape[0]) self.slider_length.SetValue(self.result[ self.list_box_data_folder.GetStringSelection()].shape[0]) self.slider_startpoint.SetRange( 1, self.result[ self.list_box_data_folder.GetStringSelection()].shape[0]) self.slider_startpoint.SetValue(1) winsound.Beep(2500, 300) print("_add data'!") def on_button_update_folder(self, event): runlist = os.listdir('D:\Project\ODEA7\PTrun') self.list_box_data_folder.Set(runlist) # self.listBox.SetSelection(2) print("update'!") def on_button_removedata(self, event): self.result.pop( self.list_box_currentdata.GetSelection() ) # obvious logic error in delete an item added more than once but can be avoided by restricted ops self.list_box_currentdata.Delete( self.list_box_currentdata.GetSelection()) print("_rmdata'!") def on_button_draw(self, event): x = self.result[self.list_box_currentdata.GetStringSelection( )].loc[:, self.list_box_parax.GetStringSelection()] y = self.result[self.list_box_currentdata.GetStringSelection( )].loc[:, self.list_box_paray.GetStringSelection()] endpoint = self.slider_startpoint.GetValue( ) + self.slider_length.GetValue() - 1 if endpoint > self.slider_startpoint.GetMax() - 1: endpoint = self.slider_startpoint.GetMax() - 1 x = x.iloc[self.slider_startpoint.GetValue():endpoint] y = y.iloc[self.slider_startpoint.GetValue():endpoint] axes = self.get_axes() colour, width, style = self._get_styles() axes.plot( x, y, picker= 5, # enable picking, i.e. the user can select this with the mouse color=colour, linewidth=width, linestyle=style) # show the updates self.canvas.draw() def on_button_popupdraw1(self, event): startPer = self.slider_startpoint.GetValue( ) / self.slider_startpoint.GetMax() endPer = (self.slider_startpoint.GetValue() + self.slider_length.GetValue() - 1) / self.slider_startpoint.GetMax() paraList = [ self.list_box_parax.GetStringSelection(), self.list_box_paray.GetStringSelection() ] fig = plt.figure() axes = fig.add_subplot(1, 1, 1) self.draw_on_request(ax=axes, startPer=startPer, endPer=endPer, paraList=paraList) plt.show() def on_button_popupdraw2(self, event): fig = plt.figure() axes = fig.add_subplot(2, 3, 1) self.draw_on_request(ax=axes, paraList=['time', 'xb1']) axes = fig.add_subplot(2, 3, 2) self.draw_on_request(ax=axes, paraList=['time', 'yb1']) axes = fig.add_subplot(2, 3, 3) self.draw_on_request(ax=axes, paraList=['time', 'zb1']) axes = fig.add_subplot(2, 3, 4, projection='3d') self.draw_on_request(ax=axes, paraList=['time', 'xb1', 'yb1'], dim=3) axes = fig.add_subplot(2, 3, 5, projection='3d') self.draw_on_request(ax=axes, paraList=['xb1', 'yb1', 'zb1'], dim=3) axes = fig.add_subplot(2, 3, 6) #self.draw_on_request(ax=axes, paraList = ['time','xb1']) plt.show() def on_button_popupdraw3(self, event): fig = plt.figure() paraLists = [['time', 'xb1'], ['time', 'yb1'], ['time', 'zb1']] index = 1 for paraList in paraLists: axes = fig.add_subplot(2, 2, index) self.draw_on_request(ax=axes, paraList=paraList) index = index + 1 fig = plt.figure() paraLists = [['time', 'xb2'], ['time', 'yb2'], ['time', 'zb2']] index = 1 for paraList in paraLists: axes = fig.add_subplot(2, 2, index) self.draw_on_request(ax=axes, paraList=paraList) index = index + 1 fig = plt.figure() paraLists = [['time', 'xb3'], ['time', 'yb3'], ['time', 'zb3']] index = 1 for paraList in paraLists: axes = fig.add_subplot(2, 2, index) self.draw_on_request(ax=axes, paraList=paraList) index = index + 1 fig = plt.figure() paraLists = [['time', 'xb4'], ['time', 'yb4'], ['time', 'zb4']] index = 1 for paraList in paraLists: axes = fig.add_subplot(2, 2, index) self.draw_on_request(ax=axes, paraList=paraList) index = index + 1 fig = plt.figure() paraLists = [['time', 'x1'], ['time', 'y1'], ['time', 'z1'], ['time', 'theta1x'], ['time', 'theta1y'], ['time', 'theta1z']] index = 1 for paraList in paraLists: axes = fig.add_subplot(2, 3, index) self.draw_on_request(ax=axes, paraList=paraList) index = index + 1 fig = plt.figure() paraLists = [['time', 'x2'], ['time', 'y2'], ['time', 'z2'], ['time', 'theta2x'], ['time', 'theta2y'], ['time', 'theta2z']] index = 1 for paraList in paraLists: axes = fig.add_subplot(2, 3, index) self.draw_on_request(ax=axes, paraList=paraList) index = index + 1 fig = plt.figure() paraLists = [['time', 'thetap'], ['time', 'thetag'], ['time', 'theta1z'], ['time', 'theta2z']] index = 1 for paraList in paraLists: axes = fig.add_subplot(2, 2, index) self.draw_on_request(ax=axes, paraList=paraList) index = index + 1 plt.show() def draw_on_request(self, ax, startPer=0, endPer=1, dim=2, paraList=['time', 'thetap']): drawdata = {} for obj in self.result.items(): #firstPara = self.list_box_parax.GetStringSelection() # = self.list_box_paray.GetStringSelection() key, data = obj drawdata[key] = self.getdrawdata(data, paraList) total_len = drawdata[key].shape[0] # length in dim 0 startPoint = int(total_len * startPer) endPoint = int(total_len * endPer) drawdata[key] = drawdata[key][startPoint:endPoint, :] if dim == 2: drawn = self.drawPlot2d(ax, drawdata[key][:, 0], drawdata[key][:, 1], label=key) ax.set_xlabel(paraList[0]) ax.set_ylabel(paraList[1]) if dim == 3: drawn = self.drawPlot3d(ax, drawdata[key][:, 0], drawdata[key][:, 1], drawdata[key][:, 2], label=key) ax.set_xlabel(paraList[0]) ax.set_ylabel(paraList[1]) ax.set_zlabel(paraList[2]) ax.legend() def getdrawdata(self, data, paraList): nd = numpy.array(data[paraList]) return nd def drawPlot2d(self, ax, x, y, label): drawn = ax.plot( x, y, picker= 5, # enable picking, i.e. the user can select this with the mouse label=label) return drawn def drawPlot3d(self, ax, x, y, z, label): drawn = ax.plot( x, y, z, picker= 5, # enable picking, i.e. the user can select this with the mouse label=label) return drawn def on_button_openfolder(self, event): start_directory = r'D:\Project\ODEA7\PTrun\\' + self.list_box_data_folder.GetStringSelection( ) os.startfile(start_directory) def on_button_opencfg(self, event): start_directory = r'D:\Project\ODEA7\PTrun\\' + self.list_box_data_folder.GetStringSelection( ) + '\cfg.ini' os.startfile(start_directory) def on_file_save(self, event): # save figure as bitmap or PDF self.toolmanager.trigger_tool("save") #################################################################################################################### #################################################################################################################### #################################################################################################################### def on_file_exit(self, event): self.Close() def OnClose(self, event): # delete graphics context to avoid crash self.canvas.cleanup() event.Skip()