import sys import os import pylab as pl import Tkinter as Tk from matplotlib.backend_bases import cursors import matplotlib rcParams = matplotlib.rcParams from matplotlib._pylab_helpers import Gcf cursord = { cursors.MOVE: "fleur", cursors.HAND: "hand2", cursors.POINTER: "arrow", cursors.SELECT_REGION: "tcross", } class PlotFlag: """ (1) Start the internal python interpreter... and make the 'pylab' module from the main python/casapy namespace visible inside it. ( done inside TPPlotter ) Note that 'pylab' is the only module of casapy that is visible from this internal interpreter. (2) figmanager = pl.get_current_fig_manager() -> This gets a handle to the current window, canvas, toolbar. (3) Create the python-C++ call-back module -> PyBind. ( description in tables/implement/TablePlot/PlotterGlobals.cc ) (3) TablePlotTkAgg.py implements a python class called 'PlotFlag' which takes an instance of 'PyBind' and 'figmanager' and makes the connection between the two. - Additional buttons are placed in the toolbar, and their callbacks defined to call methods of PyBind. - The toolbar event-loop is captured - by explicitly disconnecting previous bindings, and re-defining them for 'pan','zoom','mark-region' modes. (need to do all three, to get them to interact properly with each other) - Some Canvas events are also redefined to allow mark-region boxes to automatically resize and move around, when the window is resized or when in pan or zoom modes. ( This is needed to allow flagging with zooming ). (4) Back to the internal python interpreter. The following steps are carried out. -> figmanager = pl.get_current_fig_manager() -> import PyBind -> from TablePlotTkagg import PlotFlag -> pf = PlotFlag( PyBind ) -> pf.setup_custom_features( figmanager ) ----> All binding is complete at this point. ----> All other logic is to ensure things like... make sure new buttons are added only when needed... make sure they *are* added when needed... and this has to keep up with the native TkAgg matplotlib backend's whimsical decisions of when to create a new figure and when not to. """ def __init__(self,PyBind): #print "Init PlotFlag" self.PyBind = PyBind; self.newtoolbar = True; self.quitted = False; def sub(self): #pass self.quitted = True; self.PyBind.quit(True); def setup_custom_features(self,cfigman): if (rcParams['backend'].lower() == 'agg'): return self.toolbar = cfigman.toolbar; self.canvas = self.toolbar.canvas; self.window = cfigman.window; self.figmanager = cfigman; self.figmanager.window.wm_title("CASA Plotter"); self.figmanager.window.protocol("WM_DELETE_WINDOW", self.sub); if self.newtoolbar is True: # Add new buttons self.add_buttons(); # Reconfigure buttons. self.configure_buttons(); self.newtoolbar = False; # Toolbar parameters self.panel=0; self.rows=0; self.cols=0; # Canvas parameters self.regionlist=[]; self.panelregionlist=[]; self.axeslist=[]; self.erase_rects(); # Re-Make event bindings self.canvas.keyvald.update({65307 : 'escape'}); self.canvas.mpl_disconnect(self.toolbar._idDrag) self.toolbar._idDrag=self.canvas.mpl_connect('motion_notify_event', self.mouse_move) self.canvas._tkcanvas.bind("<KeyRelease>", self.key_release); self.canvas._tkcanvas.bind("<Configure>", self.resize); self.canvas._tkcanvas.bind("<Destroy>", self.destroy); #self.window.bind("<Destroy>", self.destroy); def plotflag_cleanup(self): self.canvas._tkcanvas.bind("<Destroy>", None); def set_cursor(self, cursor): self.toolbar.set_cursor(cursor); #self.toolbar.window.configure(cursor=cursord[cursor]); def _NewButton(self, frame, text, file, command, side=Tk.LEFT): #file = os.path.join(rcParams['datapath'], 'images', file) #file = '/opt/casa/stable/darwin/python/2.5' + file; #im = Tk.PhotoImage(master=frame, file=file) if(os.uname()[0] == 'Darwin'): b = Tk.Button(master=frame, text=text, command=command) else: b = Tk.Button(master=frame, text=text, padx=2, pady=2, command=command) #master=frame, text=text, padx=2, pady=2, image=im, command=command) #b._ntimage = im b.pack(side=side) return b def add_buttons(self): #self.newframe = Tk.Frame() self.newframe = Tk.Frame(master=self.window) bside = Tk.LEFT; self.toolbar.bMarkRegion = self._NewButton( frame=self.newframe, text="Mark Region", file="markregion.ppm", #file="markregion2.ppm", command=self.markregion, side=bside) self.toolbar.bFlag = self._NewButton(frame=self.newframe, text="Flag", file="flag4.ppm", command=None, side=bside) self.toolbar.bUnflag = self._NewButton(frame=self.newframe, text="Unflag", file="unflag4.ppm", command=None, side=bside) self.toolbar.bLocate = self._NewButton(frame=self.newframe, text="Locate", file="locate4.ppm", command=None, side=bside) self.toolbar.bIterNext = self._NewButton(frame=self.newframe, text=" Next ", file="locate4.ppm", command=None, side=bside) self.toolbar.bClear =None; #self.toolbar.bClear = self._NewButton(frame=self.newframe, # text=" Clear ", # file="locate4.ppm", # command=None, # side=bside) self.toolbar.bQuit = self._NewButton(frame=self.newframe, text=" Quit ", file="locate4.ppm", command=None, side=bside) self.toolbar.bMarkRegion.config(background='lightblue'); self.toolbar.bFlag.config(background='lightblue'); self.toolbar.bUnflag.config(background='lightblue'); self.toolbar.bLocate.config(background='lightblue'); self.toolbar.bIterNext.config(background='lightblue',state='disabled'); #self.toolbar.bClear.config(background='lightblue'); self.toolbar.bQuit.config(background='lightblue'); self.newframe.pack(side=Tk.BOTTOM,fill=Tk.BOTH); #self.newframe.pack_propagate(); def configure_buttons(self): self.toolbar.bHome.config(command=self.home); self.toolbar.bForward.config(command=self.forward); self.toolbar.bBack.config(command=self.back); self.toolbar.bsubplot.config(command=self.configure_subplots); self.toolbar.bPan.config(command=self.pan); self.toolbar.bZoom.config(command=self.zoom); self.toolbar.bMarkRegion.config(command=self.markregion); self.toolbar.bFlag.config(command=self.flag); self.toolbar.bUnflag.config(command=self.unflag); self.toolbar.bLocate.config(command=self.locate); self.toolbar.bIterNext.config(command=self.iterplotnext); #self.toolbar.bClear.config(command=self.clearplot); self.toolbar.bQuit.config(command=self.quit); #self.toolbar.bIterstop.config(command=self.iterplotstop); ### comment the next line when Wes updates matplotlib. #self.toolbar.bsave.config(command=self.savefig); def flag(self, *args): self.operate(1); def unflag(self, *args): self.operate(0); def locate(self, *args): self.operate(2); def operate(self, flag=1): #print '** Record the following regions' #for pr in self.canvas.panelregionlist: #print 'Region on panel [%(r)d,%(c)d,%(p)d] : [%(t1).3f, %(t2).3f, %(t3).3f, %(t4).3f] '%{'r':pr[5],'c':pr[6], 'p':pr[4],'t1':pr[0],'t2':pr[2], 't3':pr[1], 't4':pr[3]}; self.PyBind.markregion(self.panelregionlist); self.erase_rects(); if( flag is 1 ): #print "**Flag !!"; self.PyBind.flagdata(); if( flag is 0 ): #print "**UnFlag !!"; self.PyBind.unflagdata(); if( flag is 2 ): #print "**Locate !!"; self.PyBind.locatedata(); def iterplotnext(self, *args): self.PyBind.iterplotnext(); def iterplotstop(self, *args): self.PyBind.iterplotstop(); def clearplot(self, *args): #print 'Gui::calling clearplot' self.PyBind.clearplot(); #print 'Gui::finished clearplot' def savefig(self, *args): import time; fname = 'plot-casapy-'+time.strftime('%Y-%m-%dT%H:%M:%S')+'.png'; print 'Saving figure as ', fname, ' in current working directory.' self.canvas.figure.savefig(fname); def enable_iter_button(self): if (rcParams['backend'].lower() == 'agg'): return if( self.toolbar.bIterNext is not None ): self.toolbar.bIterNext.config(state='normal'); def disable_iter_button(self): if (rcParams['backend'].lower() == 'agg'): return if( self.toolbar.bIterNext is not None ): self.toolbar.bIterNext.config(state='disabled'); def enable_markregion_button(self): if (rcParams['backend'].lower() == 'agg'): return if( self.toolbar.bMarkRegion is not None ): self.toolbar.bMarkRegion.config(state='normal'); def disable_markregion_button(self): if (rcParams['backend'].lower() == 'agg'): return if( self.toolbar.bMarkRegion is not None ): self.toolbar.bMarkRegion.config(state='disabled'); def enable_flag_button(self): if (rcParams['backend'].lower() == 'agg'): return if( self.toolbar.bFlag is not None ): self.toolbar.bFlag.config(state='normal'); def disable_flag_button(self): if (rcParams['backend'].lower() == 'agg'): return if( self.toolbar.bFlag is not None ): self.toolbar.bFlag.config(state='disabled'); def enable_unflag_button(self): if (rcParams['backend'].lower() == 'agg'): return if( self.toolbar.bUnflag is not None ): self.toolbar.bUnflag.config(state='normal'); def disable_unflag_button(self): if (rcParams['backend'].lower() == 'agg'): return if( self.toolbar.bUnflag is not None ): self.toolbar.bUnflag.config(state='disabled'); def enable_locate_button(self): if (rcParams['backend'].lower() == 'agg'): return if( self.toolbar.bLocate is not None ): self.toolbar.bLocate.config(state='normal'); def disable_locate_button(self): if (rcParams['backend'].lower() == 'agg'): return if( self.toolbar.bLocate is not None ): self.toolbar.bLocate.config(state='disabled'); def enable_clear_button(self): if (rcParams['backend'].lower() == 'agg'): return if( self.toolbar.bClear is not None ): self.toolbar.bClear.config(state='normal'); def disable_clear_button(self): if (rcParams['backend'].lower() == 'agg'): return if( self.toolbar.bClear is not None ): self.toolbar.bClear.config(state='disabled'); def enable_quit_button(self): if (rcParams['backend'].lower() == 'agg'): return if( self.toolbar.bQuit is not None ): self.toolbar.bQuit.config(state='normal'); def disable_quit_button(self): if (rcParams['backend'].lower() == 'agg'): return if( self.toolbar.bQuit is not None ): self.toolbar.bQuit.config(state='disabled'); def draw_rubberband(self, event, x0, y0, x1, y1): if (rcParams['backend'].lower() == 'agg'): return ### workaround for matplotlib API changes #height = self.canvas.figure.bbox.height() #0.91.4 #height = self.canvas.figure.bbox.height #>=0.98 height = self.get_bbox_size(self.canvas.figure.bbox,"height") #workaround y0 = height-y0 y1 = height-y1 try: self.toolbar.lastrect except AttributeError: pass else: self.canvas._tkcanvas.delete(self.toolbar.lastrect) self.toolbar.lastrect = self.canvas._tkcanvas.create_rectangle(x0, y0, x1, y1, width=2,outline='black') def draw_rect(self, x0, y0, x1, y1, x0data, y0data, x1data, y1data,a,panel,rows,cols): self.panelregionlist.append([x0data,y0data,x1data,y1data,panel+1,rows,cols]); self.axeslist.append(a); ### workaround for matplotlib API changes #height = self.canvas.figure.bbox.height() #0.91.4 #height = self.canvas.figure.bbox.height #>=0.98 height = self.get_bbox_size(self.canvas.figure.bbox,"height") #workaround y0 = height-y0 y1 = height-y1 if(os.uname()[0] == 'Darwin'): rect = self.canvas._tkcanvas.create_rectangle(x0, y0, x1, y1, width=2,outline='black') else: rect = self.canvas._tkcanvas.create_rectangle(x0, y0, x1, y1, width=2,fill='black',stipple='gray50',outline='black') self.regionlist.append(rect); def erase_rects(self): #print "erase rects" if (rcParams['backend'].lower() == 'agg'): return for q in self.regionlist: self.canvas._tkcanvas.delete(q); self.regionlist = []; self.panelregionlist = []; self.axeslist = []; def redraw_rects(self): for q in self.regionlist: self.canvas._tkcanvas.delete(q); self.regionlist = []; for z in range(0,len(self.panelregionlist)): q = self.panelregionlist[z]; a = self.axeslist[z]; x0=q[0]; y0=q[1]; x1=q[2]; y1=q[3]; # map to new zoom limits (current fig co-ords) ### workaround for matplotlib API changes #px0,py0 = a.transData.xy_tup( (x0, y0) ) #0.91 #px0,py0 = a.transData.transform( (x0, y0) ) #>=0.98 px0,py0 = self.get_xy(a.transData, (x0, y0) ) #workaround ### workaround for matplotlib API changes #px1,py1 = a.transData.xy_tup( (x1, y1) ) #0.91 #px1,py1 = a.transData.transform( (x1, y1) ) #>=0.98 px1,py1 = self.get_xy(a.transData, (x1, y1) ) ### workaround for matplotlib API changes #height = self.canvas.figure.bbox.height() #0.91 #height = self.canvas.figure.bbox.height #>=0.98 height = self.get_bbox_size(self.canvas.figure.bbox,"height") #workaround py0 = height-py0 py1 = height-py1 if(os.uname()[0] == 'Darwin'): rect = self.canvas._tkcanvas.create_rectangle(px0, py0, px1, py1, width=2,outline='black') else: rect = self.canvas._tkcanvas.create_rectangle(px0, py0, px1, py1, width=2,fill='black',stipple='gray50',outline='black') self.regionlist.append(rect); def resize(self, event): #print 'canvas resize' self.canvas.resize(event); self.redraw_rects(); def destroy(self,*args): #print 'Gui::destroy.' self.erase_rects(); self.newtoolbar = True; if self.quitted is False: self.quit(closewin=True); print " "; #print "................................................................"; #print "............. Please IGNORE Tkinter error message. ............."; #print "................................................................"; def quit(self, closewin=True): #print 'quit with close-window : ', closewin; self.quitted = True; self.PyBind.quit(closewin); def key_release(self, event): #print 'key release' self.canvas.key_release(event); key = self.canvas._get_key(event); if(key=='escape'): numreg = len(self.regionlist); if(numreg>0): self.canvas._tkcanvas.delete(self.regionlist[numreg-1]); self.regionlist.pop(); self.panelregionlist.pop(); self.axeslist.pop(); def home(self, *args): 'restore the original view' if (rcParams['backend'].lower() == 'agg'): return self.toolbar.home(); self.redraw_rects(); def back(self, *args): 'move back up the view lim stack' if (rcParams['backend'].lower() == 'agg'): return self.toolbar.back(); self.redraw_rects(); def forward(self, *args): 'move forward in the view lim stack' if (rcParams['backend'].lower() == 'agg'): return self.toolbar.forward(); self.redraw_rects(); def configure_subplots(self): 'configure subplots' if (rcParams['backend'].lower() == 'agg'): return self.toolbar.configure_subplots(); self.redraw_rects(); def markregion(self, *args): 'activate mark-region mode' if (rcParams['backend'].lower() == 'agg'): return if self.toolbar._active == 'MARKREGION': #self.toolbar._active = None self.erase_rects(); self.update_relief(newmode=None); else: #self.toolbar._active = 'MARKREGION' self.update_relief(newmode='MARKREGION'); if self.toolbar._idPress is not None: self.toolbar._idPress=self.canvas.mpl_disconnect(self.toolbar._idPress) self.toolbar.mode = '' if self.toolbar._idRelease is not None: self.toolbar._idRelease=self.canvas.mpl_disconnect(self.toolbar._idRelease) self.toolbar.mode = '' if self.toolbar._active: self.toolbar._idPress = self.canvas.mpl_connect('button_press_event', self.press_markregion) self.toolbar._idRelease = self.canvas.mpl_connect('button_release_event', self.release_markregion) self.toolbar.mode = 'Mark Region mode' self.canvas.widgetlock(self.toolbar) else: self.canvas.widgetlock.release(self.toolbar) for a in self.canvas.figure.get_axes(): a.set_navigate_mode(self.toolbar._active) self.toolbar.set_message(self.toolbar.mode) def press_markregion(self, event): 'the press mouse button in mark region mode callback' if (rcParams['backend'].lower() == 'agg'): return if event.button == 1: self.toolbar._button_pressed=1 elif event.button == 3: self.toolbar._button_pressed=3 else: self.toolbar._button_pressed=None return # Check that the click is inside the canvas. x, y = event.x, event.y # push the current view to define home if stack is empty if self.toolbar._views.empty(): self.toolbar.push_current() self.toolbar._xypress=[] for i, a in enumerate(self.canvas.figure.get_axes()): #if x is not None and y is not None and a.in_axes(x, y) and a.get_navigate(): if x is not None and y is not None and event.inaxes==a and a.get_navigate(): xmin, xmax = a.get_xlim() ymin, ymax = a.get_ylim() lim = xmin, xmax, ymin, ymax ### workaround for matplotlib API changes #self.toolbar._xypress.append(( x, y, a, i, lim, a.transData.deepcopy() )) #0.91.4 #self.toolbar._xypress.append(( x, y, a, i, lim, a.transData.frozen() )) #>=0.98 self.toolbar._xypress.append(( x, y, a, i, lim, self.copy_trans(a.transData))) #workaround one, two, three = event.inaxes.get_geometry() self.panel = three-1 self.rows = one self.cols = two self.toolbar.press(event) def release_markregion(self, event): 'the release mouse button callback in mark region mode' if (rcParams['backend'].lower() == 'agg'): return if not self.toolbar._xypress: return for cur_xypress in self.toolbar._xypress: x, y = event.x, event.y lastx, lasty, a, ind, lim, trans = cur_xypress xmin, ymin, xmax, ymax = lim # mark rect ### workaround for matplotlib API changes #lastx, lasty = a.transData.inverse_xy_tup( (lastx, lasty) ) #0.91.4 #lastx, lasty = a.transData.inverted().transform( (lastx, lasty) ) #>=0.98 lastx, lasty = self.get_inverse_xy(a.transData, (lastx, lasty) ) #workaround ### workaround for matplotlib API changes #x, y = a.transData.inverse_xy_tup( (x, y) ) #0.91.4 #x, y = a.transData.inverted().transform( (x, y) ) #>=0.98 x, y = self.get_inverse_xy(a.transData, (x, y) ) #workaround Xmin,Xmax=a.get_xlim() Ymin,Ymax=a.get_ylim() if Xmin < Xmax: if x<lastx: xmin, xmax = x, lastx else: xmin, xmax = lastx, x if xmin < Xmin: xmin=Xmin if xmax > Xmax: xmax=Xmax else: if x>lastx: xmin, xmax = x, lastx else: xmin, xmax = lastx, x if xmin > Xmin: xmin=Xmin if xmax < Xmax: xmax=Xmax if Ymin < Ymax: if y<lasty: ymin, ymax = y, lasty else: ymin, ymax = lasty, y if ymin < Ymin: ymin=Ymin if ymax > Ymax: ymax=Ymax else: if y>lasty: ymin, ymax = y, lasty else: ymin, ymax = lasty, y if ymin > Ymin: ymin=Ymin if ymax < Ymax: ymax=Ymax ### workaround for matplotlib API changes #px1,py1 = a.transData.xy_tup( (xmin, ymin) ) #0.91.4 #px1,py1 = a.transData.transform( (xmin, ymin) ) #>=0.98 px1,py1 = self.get_xy(a.transData, (xmin, ymin) ) #workaround #px2,py2 = a.transData.xy_tup( (xmax, ymax) ) #0.91.4 #px2,py2 = a.transData.transform( (xmax, ymax) ) #>=0.98 px2,py2 = self.get_xy(a.transData, (xmax, ymax) ) #workaround self.draw_rect(px1, py1, px2, py2, xmin, ymin, xmax, ymax, a, self.panel, self.rows, self.cols) #print 'Region on panel [%(r)d,%(c)d,%(p)d] : [%(t1).3f, %(t2).3f, %(t3).3f, %(t4).3f] '%{'r':self.rows,'c':self.cols, 'p':self.panel+1,'t1':xmin,'t2':xmax, 't3':ymin, 't4':ymax}; #self.toolbar.draw() self.toolbar._xypress = None self.toolbar._button_pressed = None self.toolbar.push_current() self.toolbar.release(event) def zoom(self, *args): 'activate zoom to rect mode' if (rcParams['backend'].lower() == 'agg'): return if self.toolbar._active == 'ZOOM': #self.toolbar._active = None self.update_relief(newmode=None); else: #self.toolbar._active = 'ZOOM' self.update_relief(newmode='ZOOM'); if self.toolbar._idPress is not None: self.toolbar._idPress=self.canvas.mpl_disconnect(self.toolbar._idPress) self.toolbar.mode = '' if self.toolbar._idRelease is not None: self.toolbar._idRelease=self.canvas.mpl_disconnect(self.toolbar._idRelease) self.toolbar.mode = '' if self.toolbar._active: self.toolbar._idPress = self.canvas.mpl_connect('button_press_event', self.press_zoom) self.toolbar._idRelease = self.canvas.mpl_connect('button_release_event', self.release_zoom) self.toolbar.mode = 'Zoom to rect mode' self.canvas.widgetlock(self.toolbar) else: self.canvas.widgetlock.release(self.toolbar) for a in self.canvas.figure.get_axes(): a.set_navigate_mode(self.toolbar._active) self.toolbar.set_message(self.toolbar.mode) def press_zoom(self, event): 'the press mouse button in zoom to rect mode callback' if (rcParams['backend'].lower() == 'agg'): return if event.button == 1: self.toolbar._button_pressed=1 elif event.button == 3: self.toolbar._button_pressed=3 else: self.toolbar._button_pressed=None return x, y = event.x, event.y # push the current view to define home if stack is empty if self.toolbar._views.empty(): self.toolbar.push_current() self.toolbar._xypress=[] for i, a in enumerate(self.canvas.figure.get_axes()): #if x is not None and y is not None and a.in_axes(x, y) and a.get_navigate(): if x is not None and y is not None and event.inaxes==a and a.get_navigate(): xmin, xmax = a.get_xlim() ymin, ymax = a.get_ylim() lim = xmin, xmax, ymin, ymax ### workaround for matplotlib API changes #self.toolbar._xypress.append(( x, y, a, i, lim, a.transData.deepcopy() )) #0.91.4 #self.toolbar._xypress.append(( x, y, a, i, lim, a.transData.frozen() )) #>=0.98 self.toolbar._xypress.append(( x, y, a, i, lim, self.copy_trans(a.transData))) #workaround self.toolbar.press(event) def release_zoom(self, event): 'the release mouse button callback in zoom to rect mode' if (rcParams['backend'].lower() == 'agg'): return if not self.toolbar._xypress: return for cur_xypress in self.toolbar._xypress: x, y = event.x, event.y lastx, lasty, a, ind, lim, trans = cur_xypress # ignore singular clicks - 5 pixels is a threshold if abs(x-lastx)<5 or abs(y-lasty)<5: self.toolbar._xypress = None self.toolbar.release(event) self.toolbar.draw() return xmin, ymin, xmax, ymax = lim # zoom to rect ### workaround for matplotlib API changes #lastx, lasty = a.transData.inverse_xy_tup( (lastx, lasty) ) #0.91.4 #lastx, lasty = a.transData.inverted().transform( (lastx, lasty) ) #>=0.98 lastx, lasty = self.get_inverse_xy(a.transData, (lastx, lasty) ) #workaround ### workaround for matplotlib API changes #x, y = a.transData.inverse_xy_tup( (x, y) ) #0.91.4 #x, y = a.transData.inverted().transform( (x, y) ) #>=0.98 x, y = self.get_inverse_xy(a.transData, (x, y) ) #workaround Xmin,Xmax=a.get_xlim() Ymin,Ymax=a.get_ylim() if Xmin < Xmax: if x<lastx: xmin, xmax = x, lastx else: xmin, xmax = lastx, x if xmin < Xmin: xmin=Xmin if xmax > Xmax: xmax=Xmax else: if x>lastx: xmin, xmax = x, lastx else: xmin, xmax = lastx, x if xmin > Xmin: xmin=Xmin if xmax < Xmax: xmax=Xmax if Ymin < Ymax: if y<lasty: ymin, ymax = y, lasty else: ymin, ymax = lasty, y if ymin < Ymin: ymin=Ymin if ymax > Ymax: ymax=Ymax else: if y>lasty: ymin, ymax = y, lasty else: ymin, ymax = lasty, y if ymin > Ymin: ymin=Ymin if ymax < Ymax: ymax=Ymax if self.toolbar._button_pressed == 1: a.set_xlim((xmin, xmax)) a.set_ylim((ymin, ymax)) elif self.toolbar._button_pressed == 3: if a.get_xscale()=='log': alpha=log(Xmax/Xmin)/log(xmax/xmin) x1=pow(Xmin/xmin,alpha)*Xmin x2=pow(Xmax/xmin,alpha)*Xmin else: alpha=(Xmax-Xmin)/(xmax-xmin) x1=alpha*(Xmin-xmin)+Xmin x2=alpha*(Xmax-xmin)+Xmin if a.get_yscale()=='log': alpha=log(Ymax/Ymin)/log(ymax/ymin) y1=pow(Ymin/ymin,alpha)*Ymin y2=pow(Ymax/ymin,alpha)*Ymin else: alpha=(Ymax-Ymin)/(ymax-ymin) y1=alpha*(Ymin-ymin)+Ymin y2=alpha*(Ymax-ymin)+Ymin a.set_xlim((x1, x2)) a.set_ylim((y1, y2)) self.toolbar.draw() self.redraw_rects(); self.toolbar._xypress = None self.toolbar._button_pressed = None self.toolbar.push_current() self.toolbar.release(event) def pan(self,*args): 'Activate the pan/zoom tool. pan with left button, zoom with right' # set the pointer icon and button press funcs to the # appropriate callbacks if (rcParams['backend'].lower() == 'agg'): return if self.toolbar._active == 'PAN': #self.toolbar._active = None self.update_relief(newmode=None); else: #self.toolbar._active = 'PAN' self.update_relief(newmode='PAN'); if self.toolbar._idPress is not None: self.toolbar._idPress = self.canvas.mpl_disconnect(self.toolbar._idPress) self.toolbar.mode = '' if self.toolbar._idRelease is not None: self.toolbar._idRelease = self.canvas.mpl_disconnect(self.toolbar._idRelease) self.toolbar.mode = '' if self.toolbar._active: self.toolbar._idPress = self.canvas.mpl_connect( 'button_press_event', self.press_pan) self.toolbar._idRelease = self.canvas.mpl_connect( 'button_release_event', self.release_pan) self.toolbar.mode = 'pan/zoom mode' self.canvas.widgetlock(self.toolbar) else: self.canvas.widgetlock.release(self.toolbar) for a in self.canvas.figure.get_axes(): a.set_navigate_mode(self.toolbar._active) self.toolbar.set_message(self.toolbar.mode) def press_pan(self, event): 'the press mouse button in pan/zoom mode callback' if (rcParams['backend'].lower() == 'agg'): return if event.button == 1: self.toolbar._button_pressed=1 elif event.button == 3: self.toolbar._button_pressed=3 else: self.toolbar._button_pressed=None return x, y = event.x, event.y # push the current view to define home if stack is empty if self.toolbar._views.empty(): self.toolbar.push_current() self.toolbar._xypress=[] for i, a in enumerate(self.canvas.figure.get_axes()): #if x is not None and y is not None and a.in_axes(x, y) and a.get_navigate(): if x is not None and y is not None and event.inaxes==a and a.get_navigate(): xmin, xmax = a.get_xlim() ymin, ymax = a.get_ylim() lim = xmin, xmax, ymin, ymax ### workaround for matplotlib API changes #self.toolbar._xypress.append((x, y, a, i, lim,a.transData.deepcopy())) #0.91.4 #self.toolbar._xypress.append((x, y, a, i, lim,a.transData.frozen())) #>=0.98 self.toolbar._xypress.append((x, y, a, i, lim,self.copy_trans(a.transData))) #workaround self.canvas.mpl_disconnect(self.toolbar._idDrag) self.toolbar._idDrag=self.canvas.mpl_connect('motion_notify_event', self.drag_pan) self.toolbar.press(event) def release_pan(self, event): 'the release mouse button callback in pan/zoom mode' if (rcParams['backend'].lower() == 'agg'): return self.canvas.mpl_disconnect(self.toolbar._idDrag) self.toolbar._idDrag=self.canvas.mpl_connect('motion_notify_event', self.mouse_move) if not self.toolbar._xypress: return self.toolbar._xypress = None self.toolbar._button_pressed=None self.toolbar.push_current() self.toolbar.release(event) self.toolbar.draw() self.redraw_rects(); def drag_pan(self, event): 'the drag callback in pan/zoom mode' if (rcParams['backend'].lower() == 'agg'): return def format_deltas(event,dx,dy): if event.key=='control': if(abs(dx)>abs(dy)): dy = dx else: dx = dy elif event.key=='x': dy = 0 elif event.key=='y': dx = 0 elif event.key=='shift': if 2*abs(dx) < abs(dy): dx=0 elif 2*abs(dy) < abs(dx): dy=0 elif(abs(dx)>abs(dy)): dy=dy/abs(dy)*abs(dx) else: dx=dx/abs(dx)*abs(dy) return (dx,dy) for cur_xypress in self.toolbar._xypress: lastx, lasty, a, ind, lim, trans = cur_xypress xmin, xmax, ymin, ymax = lim #safer to use the recorded button at the press than current button: #multiple button can get pressed during motion... if self.toolbar._button_pressed==1: ### workaround for matplotlib API changes #lastx, lasty = trans.inverse_xy_tup( (lastx, lasty) ) #0.91.4 #lastx, lasty = trans.inverted().transform( (lastx, lasty) ) #>=0.98 lastx, lasty = self.get_inverse_xy(trans, (lastx, lasty) ) #workaround ### workaround for matplotlib API changes #x, y = trans.inverse_xy_tup( (event.x, event.y) ) #0.91.4 #x, y = trans.inverted().transform( (event.x, event.y) ) #>=0.98 x, y = self.get_inverse_xy(trans, (event.x, event.y) ) #workaround if a.get_xscale()=='log': dx=1-lastx/x else: dx=x-lastx if a.get_yscale()=='log': dy=1-lasty/y else: dy=y-lasty dx,dy=format_deltas(event,dx,dy) if a.get_xscale()=='log': xmin *= 1-dx xmax *= 1-dx else: xmin -= dx xmax -= dx if a.get_yscale()=='log': ymin *= 1-dy ymax *= 1-dy else: ymin -= dy ymax -= dy elif self.toolbar._button_pressed==3: try: ### workaround for matplotlib API changes #dx=(lastx-event.x)/float(a.bbox.width()) #0.91.4 #dx=(lastx-event.x)/float(a.bbox.width) #>=0.98 dx=(lastx-event.x)/float(self.get_bbox_size(a.bbox,"width")) #workaround ### workaround for matplotlib API changes #dy=(lasty-event.y)/float(a.bbox.height()) #0.91.4 #dy=(lasty-event.y)/float(a.bbox.height) #>=0.98 dy=(lasty-event.y)/float(self.get_bbox_size(a.bbox,"height")) #workaround dx,dy=format_deltas(event,dx,dy) if a.get_aspect() != 'auto': dx = 0.5*(dx + dy) dy = dx alphax = pow(10.0,dx) alphay = pow(10.0,dy)#use logscaling, avoid singularities and smother scaling... ### workaround for matplotlib API changes #lastx, lasty = trans.inverse_xy_tup( (lastx, lasty) ) #0.91.4 #lastx, lasty = trans.inverted().transform( (lastx, lasty) ) #>=0.98 lastx, lasty = self.get_inverse_xy(trans, (lastx, lasty) ) #workaround if a.get_xscale()=='log': xmin = lastx*(xmin/lastx)**alphax xmax = lastx*(xmax/lastx)**alphax else: xmin = lastx+alphax*(xmin-lastx) xmax = lastx+alphax*(xmax-lastx) if a.get_yscale()=='log': ymin = lasty*(ymin/lasty)**alphay ymax = lasty*(ymax/lasty)**alphay else: ymin = lasty+alphay*(ymin-lasty) ymax = lasty+alphay*(ymax-lasty) except OverflowError: warnings.warn('Overflow while panning') return a.set_xlim(xmin, xmax) a.set_ylim(ymin, ymax) self.redraw_rects(); self.toolbar.dynamic_update() def mouse_move(self, event): #print 'mouse_move', event.button if (rcParams['backend'].lower() == 'agg'): return if not event.inaxes or not self.toolbar._active: if self.toolbar._lastCursor != cursors.POINTER: self.set_cursor(cursors.POINTER) self.toolbar._lastCursor = cursors.POINTER else: if self.toolbar._active=='ZOOM': if self.toolbar._lastCursor != cursors.SELECT_REGION: self.set_cursor(cursors.SELECT_REGION) self.toolbar._lastCursor = cursors.SELECT_REGION if self.toolbar._xypress: x, y = event.x, event.y lastx, lasty, a, ind, lim, trans= self.toolbar._xypress[0] self.draw_rubberband(event, x, y, lastx, lasty) elif self.toolbar._active=='MARKREGION': if self.toolbar._lastCursor != cursors.SELECT_REGION: self.set_cursor(cursors.SELECT_REGION) self.toolbar._lastCursor = cursors.SELECT_REGION if self.toolbar._xypress: x, y = event.x, event.y lastx, lasty, a, ind, lim, trans= self.toolbar._xypress[0] self.draw_rubberband(event, x, y, lastx, lasty) elif (self.toolbar._active=='PAN' and self.toolbar._lastCursor != cursors.MOVE): self.set_cursor(cursors.MOVE) self.toolbar._lastCursor = cursors.MOVE if event.inaxes and event.inaxes.get_navigate(): try: s = event.inaxes.format_coord(event.xdata, event.ydata) except ValueError: pass except OverflowError: pass else: if len(self.toolbar.mode): self.toolbar.set_message('%s : %s' % (self.toolbar.mode, s)) else: self.toolbar.set_message(s) else: self.toolbar.set_message(self.toolbar.mode) def update_relief(self,newmode): 'activate new mode' if (rcParams['backend'].lower() == 'agg'): return if self.toolbar._active == 'ZOOM': self.toolbar.bZoom.config(relief='raised'); if self.toolbar._active == 'PAN': self.toolbar.bPan.config(relief='raised'); if self.toolbar._active == 'MARKREGION': self.toolbar.bMarkRegion.config(relief='raised'); self.toolbar._active = newmode; if self.toolbar._active == 'ZOOM': self.toolbar.bZoom.config(relief='sunken'); if self.toolbar._active == 'PAN': self.toolbar.bPan.config(relief='sunken'); if self.toolbar._active == 'MARKREGION': self.toolbar.bMarkRegion.config(relief='sunken'); #### Workarounds for Matplotlib version handling (ugly) #### def ismatlab_new(self): verstr=matplotlib.__version__.split(".") maj=int(verstr[0]) sub=int(verstr[1]) return (maj>0 or sub>=98) def get_inverse_xy(self,trans,(x,y)): if hasattr(trans,"inverse_xy_tup"): return trans.inverse_xy_tup((x, y)) elif hasattr(trans,"inverted"): return trans.inverted().transform((x, y)) else: return None def get_xy(self,trans,(x,y)): return self.switch_func(trans,["xy_tup","transform"],(x,y)) def copy_trans(self,trans): return self.switch_func(trans,["deepcopy","frozen"]) def get_bbox_size(self,obj,func=""): return self.get_called_or_attr(obj,func) def switch_func(self,obj,funcs=[],*args,**kwargs): """ Tries a list of functions and return a result from callable one. Deals with function name changes but parameters have to be unchanged. """ for func in funcs: called_func=self.get_called_or_attr(obj,func,*args,**kwargs) if called_func != None: break return called_func def get_called_or_attr(self,obj,func="",*args,**kwargs): """ Returns a result from function call if it's callable. If not callable, returns the attribute or False (non-existent). """ #if not hasattr(obj,func): return False #else: called=getattr(obj,func) try: called=getattr(obj,func) #except: return False except: return None if callable(called): return called(*args,**kwargs) else: return called