source: trunk/python/notationwindow.py@ 1884

Last change on this file since 1884 was 1884, checked in by Kana Sugimoto, 14 years ago

New Development: Yes

JIRA Issue: Yes (CAS-1801)

Ready for Test: Yes

Interface Changes: Yes

What Interface Changed: A new button 'note' added to casatoolbar

Test Programs: Need GUI testing

Put in Release Notes: Yes

Module(s): asap.plotter

Description:

A new button, 'note', added to casatoolbar.
You can graphically add an arbitrary text string on the plotter
when 'note' button is active.


File size: 15.4 KB
RevLine 
[1884]1import os
2import matplotlib
3from asap.logging import asaplog, asaplog_post_dec
4
5######################################
6## Notation box window ##
7######################################
8class NotationWindowCommon:
9 def __init__(self,master=None):
10 #self.parent = master
11 self.canvas = master
12 self.event = None
13 self.note = None
14 self.ancval = None
15 self.anchors = ["figure","axes","data"]
16 self.seltext = {}
17 self.numnote = 0
18
19 @asaplog_post_dec
20 def print_text(self):
21 anchor = self.anchors[self.ancval.get()]
22 notestr = self._get_note().rstrip("\n")
23 if len(notestr.strip()) == 0:
24 self._clear_textbox()
25 #print "Empty string!"
26 return
27
28 myaxes = None
29 calcpos = True
30 xpos=None
31 ypos=None
32 if self.seltext:
33 # You are modifying a text
34 mycanvas = self.canvas
35 oldanch = self.seltext['anchor']
36 if oldanch != 'figure':
37 myaxes = self.seltext['parent']
38 calcpos = (anchor != oldanch)
39 if not calcpos:
40 # printing text in the same coord.
41 # you don't have to recalc position
42 (xpos, ypos) = self.seltext['textobj'].get_position()
43 transform = self.seltext['textobj'].get_transform()
44 parent = self.seltext['parent']
45 elif anchor == "figure":
46 # converting from "axes"/"data" -> "figure"
47 (x, y) = self.seltext['textobj']._get_xy_display()
48 elif oldanch == "data":
49 # converting from "data" -> "axes".
50 # need xdata & ydata in the axes
51 (x, y) = self.seltext['textobj'].get_position()
52 else:
53 # converting "figure"/"axes" -> "data"
54 # need to calculate xdata & ydata in the axes
55 pixpos = self.seltext['textobj']._get_xy_display()
56 (w,h) = mycanvas.get_width_height()
57 relpos = (pixpos[0]/float(w), pixpos[1]/float(h))
58 if not myaxes:
59 myaxes = self._get_axes_from_pos(relpos,mycanvas)
60 if not myaxes:
61 raise RuntimeError, "Axes resolution failed!"
62 (x, y) = self._convert_pix2dat(relpos,myaxes)
63 self._remove_seltext()
64 elif self.event:
65 mycanvas = self.event.canvas
66 myaxes = self.event.inaxes
67 if myaxes and (anchor != "figure"):
68 x = self.event.xdata
69 y = self.event.ydata
70 else:
71 x = self.event.x
72 y = self.event.y
73 else:
74 raise RuntimeError, "No valid position to print data"
75 return
76
77 # now you know
78 picker = True
79 # alignment of the text: ha (horizontal), va (vertical)
80 ha = 'left'
81 va = 'center'
82 if not calcpos:
83 # you aready know parent, tansform, xpos and ypos
84 pass
85 elif anchor == "figure":
86 # text instance will be appended to mycanvas.figure.texts
87 parent = mycanvas.figure
88 transform = parent.transFigure
89 (w,h) = mycanvas.get_width_height()
90 xpos = x/float(w)
91 ypos = y/float(h)
92 elif myaxes:
93 ## text instance will be appended to myaxes.texts
94 parent = myaxes
95 if anchor == "axes":
96 transform = myaxes.transAxes
97 lims = myaxes.get_xlim()
98 xpos = (x-lims[0])/(lims[1]-lims[0])
99 lims = myaxes.get_ylim()
100 ypos = (y-lims[0])/(lims[1]-lims[0])
101 else:
102 # anchored on "data"
103 transform = myaxes.transData
104 xpos = x
105 ypos = y
106 parent.text(xpos,ypos,notestr,transform=transform,
107 ha=ha,va=va,picker=picker)
108 mycanvas.draw()
109
110 self.numnote += 1
111
112 self._clear_textbox()
113 msg = "A note added to figure: str = '"+notestr+"'"
114 msg += " at ["+str(xpos)+", "+str(ypos)+"] in "+anchor+"-coordinate"
115 msg += "\nTotal number of notes are "+str(self.numnote)
116 asaplog.push( msg )
117
118 def _get_axes_from_pos(self,pos,canvas):
119 if len(pos) != 2:
120 raise ValueError, "pixel position should have 2 elements"
121 for axes in canvas.figure.axes:
122 ##check if pos is in the axes
123 #if axes.contains_point(pos): ### seems not working
124 # return axes
125 axbox = axes.get_position().get_points()
126 if (axbox[0][0] <= pos[0] <= axbox[1][0]) and \
127 (axbox[0][1] <= pos[1] <= axbox[1][1]):
128 return axes
129 return None
130
131 def _convert_pix2dat(self,pos,axes):
132 # convert a relative position from lower-left of the canvas
133 # to a data in axes
134 if len(pos) != 2:
135 raise ValueError, "pixel position should have 2 elements"
136 # left-/bottom-pixel, and pixel width & height of the axes
137 bbox = axes.get_position()
138 lbpos = bbox.get_points()[0]
139 wax = bbox.width
140 hax = bbox.height
141 # check pos value
142 if (pos[0] < lbpos[0]) or (pos[1] < lbpos[1]) \
143 or (pos[0] > (lbpos[0]+wax)) or (pos[1] > (lbpos[1]+hax)):
144 raise ValueError, "The position is out of the axes"
145 xlims = axes.get_xlim()
146 ylims = axes.get_ylim()
147 wdat = xlims[1] - xlims[0]
148 hdat = ylims[1] - ylims[0]
149 xdat = xlims[0] + wdat*(pos[0] - lbpos[0])/wax
150 ydat = ylims[0] + hdat*(pos[1] - lbpos[1])/hax
151 return (xdat, ydat)
152
153 @asaplog_post_dec
154 def _selected_text(self,event):
155 (w,h) = event.canvas.get_width_height()
156 dist2 = w*w+h*h
157 selected = {}
158 for textobj in self.canvas.figure.texts:
159 if textobj.contains(event)[0]:
160 d2 = self._get_text_dist2(event,textobj)
161 if dist2 >= d2:
162 dist2=d2
163 selected={'anchor': 'figure', \
164 'parent': event.canvas.figure, 'textobj': textobj}
165 msg = "Fig loop: a text, '"+textobj.get_text()+"', at "
166 msg += str(textobj.get_position())+" detected"
167 asaplog.push(msg)
168 for ax in self.canvas.figure.axes:
169 for textobj in ax.texts:
170 if textobj.contains(event)[0]:
171 d2 = self._get_text_dist2(event,textobj)
172 if dist2 >= d2:
173 anchor='axes'
174 if ax.transData == textobj.get_transform():
175 anchor='data'
176 selected={'anchor': anchor, 'parent': ax, 'textobj': textobj}
177 msg = "Ax loop: a text, '"+textobj.get_text()+"', at "
178 msg += str(textobj.get_position())+" detected"
179 asaplog.push(msg)
180
181 return selected
182
183 def _get_text_dist2(self,event,textobj):
184 (x,y) = textobj._get_xy_display()
185 return (x-event.x)**2+(y-event.y)**2
186
187 def delete_note(self):
188 #print "You selected 'OK'"
189 self._remove_seltext()
190 self.canvas.draw()
191
192 @asaplog_post_dec
193 def _remove_seltext(self):
194 if len(self.seltext) < 3:
195 raise ValueError, "Don't under stand selected text obj."
196 return
197 try:
198 self.seltext['textobj'].remove()
199 except NotImplementedError:
200 self.seltext['parent'].texts.pop(self.seltext['parent'].texts.index(self.seltext['textobj']))
201 self.numnote -= 1
202
203 textobj = self.seltext['textobj']
204 msg = "A note deleted from figure: str = '"+textobj.get_text()+"'"
205 msg += " at "+str(textobj.get_position())\
206 +" in "+self.seltext['anchor']+"-coordinate"
207 msg += "\nTotal number of notes are "+str(self.numnote)
208 asaplog.push( msg )
209
210 self.seltext = {}
211
212 def cancel_delete(self):
213 #print "You selected 'CANCEL'"
214 self.seltext = {}
215
216
217#####################################
218## Backend dependent Classes ##
219#####################################
220### TkAgg
221if matplotlib.get_backend() == 'TkAgg':
222 import Tkinter as Tk
223 import tkMessageBox
224
225class NotationWindowTkAgg(NotationWindowCommon):
226 def __init__(self,master=None):
227 self.parent = master._tkcanvas
228 NotationWindowCommon.__init__(self,master=master)
229 self.textwin=self._create_textwindow(master=None)
230 self.menu=self._create_modmenu(master=self.parent)
231
232 def _create_textwindow(self,master=None):
233 twin = Tk.Toplevel(padx=3,pady=3)
234 twin.title("Annotation")
235 twin.resizable(width=True,height=True)
236 self.textbox = self._NotationBox(parent=twin)
237 self.radio = self._AnchorRadio(parent=twin)
238 self.actionbs = self._ActionButtons(parent=twin)
239
240 self.textbox.pack(side=Tk.TOP,fill=Tk.BOTH,expand=True)
241 self.actionbs.pack(side=Tk.BOTTOM)
242 self.radio.pack(side=Tk.BOTTOM)
243 #twin.deiconify()
244 #twin.minsize(width=twin.winfo_width(),height=twin.winfo_height())
245 twin.withdraw()
246 return twin
247
248 def _NotationBox(self,parent=None):
249 textbox = Tk.Text(master=parent,background='white',
250 height=2,width=20,cursor="xterm",
251 padx=2,pady=2,undo=True,maxundo=10)
252 return textbox
253
254 def _AnchorRadio(self,parent=None):
255 radio = Tk.LabelFrame(master=parent,text="anchor",
256 labelanchor="nw",padx=5,pady=3)
257 self.ancval = Tk.IntVar(master=radio,value=0)
258 self.rFig = self._NewRadioButton(radio,"figure",state=Tk.NORMAL,
259 variable=self.ancval,value=0,
260 side=Tk.LEFT)
261 self.rAxis = self._NewRadioButton(radio,"panel",state=Tk.DISABLED,
262 variable=self.ancval,value=1,
263 side=Tk.LEFT)
264 self.rData = self._NewRadioButton(radio,"data",state=Tk.DISABLED,
265 variable=self.ancval,value=2,
266 side=Tk.LEFT)
267 # set initial selection "figure"
268 self.ancval.set(0)
269 return radio
270
271 def _NewRadioButton(self,parent,text,state=Tk.NORMAL,variable=None,value=None,side=Tk.LEFT):
272 rb = Tk.Radiobutton(master=parent,text=text,state=state,
273 variable=variable,value=value)
274 rb.pack(side=side)
275 return rb
276
277 def _enable_radio(self):
278 self.rAxis.config(state=Tk.NORMAL)
279 self.rData.config(state=Tk.NORMAL)
280 #self.rFig.config(state=Tk.NORMAL)
281 self.rFig.select()
282
283 def _reset_radio(self):
284 self.rAxis.config(state=Tk.DISABLED)
285 self.rData.config(state=Tk.DISABLED)
286 self.rFig.config(state=Tk.NORMAL)
287 self.rFig.select()
288
289 def _select_radio(self,selection):
290 if not selection in self.anchors:
291 return
292 if selection == "data":
293 self.rData.select()
294 elif selection == "axes":
295 self.rAxis.select()
296 else:
297 self.rFig.select()
298
299 def _get_note(self):
300 return self.textbox.get("1.0",Tk.END)
301
302 def _clear_textbox(self):
303 self.textbox.delete("1.0",Tk.END)
304
305 def _set_note(self,note=None):
306 self._clear_textbox()
307 if len(note) >0:
308 self.textbox.insert("1.0",note)
309
310 def _ActionButtons(self,parent=None):
311 actbuts = Tk.Frame(master=parent)
312 bCancel = self._NewButton(actbuts,"cancel",self._cancel_text,side=Tk.LEFT)
313 bPrint = self._NewButton(actbuts,"print", self._print_text,side=Tk.LEFT)
314 return actbuts
315
316 def _NewButton(self, parent, text, command, side=Tk.LEFT):
317 if(os.uname()[0] == 'Darwin'):
318 b = Tk.Button(master=parent, text=text, command=command)
319 else:
320 b = Tk.Button(master=parent, text=text, padx=2, pady=2, command=command)
321 b.pack(side=side)
322 return b
323
324 def _cancel_text(self):
325 self._finish_textwindow()
326
327 def _print_text(self):
328 self.print_text()
329 self._finish_textwindow()
330
331 def load_textwindow(self,event):
332 if event.canvas._tkcanvas != self.parent:
333 raise RuntimeError, "Got invalid event!"
334
335 self.event = event
336 is_ax = (event.inaxes != None)
337 (xpix, ypix) = self._disppix2screen(event.x, event.y)
338 offset=5
339 self.show_textwindow(xpix+offset,ypix+offset,enableaxes=is_ax)
340
341 def show_textwindow(self,xpix,ypix,basetext=None,enableaxes=False):
342 if not self.textwin: return
343 self._reset_radio()
344 if enableaxes:
345 self._enable_radio()
346 self.textwin.deiconify()
347 (w,h) = self.textwin.minsize()
348 if w*h <= 1:
349 self.textwin.minsize(width=self.textwin.winfo_width(),
350 height=self.textwin.winfo_height())
351 (w,h) = self.textwin.minsize()
352 self.textwin.geometry("%sx%s+%s+%s"%(w,h,xpix,ypix))
353
354 def _finish_textwindow(self):
355 self._reset_radio()
356 self._clear_textbox()
357 self.textwin.withdraw()
358
359 def _create_modmenu(self,master=None):
360 if master:
361 self.parent=master
362 if not self.parent:
363 return False
364 menu = Tk.Menu(master=self.parent,tearoff=False)
365 menu.add_command(label="Modify",command=self.modify)
366 menu.add_command(label="Delete",command=self.delnote_dialog)
367 return menu
368
369 def show_modmenu(self,event):
370 self.seltext = self._selected_text(event)
371 if len(self.seltext) == 3:
372 tkcanvas=event.canvas._tkcanvas
373 xpos = tkcanvas.winfo_rootx() + int(event.x)
374 ypos = tkcanvas.winfo_rooty() \
375 + tkcanvas.winfo_height() - int(event.y)
376 self.menu.post(xpos,ypos)
377
378 def modify(self):
379 #print "Modify selected!!"
380 textobj = self.seltext['textobj']
381 (xtx, ytx) = textobj._get_xy_display()
382 is_ax = (self.seltext['anchor'] != 'figure')
383 if not is_ax:
384 # previous anchor is figure
385 pos = textobj.get_position()
386 is_ax = (self._get_axes_from_pos(pos,self.canvas) != None)
387
388 (xpix, ypix) = self._disppix2screen(xtx,ytx)
389 offset = int(textobj.get_size())*2
390 self.show_textwindow(xpix,ypix+offset,basetext=textobj.get_text(),\
391 enableaxes=is_ax)
392 self._select_radio(self.seltext['anchor'])
393 self._set_note(textobj.get_text())
394
395 def delnote_dialog(self):
396 remind = "Delete text?\n '"+self.seltext['textobj'].get_text()+"'"
397 answer = tkMessageBox.askokcancel(parent=self.parent,title="Delete?",
398 message=remind,
399 default=tkMessageBox.CANCEL)
400 if answer:
401 self.delete_note()
402 else:
403 self.cancel_delete()
404
405
406 def _disppix2screen(self,xpixd,ypixd):
407 # calculate a pixel position form Upper-left of the SCREEN
408 # from a pixel from Lower-left of the CANVAS (e.g., event.x/y)
409 xpixs = self.parent.winfo_rootx() + xpixd
410 ypixs = self.parent.winfo_rooty() + self.parent.winfo_height() \
411 - ypixd
412 return (int(xpixs), int(ypixs))
413
Note: See TracBrowser for help on using the repository browser.