def set(self, lo, hi): if float(lo) <= 0.0 and float(hi) >= 1.0: # grid_remove is currently missing from Tkinter! self.tk.call("grid", "remove", self) else: self.grid() Scrollbar.set(self, lo, hi)
def set(self, lo, hi): if self.showing and lo == "0.0" and hi == "1.0": self.grid_remove() self.showing = False elif not self.showing and not (lo == "0.0" and hi == "1.0"): self.grid() self.showing = True Scrollbar.set(self, lo, hi)
def set(self, lo, hi): if float(lo) <= 0.0 and float(hi) >= 1.0: # grid_remove è attualmente assente da Tkinter. # Il metodo tk.call viene infatti richiamato sull'oggetto # scrollbar self.tk.call("grid", "remove", self) else: self.grid() Scrollbar.set(self, lo, hi)
def set(self, lo, hi): if float(lo) <= 0.0 and float(hi) >= 1.0: self.tk.call("grid", "remove", self) else: self.grid() Scrollbar.set(self, lo, hi)
class CompetitorTable(Frame): def __init__(self, parent, db, *args, **kwargs): Frame.__init__(self, parent, *args, **kwargs) self.parent = parent self.db = db self.labelfont = ['Arial', -0.017, 'bold'] self.font = ['Arial', -0.017] self.labelfont[1] = round(self.font[1] * self.parent.parent.parent.SCREEN_HEIGHT) self.font[1] = round(self.font[1] * self.parent.parent.parent.SCREEN_HEIGHT) self.config(bg=LLBLUE) self.tablebg = 'white' self.tableborder = LLBLUE self.labelfg = 'white' self.labelbg = LBLUE self.scrollbar = Scrollbar(self) self.idLabel = Label(self, text='ID', fg=self.labelfg, bg=self.labelbg, font=self.labelfont, height=1, anchor='sw', highlightbackground=self.labelfg) self.fnameLabel = Label(self, text='First Name', fg=self.labelfg, bg=self.labelbg, font=self.labelfont, height=1, anchor='sw') self.lnameLabel = Label(self, text='Last Name', fg=self.labelfg, bg=self.labelbg, font=self.labelfont, height=1, anchor='sw') self.levelLabel = Label(self, text='Level', fg=self.labelfg, bg=self.labelbg, font=self.labelfont, height=1, anchor='sw') self.sexLabel = Label(self, text='Sex', fg=self.labelfg, bg=self.labelbg, font=self.labelfont, height=1, anchor='sw') self.ageLabel = Label(self, text='Age', fg=self.labelfg, bg=self.labelbg, font=self.labelfont, height=1, anchor='sw') self.idLB = Listbox(self, yscrollcommand=self.y_scroll, background=self.tablebg, highlightbackground=self.tableborder, borderwidth=0, width=5, font=self.font) self.fnameLB = Listbox(self, yscrollcommand=self.y_scroll, background=self.tablebg, highlightbackground=self.tableborder, borderwidth=0, width=25, font=self.font) self.lnameLB = Listbox(self, yscrollcommand=self.y_scroll, background=self.tablebg, highlightbackground=self.tableborder, borderwidth=0, width=25, font=self.font) self.levelLB = Listbox(self, yscrollcommand=self.y_scroll, background=self.tablebg, highlightbackground=self.tableborder, borderwidth=0, width=12, font=self.font) self.sexLB = Listbox(self, yscrollcommand=self.y_scroll, background=self.tablebg, highlightbackground=self.tableborder, borderwidth=0, width=2, font=self.font) self.ageLB = Listbox(self, yscrollcommand=self.y_scroll, background=self.tablebg, highlightbackground=self.tableborder, borderwidth=0, width=3, font=self.font) #self.registerButton = Button(self, bg=BLUE, fg='white', text="REGISTER\n\nNEW", font=self.font, width=5, wraplength=1, # borderwidth=1, command=self.register_new) #self.deleteButton = Button(self, bg=BLUE, fg='white', text="DELETE\n\nSELECTED", font=self.font, width=5, wraplength=1, # borderwidth=1, command=self.delete_competitor) #self.editButton = Button(self, bg=BLUE, fg='white', text="EDIT\n\nSELECTED", font=self.font, width=5, wraplength=1, # borderwidth=1, command=self.edit_competitor) self.listboxes = (self.idLB, self.fnameLB, self.lnameLB, self.levelLB, self.sexLB, self.ageLB) for lb in self.listboxes: lb.bind('<Delete>', self.delete_competitor) lb.bind('<Double-Button-1>', self.edit_competitor) self.idLB.bind('<FocusOut>', lambda e: self.idLB.selection_clear(0, 'end')) # these binds ensure that when self.fnameLB.bind('<FocusOut>', lambda e: self.fnameLB.selection_clear(0, 'end')) # leaving the table there self.lnameLB.bind('<FocusOut>', lambda e: self.lnameLB.selection_clear(0, 'end')) # doesn't remain a selection self.levelLB.bind('<FocusOut>', lambda e: self.levelLB.selection_clear(0, 'end')) # despite not having focus self.sexLB.bind('<FocusOut>', lambda e: self.sexLB.selection_clear(0, 'end')) self.ageLB.bind('<FocusOut>', lambda e: self.ageLB.selection_clear(0, 'end')) self.scrollbar.config(command=self.set_scrollables) self.idLabel.grid(row=0, column=0, sticky='nsew') self.fnameLabel.grid(row=0, column=1, sticky='nsew') self.lnameLabel.grid(row=0, column=2, sticky='nsew') self.levelLabel.grid(row=0, column=3, sticky='nsew') self.sexLabel.grid(row=0, column=4, sticky='nsew') self.ageLabel.grid(row=0, column=5, sticky='nsew') self.idLB.grid(row=1, column=0, sticky='nsew') self.fnameLB.grid(row=1, column=1, sticky='nsew') self.lnameLB.grid(row=1, column=2, sticky='nsew') self.levelLB.grid(row=1, column=3, sticky='nsew') self.sexLB.grid(row=1, column=4, sticky='nsew') self.ageLB.grid(row=1, column=5, sticky='nsew') #self.editButton.grid(row=0, column=6, sticky='nsew') #self.registerButton.grid(row=1, column=6, sticky='nsew') #self.deleteButton.grid(row=2, column=6, sticky='nsew') self.scrollbar.grid(row=0, column=7, sticky='nsew', rowspan=2) self.rowconfigure(0, weight=1) self.rowconfigure(1, weight=100) self.columnconfigure(0, weight=5) self.columnconfigure(1, weight=25) self.columnconfigure(2, weight=25) self.columnconfigure(3, weight=12) self.columnconfigure(4, weight=2) self.columnconfigure(5, weight=3) self.competitorRegistrationWindow = None def register_new(self, *args): if not self.competitorRegistrationWindow: self.competitorRegistrationWindow = CompetitorRegistrationWindow(self, self.db) self.competitorRegistrationWindow.protocol('WM_DELETE_WINDOW', self.close_competitor_registration_window) else: print('competitor registration window already open') def close_competitor_registration_window(self): self.competitorRegistrationWindow.destroy() self.competitorRegistrationWindow = None def get_selected_competitor(self): if len(self.idLB.curselection()) > 0: row = self.idLB.curselection() elif len(self.fnameLB.curselection()) > 0: row = self.fnameLB.curselection() elif len(self.lnameLB.curselection()) > 0: row = self.lnameLB.curselection() elif len(self.levelLB.curselection()) > 0: row = self.levelLB.curselection() elif len(self.sexLB.curselection()) > 0: row = self.sexLB.curselection() elif len(self.ageLB.curselection()) > 0: row = self.ageLB.curselection() # this block searches for any selection in all the list boxes return row def delete_competitor(self, *args): try: competitor_id = self.idLB.get(self.get_selected_competitor()) except UnboundLocalError: # occurs when there is no competitor selected print('cannot delete a competitor when there is none selected') return self.db.delete_row(competitor_id) def edit_competitor(self, *args): try: id = self.get_selected_competitor() except UnboundLocalError: # occurs when there is no competitor selected print('cannot edit a competitor when there is none selected') return if id: entrytab = self.parent.parent.parent.entryTab entrytab.routeAttemptsEntryFrame.reset() enable_frame(entrytab.competitorInfoFrame) enable_frame(entrytab.routeAttemptsEntryFrame) entrytab.competitorInfoFrame.fill_competitor(self.idLB.get(id)) # fills info to entry def set_scrollables(self, *args): self.idLB.yview(*args) self.fnameLB.yview(*args) self.lnameLB.yview(*args) self.levelLB.yview(*args) self.sexLB.yview(*args) self.ageLB.yview(*args) def y_scroll(self, *args): # keeps all listboxes at same scroll position always for lb in self.listboxes: lb.yview_moveto(args[0]) self.scrollbar.set(*args) def update_table(self, pattern=None): self.clear_table() if pattern and not pattern == 'Search competitors...': rows = self.db.get_specific(pattern) else: rows = self.db.get_all() for i, row in enumerate(rows): self.idLB.insert(i, row[0]) self.fnameLB.insert(i, row[1]) self.lnameLB.insert(i, row[2]) self.levelLB.insert(i, row[3]) self.sexLB.insert(i, row[4]) self.ageLB.insert(i, row[5]) def clear_table(self): self.idLB.delete(0, 'end') self.fnameLB.delete(0, 'end') self.lnameLB.delete(0, 'end') self.levelLB.delete(0, 'end') self.sexLB.delete(0, 'end') self.ageLB.delete(0, 'end')
class CategoricalStandings(Frame): def __init__(self, parent, db, level=None, sex=None, *args, **kwargs): Frame.__init__(self, parent, *args, **kwargs) self.parent = parent self.db = db self.titleFont = ['Arial', -0.016] self.font = ['Arial', -0.014] self.titleFont[1] = round(self.titleFont[1] * self.parent.parent.parent.parent.SCREEN_HEIGHT) self.font[1] = round(self.font[1] * self.parent.parent.parent.parent.SCREEN_HEIGHT) self.titlebg = LBLUE self.titlefg = 'black' self.selectedAttributes = {'level': level, 'sex': sex} self.titleString = StringVar() self.titleString.set('{} | {} ({})'.format(level, sex, 0)) self.scrollbar = Scrollbar(self) self.scrollbar.config(command=self.set_scrollables) self.title = Label(self, textvariable=self.titleString, bg=self.titlebg, fg=self.titlefg, font=self.titleFont) self.idLB = Listbox(self, yscrollcommand=self.y_scroll, borderwidth=0, width=3, activestyle='none', font=self.font) self.fnameLB = Listbox(self, yscrollcommand=self.y_scroll, borderwidth=0, width=15, activestyle='none', font=self.font) self.lnameLB = Listbox(self, yscrollcommand=self.y_scroll, borderwidth=0, width=15, activestyle='none', font=self.font) self.scoreLB = Listbox(self, yscrollcommand=self.y_scroll, borderwidth=0, width=10, activestyle='none', font=self.font) self.listboxes = (self.idLB, self.fnameLB, self.lnameLB, self.scoreLB) for lb in self.listboxes: lb.bind('<Delete>', self.delete_competitor) lb.bind('<Double-Button-1>', self.edit_competitor) self.title.grid(row=0, column=0, columnspan=5, sticky='nsew') self.idLB.grid(row=1, column=0, sticky='nsew') self.fnameLB.grid(row=1, column=1, sticky='nsew') self.lnameLB.grid(row=1, column=2, sticky='nsew') self.scoreLB.grid(row=1, column=3, sticky='nsew') self.scrollbar.grid(row=1, column=4, sticky='nsew') self.rowconfigure(0, weight=1) self.rowconfigure(1, weight=6) self.columnconfigure(0, weight=3) self.columnconfigure(1, weight=10) self.columnconfigure(2, weight=18) self.columnconfigure(3, weight=10) self.columnconfigure(4, weight=1) self.update_table() def set_scrollables(self, *args): self.idLB.yview(*args) self.fnameLB.yview(*args) self.lnameLB.yview(*args) self.scoreLB.yview(*args) def y_scroll(self, *args): for lb in self.listboxes: lb.yview_moveto(args[0]) self.scrollbar.set(*args) def update_table(self, pattern=None): self.clear_table() if pattern and not pattern == 'Search competitors...': rows = self.db.get_specific_rows_by_score(pattern=pattern, **self.selectedAttributes) else: rows = self.db.get_specific_rows_by_score(**self.selectedAttributes) if rows: self.titleString.set('{} | {} ({})'.format(self.selectedAttributes['level'], self.selectedAttributes['sex'], len(rows))) for i, row in enumerate(rows): self.idLB.insert(i, row[0]) self.fnameLB.insert(i, row[1]) self.lnameLB.insert(i, row[2]) self.scoreLB.insert(i, row[3]) else: self.titleString.set('{} | {} ({})'.format(self.selectedAttributes['level'], self.selectedAttributes['sex'], 0)) def get_selected_competitor(self): if len(self.idLB.curselection()) > 0: row = self.idLB.curselection() elif len(self.fnameLB.curselection()) > 0: row = self.fnameLB.curselection() elif len(self.lnameLB.curselection()) > 0: row = self.lnameLB.curselection() elif len(self.scoreLB.curselection()) > 0: row = self.scoreLB.curselection() # this block searches for any selection in all the list boxes return row def edit_competitor(self, *args): try: id = self.get_selected_competitor() except UnboundLocalError: # occurs when there is no competitor selected print('cannot edit a competitor when there is none selected') return if id: entrytab = self.parent.parent.parent.parent.entryTab entrytab.routeAttemptsEntryFrame.reset() enable_frame(entrytab.competitorInfoFrame) enable_frame(entrytab.routeAttemptsEntryFrame) entrytab.competitorInfoFrame.fill_competitor(self.idLB.get(id)) # fills info to entry def delete_competitor(self, *args): try: competitor_id = self.idLB.get(self.get_selected_competitor()) except UnboundLocalError: # occurs when there is no competitor selected print('cannot delete a competitor when there is none selected') return self.db.delete_row(competitor_id) def clear_table(self): self.idLB.delete(0, 'end') self.fnameLB.delete(0, 'end') self.lnameLB.delete(0, 'end') self.scoreLB.delete(0, 'end')
class DataViewer: ''' DataViewer ====================== David Miller, 2020 The University of Sheffield 2020 Wrapper GUI for a Matplotlib Figure showing the data given on creation. Called by HDF5Viewer when the user double clicks on a dataset. This GUI is designed a means of performing basic inspections of data stored in HDF5 files. If users want to perform something intensive or particular custom, they are advised to do so elsewhere. On creation, the DataViewer opens up the file specified by filename and accesses the dataset specified by the path dataName. It then decides how to plot the data based on the number of dimensions in the dataset. The used plots are as follows using their respective default options: |No. dims | Plots | |---------|----------| | 1 | Line | | 2 | Contourf | | 3 | Contourf | In the case of three dimensions, a scrollbar is added on top of the plot and provides a means for the user to select which 2D slice of the 3D dataset to show. The scrollbar only supports drag operations. Any higher dimensional data is ignored. The title above the Figure displays the name of the dataset and which index, if any is being displayed. Methods ------------------------- on_key_press(event): Handler for key presses used on the Matploblib canvas scroll_data(self,*args): Handler for changing which slice of a 3D dataset is displayed. A new data index is chosen based on where the scrollbar cursor is dragged to. The index is calculated by multiplying the scrollbar positon [0,1] by the number of frames in the dataset and then converted to an integer. Updates the title and canvas on exit. Sets the scrollbar position to where the user left it. Currently this only has support for clicking and dragging the scrollbar cursor. Other operations are ignored. ''' # using filename and name of dataset # create a figure and display the data def __init__(self,master,dataName,filename): self.master = master # save creation options self.dataName = dataName self.filename = filename # set title self.master.title("Data Viewer") # current colormap, default self.curr_cmap = getattr(matplotlib.cm,matplotlib.rcParams['image.cmap']) # currnt line color self.curr_lcol = matplotlib.rcParams['axes.prop_cycle'].by_key()['color'][0] ## menu bar for customisation # root menu menu = Menu(master) # options menu optsmenu = Menu(menu,tearoff=0) # label for graph self.title = StringVar() self.title.set(f'Displaying {dataName}') self.graph_title = Label(master,textvariable=self.title) self.graph_title.pack(side=TOP,pady=10,padx=10) # create figure self.fig = Figure(figsize=(5,5),dpi=100) self.axes = self.fig.add_subplot(111) # get data from dataset and plot data with h5py.File(filename,mode='r') as f: self.data_shape = f[dataName].shape # if the data is 1D, plot as line if len(self.data_shape)==1: self.axes.plot(f[dataName][()],self.curr_lcol) optsmenu.add_command(label="Set color",command=self.set_color) # if data is 2D, plot as filled contour elif len(self.data_shape)==2: self.axes.contourf(f[dataName][()],cmap=self.curr_cmap) optsmenu.add_command(label="Set colormap",command=self.set_colormap) # if data is 3D plot as contourf, but also add a scrollbar for navigation elif len(self.data_shape)==3: optsmenu.add_command(label="Set colormap",command=self.set_colormap) # create scroll bar for viewing different slices self.plot_scroll=Scrollbar(master,orient="horizontal",command=self.scroll_data) # add too gui self.plot_scroll.pack(side=TOP,fill=BOTH,expand=True) # plot first slice of data self.axes.contourf(f[dataName][:,:,0],cmap=self.curr_cmap) # create index for current depth index self.depth_index = 0 self.title.set(f"Displaying {dataName} [{self.depth_index}]") # add to root menu menu.add_cascade(label="Options",menu=optsmenu) self.master.config(menu=menu) # create canvas to render figure self.fig_canvas = FigureCanvasTkAgg(self.fig,self.master) # update result self.fig_canvas.draw() # update canvas to set position and expansion options self.fig_canvas.get_tk_widget().pack(side=TOP,fill=BOTH,expand=True) ## add matplotlib toolbar self.fig_toolbar = NavigationToolbar2Tk(self.fig_canvas,self.master) self.fig_toolbar.update() # add to gui. always one row below the canvas self.fig_canvas._tkcanvas.pack(side=TOP,fill=BOTH,expand=True) ## add key press handlers self.fig_canvas.mpl_connect("key_press_event",self.on_key_press) # ensure elements are expandable in grid num_cols,num_rows = master.grid_size() for c in range(num_cols): master.columnconfigure(c,weight=1) for r in range(num_rows): master.rowconfigure(r,weight=1) # finish any idle tasks and set the minimum size of the window to cur master.update_idletasks() master.after_idle(lambda: master.minsize(master.winfo_width(), master.winfo_height())) # handler for matplotlib keypress events def on_key_press(event): key_press_handler(event,self.fig_canvas,self.fig_toolbar) # handler for using the scrollbar to view slices of data def scroll_data(self,*args): #print(args) # if the user has dragged the scrollbar if args[0] == "moveto": # args is only one element in this case and is a number between 0 and 1 # 0 is left most position and 1 is right most position # the data index is calculated as this number times depth and converted to integer self.depth_index = int(float(args[1])*self.data_shape[2]) # if index exceeds dataset limits # correct it if self.depth_index>=self.data_shape[-1]: self.depth_index = self.data_shape[-1]-1 # set the scrollbar position to where the user dragged it to self.plot_scroll.set(float(args[1]),(self.depth_index+1)/self.data_shape[2]) # reopen file with h5py.File(self.filename,mode='r') as f: self.axes.contourf(f[self.dataName][:,:,self.depth_index],cmap=self.curr_cmap) # update canvas self.fig_canvas.draw() # update title self.title.set(f"Displaying {self.dataName} [{self.depth_index}]") def set_colormap(self): ch = ColormapChooser(self.master).show() if ch: self.curr_cmap = ch self.scroll_data("moveto",str(self.plot_scroll.get()[0])) def update_line(self): # remove first line from plot self.axes.lines.pop(0) with h5py.File(self.filename,mode='r') as f: self.axes.plot(f[self.dataName][()],self.curr_lcol) # update canvas self.fig_canvas.draw() def set_color(self): col = colorchooser.askcolor() if col[1]: self.curr_lcol = col[1] self.update_line()
class Plotter(Canvas): def __init__(self, parent: tk.Frame = None, interval=20, linenum=10, save=True, drag=True, adaptation=True, lengthx=20000): Canvas.__init__(self, parent, bg='#345', closeenough=2) parent.update_idletasks() self.pack(fill=tk.BOTH, expand=True) self.update_idletasks() self.hScroll = Scrollbar(parent, orient='horizontal', command=self.hScrolled) self.hScroll.pack(side=tk.BOTTOM, fill=tk.X) self.configure(yscrollcommand=None, xscrollcommand=self._canvashScrolled) self.bind("<Button-1>", self.__mouseDown) self.bind('<Button-3>', self.__mouseRightDown) self.bind("<MouseWheel>", self.__mouseScale) self.bind("<B1-Motion>", self.__mouseDownMove) self.bind("<B1-ButtonRelease>", self.__mouseUp) self.bind("<Enter>", self.__mouseEnter) self.bind("<Leave>", self.__mouseLeave) self.bind("<Motion>", self.__mouseHoverMove) self["xscrollincrement"] = 1 self["yscrollincrement"] = 1 self.__dataList = [] self._vernierdata = [0] * linenum self.x = 0 self.scrollx = 3.0 self.offsety = 0 self._interval = interval # main loop interval to handle input data self._lengthx = lengthx # scrollregion x self._vernierOn = False self._doubleVernierOn = False self._dragOn = drag # enable mouse move items self._gridOn = True # draw grid lines self._loopOn = True self._tipOn = False self._adaptation = adaptation self._vernier = None self._vernierX = -1 self._lastVernierCanvasX = -1 # double verniers self._rightVernier = None self._leftVernier = None self._rightVernierX = -1 self._leftVernierX = -1 # force update double-vernier by reset the value to -1 self._lastRightVernierCanvasX = -1 self._lastLeftVernierCanvasX = -1 self._doubleVernierDistance = tk.IntVar() # 0-any distance self._save = save # 保存数据 self._dataWriter = None # 数据保存writer self._datafile = '' # 保存文件名称 self._scrollOn = False self._lineNum = linenum self._firststart = True self._scrollDelay = 0 self._popLock = 0 # 防止多个toplevel # this data is used to keep track of an item being dragged # in this app, only vertical position self._drag_data = { "sx": 0, "sy": 0, "x": 0, "y": 0, "item": None, "type": 'signal' } self._lines = [] self.after(200, self.__initCanvas) def __main(self): if self._drag_data['item'] is None: pass else: _item = self._drag_data['item'] if _item._lastoffsety != _item._offsety: pass self.after(20, self.__main) def __loop(self): '''mainloop ''' if self._loopOn: for _line in self._lines: # _line.adaptation() if _line.getLineLen() > 0: _line.plot() self.setVernierValue() self.setDoubleVernierValue() self.autoScrollToEnd() self.setSignalTip() # always make vernier at top if self._doubleVernierOn: self.tag_raise('double_valuey') self.tag_raise('left_vernier') self.tag_raise('right_vernier') if self._vernierOn: self.tag_raise('valuey') self.tag_raise('vernier') self.after(self._interval, self.__loop) def __initCanvas(self): self.update_idletasks() self._width = self.winfo_width() self._height = self.winfo_height() self._originHeight = self.winfo_height() self._initHeight = self.winfo_height() self.drawGridLines(50) self.configure(scrollregion=(0, 0, self._lengthx, self.winfo_height())) self.xview_moveto(0) self.bind("<Configure>", self.__resize) self.__loop() # --------------- global API --------------- def setLoopStatus(self, on=False): '''set the mainloop status Keyword Arguments: on {bool} -- status (default: {False}) ''' self._loopOn = on def setDrag(self, on=False): '''enable or disable the mouse drag Keyword Arguments: on {bool} -- status (default: {False}) ''' self._dragOn = on def setVernier(self, on=False): '''show or hide coordinates when mouse move Keyword Arguments: on {bool} -- true for show vernier (default: {False}) ''' self._vernierOn = on def setDoubleVernier(self, on=False): '''enable or disable double vernier function Keyword Arguments: on {bool} -- true for enable double vernier (default: {False}) ''' self._doubleVernierOn = on def setInterval(self, interval=20): '''set mainloop interval Keyword Arguments: interval {int} -- loop interval (default: {20}) ''' self._interval = interval def setGrid(self, on=True): '''show or hide the grids Keyword Arguments: on {bool} -- true for show grid lines (default: {True}) ''' self._gridOn = on def setSignalTip(self): if self._tipOn and len(self._lines) > 0: self.delete('sigtip') maxlen = max([len(line.name) for line in self._lines]) gap = (maxlen + 1) * 7 gap = max(gap, 100) for i, line in enumerate(self._lines): color = line.color if line.hidden: color = "#aaa" _row = int(i / 5) _column = i % 5 self.create_rectangle(self.canvasx(10) + gap * _column, 10 + 20 * _row, gap * _column + self.canvasx(10) + 10, 20 * _row + 20, fill=color, tags=( 'sigtip', 'tip' + line.id, )) _text = line.id if line.name != '' and line.name is not None: _text += '-' + line.name self.create_text(self.canvasx(10) + 15 + gap * _column, 15 + 20 * _row, text=_text, fill=color, font=('微软雅黑', 8), tags=( 'sigtip', 'tip' + line.id, ), anchor='w') else: self.delete('sigtip') def setVernierValue(self): '''draw vernier and valuey tip by given vernierx ''' x = self.canvasx(self._vernierX) if self._lastVernierCanvasX == x: return self.delete('valuey') self.delete('vernier') if not self._vernierOn or self._vernierX < 0: self._lastVernierCanvasX = -1 return self.drawOneVernierValue(x) self._lastVernierCanvasX = x def setDoubleVernierValue(self): x1 = self.canvasx(self._rightVernierX) x2 = self.canvasx(self._leftVernierX) if self._lastRightVernierCanvasX == x1 and self._lastLeftVernierCanvasX == x2: return self.delete('double_valuey') self.delete('left_vernier') self.delete('right_vernier') if not self._doubleVernierOn: return self.drawOneVernierValue(x1, linetag='right_vernier', texttag='double_valuey') self.drawOneVernierValue(x2, linetag='left_vernier', texttag='double_valuey') self.drawTriangle(x1, tagname='right_vernier') self.drawTriangle(x2, tagname='left_vernier') self._lastRightVernierCanvasX = x1 self._lastLeftVernierCanvasX = x2 def drawOneVernierValue(self, x, linecolor='#FFFAFA', textcolor='#FFFZFA', linetag='vernier', texttag='valuey'): self.create_line((x, 0, x, self._height), fill=linecolor, tags=(linetag, )) self.create_text(x + 5, self._height - 12, text='X-' + str(self.canvasx(x)), fill='#808090', font=('微软雅黑', 7), tags=(texttag, ), anchor='w') for _line in self._lines: if x > _line.getLineLen() or _line.hidden: continue tipy = _line.getTipY(x) valuey = _line.getScreenY(x) y = valuey # self.create_rectangle(x+5, y-15, x+15, y-5, tags=('valuey', ), fill=_line.color, outline=_line.color) self.create_text(x + 5, y - 5, text=tipy, fill=textcolor, font=('微软雅黑', 9), tags=(texttag, ), anchor='w') def drawTriangle(self, x, tagname='double_vernier', color='#FFFZFA'): self.create_polygon([x - 5, 0, x + 5, 0, x, 10], outline=color, fill=color, tags=(tagname, )) self.create_polygon([ x - 5, self._height - 2, x + 5, self._height - 2, x, self._height - 12 ], outline=color, fill=color, tags=(tagname, )) def setScroll(self, on=True): self._scrollOn = on def clear(self): # Fill strip with background color self._loopOn = False self._lines = [] self.drawGridLines() def stopLoop(self): self._loopOn = False def drawGridLines(self, width=50): '''draw grid lines Keyword Arguments: width {int} -- distance between grid lines (default: {50}) ''' _height = self.winfo_height() offsety = math.floor(_height / 2) + 1 offsetx = self._lengthx if self._lengthx > self.winfo_width( ) else self.winfo_width() self.delete('grid') self.create_line((0, offsety, offsetx, offsety), fill="#708090", dash=(2, 6), tags=('grid')) if self._gridOn is True: xnum = math.floor(offsetx / width) for i in range(xnum): self.create_line((i * 50 + 50, 0, i * 50 + 50, _height), fill="#708090", dash=(1, 6), tags=('grid')) ynum = math.floor(_height / (2 * width)) for j in range(ynum): self.create_line( (0, j * 50 + 50 + offsety, offsetx, j * 50 + 50 + offsety), fill="#708090", dash=(2, 6), tags=('grid')) self.create_line((0, -j * 50 - 50 + offsety, offsetx, -j * 50 - 50 + offsety), fill="#708090", dash=(2, 6), tags=('grid')) def getSignalbyId(self, id): '''get signal by given id Arguments: id {str} -- signal id Returns: signal -- signal ''' for _line in self._lines: if _line.id == id: return _line return None def getSignalbyTags(self, tags): '''get signal by given tag list Arguments: tags {list} -- signal's tag list Returns: [signal] -- signal ''' for _line in self._lines: if _line.id in tags: # 求交集 return _line return None def sortSignals(self): '''sort signals by order in self._lines ''' self.delete('auxiliary') _height = self._height - 40 # 上下各留20像素 _lines = [ line for line in self._lines if (len(line._points) > 0 and line.hidden is not True) ] if len(_lines) == 0: return _blockHeight = _height / len(_lines) self._lastLeftVernierCanvasX = -1 for i, _line in enumerate(_lines): desty = 20 + (i + 1) * _blockHeight - _blockHeight / 2 y = self._height / 2 - desty self.create_line(0, desty, self._lengthx, desty, fill='red', dash=(6, 6), tags=('auxiliary', )) _offsety = (_line.getMaxY() + _line.getMinY()) / 2 - y _offsety = _offsety + (self._height - self._originHeight) / 2 _line.setOffsetY(_offsety) _nowHeight = (_line.getMaxY() - _line.getMinY()) * \ _line.getScaleY() _delta = 1 if _nowHeight > 0: _delta = (_blockHeight - 10) / _nowHeight _line.setScaleY(_delta * _line.getScaleY()) def resortSignals(self): # if self.find_withtag('auxiliary'): self.delete('auxiliary') self._lastLeftVernierCanvasX = -1 for _line in self._lines: _line.restore() def stopSave(self): self._save = False if self._dataWriter is not None: self._dataWriter.close() def loadData(self, filename): '''load csv file stored Arguments: filename {str} -- csv file name ''' # if self._loopOn is True: # return self._width = self.winfo_width() self._height = self.winfo_height() self._originHeight = self.winfo_height() self._initHeight = self.winfo_height() with open(filename, mode='r', encoding='gbk') as f: reader = csv.reader(f) ids = next(reader) names = next(reader) historys = next(reader) colors = next(reader) decimals = next(reader) units = next(reader) valtips = next(reader) if set([ len(ids), len(historys), len(colors), len(decimals), len(units), len(valtips) ]) == 1: logger.warning('error param count in csv file') return self._lines = [] num = len(ids) for i in range(num): _tips = {} if valtips[i] != '{}': # print(valtips[i]) _tips = json.loads(valtips[i]) _line = Signal(canvas=self, id=ids[i], name=names[i], history=20000, color=colors[i], unit=str(units[i]), decimal=int(decimals[i]), valuetip=_tips) self._lines.append(_line) with open(filename, mode='r', encoding='gbk') as f: lines = f.readlines() datalen = len(lines) data = lines[7:] for i, _line in enumerate(self._lines): _line.setHistory(datalen) _line.addPoints( [float(row.strip().split(',')[i]) for row in data]) def _selectOneSignal(self, id): for _line in self._lines: if _line.id == id: _line.setSelected(True) else: _line.setSelected(False) def deleteSignal(self, id): for _line in self._lines: if _line.id == id: _line.setSelected(False) _line.hide() def initLineList(self, lineNumber, names=[], decimals=[], valuetips=[], units=[]): '''init all lines by given number and other params Arguments: number {int} -- number of lines ''' self._width = self.winfo_width() self._height = self.winfo_height() self._originHeight = self.winfo_height() self._initHeight = self.winfo_height() self._lines = [] # self._currentId = None # 当前选择曲线id _list = list(colorList) _lineid = [] _linenames = [] _linedecimals = [] _linevaluetips = [] _lineunits = [] self.x = 0 _lineid = list(range(0, lineNumber)) if len(names) == lineNumber: _linenames = names else: _linenames = [''] * lineNumber if len(decimals) == lineNumber: _linedecimals = decimals else: _linedecimals = [0] * lineNumber if len(valuetips) == lineNumber: _linevaluetips = valuetips else: _linevaluetips = [{}] * lineNumber if len(units) == lineNumber: _lineunits = units else: _lineunits = [''] * lineNumber while len(self._lines) < lineNumber: _len = len(self._lines) _index = random.randint(0, len(_list) - 1) _random = _list[_index] _list.remove(_random) _line = Signal(id=str(_lineid[_len]), name=str(_linenames[_len]), canvas=self, history=20000, color=_random, decimal=_linedecimals[_len], unit=_lineunits[_len], valuetip=dict(_linevaluetips[_len])) self._lines.append(_line) if self._save: self._datafile = 'data/' + \ time.strftime("%Y_%m_%d_%H_%M_%S", time.localtime()) + '.csv' # logger.debug(self._datafile) with open(self._datafile, mode='a', encoding='gbk', newline='') as f: self._dataWriter = csv.writer(f) self._dataWriter.writerow([line.id for line in self._lines]) self._dataWriter.writerow([line.name for line in self._lines]) self._dataWriter.writerow( [line.getHistory() for line in self._lines]) self._dataWriter.writerow([line.color for line in self._lines]) self._dataWriter.writerow( [line.decimal for line in self._lines]) self._dataWriter.writerow([line.unit for line in self._lines]) self._dataWriter.writerow( [json.dumps(line.valuetip) for line in self._lines]) def addLinePoint(self, values=[]): if len(values) != len(self._lines): return for i, _line in enumerate(self._lines): _line.addPoint(values[i]) if self._save: with open(self._datafile, mode='a', encoding='gbk', newline='') as f: self._dataWriter = csv.writer(f) self._dataWriter.writerow(values) self.x = self.x + 1 # --------------- inner API --------------- def hScrolled(self, *args): self.xview(*args) # delta = self.canvasx(10) - self.scrollx # print(self.canvasx(10), delta) # for _tip in self.find_withtag('sigtip'): # self.move(_tip, delta, 0) # self.scrollx = self.canvasx(10) def autoScrollToEnd(self): '''scrool canvas to signal end when needed ''' if self._scrollOn is True: try: maxlen = max([_line.getLineLen() for _line in self._lines]) except Exception: maxlen = 0 if maxlen < self._width * 0.75: maxlen = 0 elif maxlen > self._lengthx - self._width * 0.25: maxlen = int(self._lengthx - self._width * 0.25) else: maxlen = maxlen - int(self._width * 0.75) fraction = maxlen / self._lengthx self.xview_moveto(fraction) else: pass def _canvashScrolled(self, *args): self.hScroll.set(*args) # delta = self.canvasx(10) - self.scrollx # for _tip in self.find_withtag('sigtip'): # self.move(_tip, delta, 0) # self.scrollx = self.canvasx(10) def __resize(self, event): # self.update_idletasks() if len(self._lines) == 0: self._originHeight = self.winfo_height() _deltay = (self.winfo_height() - self._height) / 2 self.drawGridLines(50) for _line in self._lines: _line.setSelected(False) _line.moveY(_deltay) for _item in self.find_withtag('auxiliary'): self.move(_item, 0, _deltay) self._height = self.winfo_height() self._width = self.winfo_width() self._lastRightVernierCanvasX = -1 # redraw double vernier def __mouseScale(self, event): if self._drag_data["item"] is None: return if (event.delta > 0): self._drag_data["item"].scaleY(1.2) else: self._drag_data["item"].scaleY(0.8) # def updateLines(self, lineList:list): def __mouseDown(self, event): for signal in self._lines: signal.setSelected(selected=False) self._drag_data['item'] = None # print('down', event.x, event.y, self.canvasx(0)) # _items = self.find_closest(event.x, event.y) # print('closest', self.gettags(_items[0])) _x = self.canvasx(0) _items1 = self.find_overlapping(event.x - 3 + _x, event.y - 3, event.x + 3 + _x, event.y + 3) _currentItem = None for _item in _items1: _tags = self.gettags(_item) if 'signal' in _tags: _currentItem = _item self._drag_data['type'] = 'signal' break elif 'sigtip' in _tags: # if selected item is sigtip, show or hide the signal selectedid = _tags[1][3:] print(selectedid, _tags) _line = self.getSignalbyId(selectedid) if _line.hidden: _line.show() else: _line.hide() break elif 'left_vernier' in _tags: _currentItem = _item self._drag_data['type'] = 'left_vernier' break elif 'right_vernier' in _tags: _currentItem = _item self._drag_data['type'] = 'right_vernier' break if _currentItem is not None: if self._drag_data['type'] == 'signal': _tags = self.gettags(_currentItem) self._drag_data['item'] = self.getSignalbyTags(_tags) self._drag_data['x'] = 0 self._drag_data['y'] = event.y if hasattr(self._drag_data['item'], 'id'): self._selectOneSignal(self._drag_data['item'].id) self.tag_raise( self._drag_data['item'].id) # raise this signal else: self._drag_data['item'] = _currentItem self._drag_data['x'] = event.x self._drag_data['y'] = event.y self._drag_data['sy'] = event.y self._drag_data['sx'] = event.x def __mouseRightDown(self, event): if self._doubleVernierOn and self._popLock == 0: self._popLock = 1 self._paramTop = Toplevel() w = 260 h = 100 # 计算 x, y 位置 x = (self._width / 2) - (w / 2) y = (self._height / 2) - (h / 2) - 20 self._paramTop.attributes('-alpha', 1.0) self._paramTop.attributes('-topmost', True) self._paramTop.positionfrom(who='user') self._paramTop.resizable(width=False, height=False) self._paramTop.title('设置距离') self._paramTop.geometry('{}x{}+{}+{}'.format(w, h, int(x), int(y))) tk.Entry(self._paramTop, textvariable=self._doubleVernierDistance).pack(fill=tk.X) _row3 = tk.Frame(self._paramTop) _row3.pack(pady=20, fill=tk.BOTH, expand=tk.TRUE) tk.Button(_row3, text='确定', font=('微软雅黑', 9), width=6, command=self._setDoubleVernierDistance).pack( side=tk.LEFT, padx=40) tk.Button(_row3, text='取消', font=('微软雅黑', 9), width=6, command=self._cancelDoubleVernierDistance).pack( side=tk.LEFT, padx=40) self._paramTop.protocol('WM_DELETE_WINDOW', self._cancelDoubleVernierDistance) def __mouseDownMove(self, event): '''Handle dragging of an object''' if self._drag_data["item"] is None: return if self._drag_data['type'] == 'signal': if self._dragOn is not True: return # compute how much the mouse has moved # delta_x = 0 # event.x - self._drag_data["x"] delta_y = event.y - self._drag_data["y"] # offset_y = event.y - self._drag_data["sy"] self._drag_data["item"].moveY(delta_y) # record the new position self._drag_data["x"] = 0 self._drag_data["y"] = event.y else: if self._doubleVernierOn: _distance = self._doubleVernierDistance.get() if self._drag_data['type'] == 'right_vernier': self._rightVernierX = event.x if _distance != 0: self._leftVernierX = self._rightVernierX - _distance elif self._rightVernierX <= self._leftVernierX: self._rightVernierX = self._leftVernierX + 1 elif self._drag_data['type'] == 'left_vernier': self._leftVernierX = event.x if _distance != 0: self._rightVernierX = self._leftVernierX + _distance elif self._rightVernierX <= self._leftVernierX: self._leftVernierX = self._rightVernierX - 1 def __mouseHoverMove(self, event): if self._vernierOn: # delta_x = event.x - self._vernierX # self.after(80, self.__delayMove, delta_x) self._vernierX = event.x # print(self.gettags(self.find_withtag(tk.CURRENT))) def __delayMove(self, delta_x): if self._vernier: self.move(self._vernier, delta_x, 0) def __mouseUp(self, event): # print(event.x, event.y) # force update the double-vernier self._lastLeftVernierCanvasX = -1 pass def __mouseEnter(self, event): if self._vernierOn: # self._vernier = self.create_line( # (event.x, 0, event.x, self._height), fill="#FFFAFA") self._vernierX = event.x pass def __mouseLeave(self, event): self.delete('vernier') self._vernierX = -1 def _setDoubleVernierDistance(self): self._popLock = 0 _distance = self._doubleVernierDistance.get() if _distance > self._width - 10: _distance = self._width - 10 self._doubleVernierDistance.set(_distance) print('distance', _distance) if _distance != 0: if self._leftVernierX + _distance < self._width: self._rightVernierX = self._leftVernierX + _distance elif self._rightVernierX - _distance > 0: self._leftVernierX = self._rightVernierX - _distance else: self._leftVernierX = 5 self._rightVernierX = self._leftVernierX + _distance self._paramTop.destroy() def _cancelDoubleVernierDistance(self): self._doubleVernierDistance.set(0) self._popLock = 0 self._paramTop.destroy() # --------------- below code just for testing the function --------------- def _autoTest(self): if len(self._lines) != 5: self._lines = [] valuetip = {"0": "disable", "1": "enable", "2": "rsvd"} self._lines.append( Signal(id='Y1', name='A', canvas=self, history=20000, color='#ff4')) self._lines.append( Signal(id='Y2', name='B', canvas=self, history=20000, color='#f40', decimal=2, unit='A')) self._lines.append( Signal(id='Y3', name='B999999', canvas=self, history=20000, color='#4af')) self._lines.append( Signal(id='Y4', name='A', canvas=self, history=20000, color='#080')) self._lines.append( Signal(id='Y5', name='C', canvas=self, history=20000, color='purple', valuetip=valuetip)) if self._save: self._datafile = 'data/' + \ time.strftime("%Y_%m_%d_%H_%M_%S", time.localtime()) + '.csv' logger.debug(self._datafile) with open(self._datafile, mode='a', encoding='gbk', newline='') as f: self._dataWriter = csv.writer(f) self._dataWriter.writerow( [line.id for line in self._lines]) self._dataWriter.writerow( [line.name for line in self._lines]) self._dataWriter.writerow( [line.getHistory() for line in self._lines]) self._dataWriter.writerow( [line.color for line in self._lines]) self._dataWriter.writerow( [line.decimal for line in self._lines]) self._dataWriter.writerow( [line.unit for line in self._lines]) self._dataWriter.writerow( [json.dumps(line.valuetip) for line in self._lines]) y1 = 10 * math.sin(0.02 * math.pi * self.x) y2 = 20 + 5 * (random.random() - 0.5) y3 = 50 y4 = -30 if (self.x % 20 == 0) else 20 y5 = random.randint(-100, 2000) self._lines[0].addPoint(y1) self._lines[1].addPoint(y2) self._lines[2].addPoint(y3) self._lines[3].addPoint(y4) self._lines[4].addPoint(y5) if self._save: with open(self._datafile, mode='a', encoding='gbk', newline='') as f: self._dataWriter = csv.writer(f) self._dataWriter.writerow([y1, y2, y3, y4, y5]) self.x = self.x + 1 self.after(10, self._autoTest) def toggleVernier(self): self._vernierOn = True if self._vernierOn is False else False if self._vernierOn is True: self._doubleVernierOn = False self._lastRightVernierCanvasX = -1 self._lastLeftVernierCanvasX = -1 def toggleDoubleVernier(self): self._doubleVernierOn = True if self._doubleVernierOn is False else False if self._doubleVernierOn is True: self._leftVernierX = 5 self._rightVernierX = self._width - 5 self._vernierOn = False else: self._lastRightVernierCanvasX = -1 self._lastLeftVernierCanvasX = -1 def toggleDrag(self): self._dragOn = True if self._dragOn is False else False def toggleGrid(self): self._gridOn = True if self._gridOn is False else False self.drawGridLines(50) def _selectTest(self): _line = self._lines[random.randint(0, 3)] if _line.selected is True: _line.setSelected(False) else: _line.setSelected(True) def toggleTip(self): self._tipOn = True if self._tipOn is False else False def _restoreTest(self): for _line in self._lines: _line.restore() def _scaleTest(self): _scale = random.randint(1, 3) if self._drag_data['item'] is not None: self._drag_data['item'].scaleY(_scale) def _sortTest(self): self.sortSignals() def _resortTest(self): self.resortSignals() def _scrollTest(self): self._scrollOn = True if self._scrollOn is False else False def _adapationTest(self): for _line in self._lines: if not _line.hidden: _line.adaptation() def _loadTest(self): filename = filedialog.askopenfilename(title='载入数据', filetypes=[('csv', '*.csv')]) self.loadData(filename) def _clear(self): if len(self._lines) != 0: self._lines = []
class ListFrame(Frame): def __init__(self, master, root, recursive, regex, repl, options): Frame.__init__(self, master) self._left_list = Listbox(self) self._left_list.pack(side=LEFT, fill=BOTH, expand=True) self._right_list = Listbox(self) self._right_list.pack(side=LEFT, fill=BOTH, expand=True) self._right_scroll = Scrollbar(self._right_list, orient=VERTICAL) self._right_list.config(yscrollcommand=self._right_scroll.set) self._right_scroll.config(command=self._right_list.yview) self._scrollbar = Scrollbar(self, orient=VERTICAL, command=self._scroll_scrollbar) self._scrollbar.pack(side=RIGHT, fill=Y) self._left_list.config(yscrollcommand=self._scroll_left) self._right_list.config(yscrollcommand=self._scroll_right) self._regex = regex self._repl = repl self._settings = options self._root = None self._recursive = None self._names = None self._mapping = None self._errors = None self._update_root(root, recursive) master.bind('<<RootUpdate>>', self._on_root_update) master.bind('<<RegexUpdate>>', self._on_regex_update) master.bind('<<OptionsUpdate>>', self._on_options_update) master.bind('<<Refresh>>', self._on_refresh) def _scroll_left(self, sfrom, sto): self._scrollbar.set(sfrom, sto) self._right_list.yview('moveto', sfrom) def _scroll_right(self, sfrom, sto): self._scrollbar.set(sfrom, sto) self._left_list.yview('moveto', sfrom) def _scroll_scrollbar(self, *args): self._left_list.yview(*args) self._right_list.yview(*args) def _on_root_update(self, event): self._update_root(event.widget.root, event.widget.recursive) def _on_regex_update(self, event): self._update_regex(event.widget.regex, event.widget.repl) def _on_refresh(self, event): self._update_root(self._root, self._recursive) def _on_options_update(self, event): self._settings = event.widget.options self._update_lists() def _update_regex(self, regex, repl): self._regex = regex self._repl = repl self._update_lists() def _is_type_enabled(self, ftype): if ftype is True: return self._settings.files elif ftype is False: return self._settings.dirs else: return self._settings.others def _walk(self): for root, dirs, files in os.walk(self._root): for name in files + dirs: path = os.path.join(root, name) yield os.path.relpath(path, self._root) def _entries(self): if self._recursive: return self._walk() else: return os.listdir(self._root) def _update_root(self, root, recursive): self._root = root self._recursive = recursive self._left_list.delete(0, END) self._names = [] if self._root: for name in sorted(self._entries()): path = os.path.join(self._root, name) ftype = None if os.path.isfile(path): ftype = True if os.path.isdir(path): ftype = False self._names.append((name, ftype)) self._update_lists() def _insert_name_both(self, name, color, color_right_only=False): idx = self._left_list.size() self._left_list.insert(END, name) if not color_right_only: self._left_list.itemconfig(idx, dict(fg=color)) self._right_list.insert(END, name) self._right_list.itemconfig(idx, dict(fg=color)) def _update_lists(self): self._mapping = [] self._errors = [] rev_mapping = {} self._left_list.delete(0, END) self._right_list.delete(0, END) if not self._repl: self._errors.append('Invalid replacement string') for name, ftype in self._names: enabled = self._is_type_enabled(ftype) if enabled or not self._settings.hide_wrong_type: if not enabled or not self._regex: self._insert_name_both(name, 'gray') elif self._regex and not self._regex.match(name): if not self._settings.hide_mismatches: self._insert_name_both(name, 'gray') elif not self._repl: self._insert_name_both(name, 'gray', color_right_only=True) else: idx = self._left_list.size() right_name = self._regex.sub(self._repl, name) self._left_list.insert(END, name) self._right_list.insert(END, right_name) if name != right_name: self._mapping.append((name, right_name)) right_path = os.path.join(self._root, right_name) if os.path.exists(right_path): error = 'File already exists: %s' % right_name elif right_name in rev_mapping: other_name, other_idx = rev_mapping[right_name] colliding_sources = name, other_name error = 'Name collision: %s <- %s | %s' % (right_name, *colliding_sources) self._right_list.itemconfig(other_idx, dict(fg='red')) else: error = None rev_mapping[right_name] = name, idx if error: self._errors.append(error) self._right_list.itemconfig(idx, dict(fg='red')) @property def mapping(self): if not self._errors: return self._mapping @property def errors(self): return self._errors