6 from math
import log10
, sqrt
7 from matplotlib
.backend_bases
import LocationEvent
8 from matplotlib
.backends
.backend_gtkagg
import FigureCanvasGTKAgg
as FigureCanvas
9 from matplotlib
.backends
.backend_gtk
import FileChooserDialog
10 from matplotlib
.widgets
import SpanSelector
, RectangleSelector
11 from matplotlib
.transforms
import Bbox
13 IOSCOPY_COL_TEXT
= 0 # Combo box text
14 IOSCOPY_COL_X10
= 1 # x10 mode status
15 IOSCOPY_COL_VIS
= 2 # Combobox items sensitive
16 IOSCOPY_COL_SPAN
= 3 # Span mode status
17 IOSCOPY_COL_HADJ
= 4 # Horizontal scrollbar adjustment
18 IOSCOPY_COL_VADJ
= 5 # Vertical scrollbar adjustment
20 DEFAULT_ZOOM_FACTOR
= 0.8
21 DEFAULT_PAN_FACTOR
= 10
23 class MyRectangleSelector(RectangleSelector
):
24 """ FIXME: To be removed once upstream has merged PR #658
25 https://github.com/matplotlib/matplotlib/pull/658
28 def ignore(self
, event
):
29 'return ``True`` if *event* should be ignored'
30 # If RectangleSelector is not active :
34 # If canvas was locked
35 if not self
.canvas
.widgetlock
.available(self
):
38 # Only do rectangle selection if event was triggered
39 # with a desired button
40 if self
.validButtons
is not None:
41 if not event
.button
in self
.validButtons
:
44 # If no button was pressed yet ignore the event if it was out
46 if self
.eventpress
== None:
47 return event
.inaxes
!= self
.ax
49 # If a button was pressed, check if the release-button is the
50 # same. If event is out of axis, limit the data coordinates to axes
52 if event
.button
== self
.eventpress
.button
and event
.inaxes
!= self
.ax
:
53 (xdata
, ydata
) = self
.ax
.transData
.inverted().transform_point((event
.x
, event
.y
))
54 x0
, x1
= self
.ax
.get_xbound()
55 y0
, y1
= self
.ax
.get_ybound()
56 xdata
= max(x0
, xdata
)
57 xdata
= min(x1
, xdata
)
58 ydata
= max(y0
, ydata
)
59 ydata
= min(y1
, ydata
)
64 # If a button was pressed, check if the release-button is the
66 return (event
.inaxes
!=self
.ax
or
67 event
.button
!= self
.eventpress
.button
)
69 class IOscopy_GTK_Figure(oscopy
.Figure
):
70 def __init__(self
, sigs
={}, fig
=None, title
=''):
71 oscopy
.Figure
.__init
__(self
, None, fig
)
72 self
._TARGET
_TYPE
_SIGNAL
= 10354
73 self
._to
_figure
= [("oscopy-signals", gtk
.TARGET_SAME_APP
,\
74 self
._TARGET
_TYPE
_SIGNAL
)]
75 self
._hadjpreval
= None
76 self
._vadjpreval
= None
81 hbox1
= gtk
.HBox() # The window
82 vbox1
= gtk
.VBox() # The Graphs
83 hbox1
.pack_start(vbox1
)
85 canvas
= FigureCanvas(self
)
86 canvas
.mpl_connect('button_press_event', self
._button
_press
)
87 canvas
.mpl_connect('scroll_event', self
._mouse
_scroll
)
88 canvas
.mpl_connect('axes_enter_event', self
._axes
_enter
)
89 canvas
.mpl_connect('axes_leave_event', self
._axes
_leave
)
90 canvas
.mpl_connect('figure_enter_event', self
._figure
_enter
)
91 canvas
.mpl_connect('figure_leave_event', self
._figure
_leave
)
92 canvas
.mpl_connect('key_press_event', self
._key
_press
)
93 canvas
.mpl_connect('motion_notify_event', self
._show
_coords
)
94 self
._draw
_hid
= canvas
.mpl_connect('draw_event', self
._update
_scrollbars
)
95 w
.connect('delete-event', lambda w
, e
: w
.hide() or True)
96 w
.drag_dest_set(gtk
.DEST_DEFAULT_MOTION |\
97 gtk
.DEST_DEFAULT_HIGHLIGHT |\
98 gtk
.DEST_DEFAULT_DROP
,
99 self
._to
_figure
, gtk
.gdk
.ACTION_COPY
)
101 hbar
= gtk
.HScrollbar()
102 hbar
.set_sensitive(False)
104 vbox1
.pack_start(hbar
, False, False)
105 vbar
= gtk
.VScrollbar()
106 vbar
.set_sensitive(False)
108 hbox1
.pack_start(vbar
, False, False)
110 vbox1
.pack_start(canvas
)
112 vbox2
= gtk
.VBox() # The right-side menu
113 store
= gtk
.ListStore(gobject
.TYPE_STRING
, # String displayed
114 gobject
.TYPE_BOOLEAN
, # x10 mode status
115 gobject
.TYPE_BOOLEAN
, # Combobox item sensitive
116 gobject
.TYPE_BOOLEAN
, # Span mode status
117 gobject
.TYPE_PYOBJECT
, # Horizontal Adjustment
118 gobject
.TYPE_PYOBJECT
, # Vertical Adjustment
120 iter = store
.append([_('All Graphs'), False, True, False, gtk
.Adjustment(), gtk
.Adjustment()])
122 iter = store
.append([_('Graph %d') % (i
+ 1), False, True if i
< len(self
.graphs
) else False, False, gtk
.Adjustment(), gtk
.Adjustment()])
123 self
._cbx
_store
= store
124 hbar
.set_adjustment(store
[0][IOSCOPY_COL_HADJ
])
125 vbar
.set_adjustment(store
[0][IOSCOPY_COL_VADJ
])
127 graphs_cbx
= gtk
.ComboBox(store
)
128 cell
= gtk
.CellRendererText()
129 graphs_cbx
.pack_start(cell
, True)
130 graphs_cbx
.add_attribute(cell
, 'text', IOSCOPY_COL_TEXT
)
131 graphs_cbx
.add_attribute(cell
, 'sensitive', IOSCOPY_COL_VIS
)
132 graphs_cbx
.set_active(0)
133 vbox2
.pack_start(graphs_cbx
, False, False)
135 # master pan radiobuttons
136 label
= gtk
.Label('master pan')
137 vbox2
.pack_start(label
, False, False)
139 rbtns
= [gtk
.RadioButton(None, '%d' % (i
+ 1)) for i
in xrange(4)]
141 for rb
in rbtns
[1:4]: rb
.set_group(rbtns
[0])
143 rb
.set_sensitive(False)
144 rbtnbox
.pack_start(rb
)
146 self
._mpsel
_get
_act
= [b
.get_active
for b
in rbtns
]
147 self
._mpsel
_set
_act
= [b
.set_active
for b
in rbtns
]
148 self
._mpsel
_set
_sens
= [b
.set_sensitive
for b
in rbtns
]
150 b
.connect('toggled', self
._update
_scrollbars
)
151 vbox2
.pack_start(rbtnbox
, False, False)
153 vbox2
.pack_start(gtk
.HSeparator(), False, False)
155 x10_toggle_btn
= gtk
.ToggleButton('x10 mode')
156 x10_toggle_btn
.set_mode(True)
157 x10_toggle_btn
.connect('toggled', self
.x10_toggle_btn_toggled
,
160 span_toggle_btn
= gtk
.ToggleButton(_('Span'))
161 span_toggle_btn
.set_mode(True)
162 span_toggle_btn
.connect('toggled', self
.span_toggle_btn_toggled
,
165 save_fig_btn
= gtk
.Button(_('Export'))
166 save_fig_btn
.connect('clicked', self
.save_fig_btn_clicked
)
168 coords_lbl1
= gtk
.Label('')
169 coords_lbl1
.set_alignment(0.1, 0.5)
170 coords_lbl2
= gtk
.Label('')
171 coords_lbl2
.set_alignment(0.1, 0.5)
173 self
._cbx
= graphs_cbx
174 self
._btn
= x10_toggle_btn
175 self
._coords
_lbl
1 = coords_lbl1
176 self
._coords
_lbl
2 = coords_lbl2
178 graphs_cbx
.connect('changed', self
.graphs_cbx_changed
, x10_toggle_btn
,
179 span_toggle_btn
, store
)
180 hbar
.connect('change-value', self
.hscroll_change_value
, graphs_cbx
,
182 hbar
.connect('enter-notify-event', self
.disable_adj_update_on_draw
)
183 hbar
.connect('leave-notify-event', self
.enable_adj_update_on_draw
)
184 hbar
.connect('button-press-event', self
.hadj_pressed
)
185 hbar
.connect('button-release-event', self
.hadj_released
)
186 vbar
.connect('change-value', self
.vscroll_change_value
, graphs_cbx
,
188 vbar
.connect('enter-notify-event', self
.disable_adj_update_on_draw
)
189 vbar
.connect('leave-notify-event', self
.enable_adj_update_on_draw
)
190 vbar
.connect('button-press-event', self
.vadj_pressed
)
191 vbar
.connect('button-release-event', self
.vadj_released
)
192 vbox2
.pack_start(x10_toggle_btn
, False, False)
193 vbox2
.pack_start(span_toggle_btn
, False, False)
194 vbox2
.pack_start(save_fig_btn
, False, False)
195 vbox2
.pack_end(coords_lbl1
, False, False)
196 vbox2
.pack_end(coords_lbl2
, False, False)
198 hbox1
.pack_start(vbox2
, False, False)
205 # # Update canvas for SpanSelector of Graphs
206 for gr
in self
.graphs
:
207 if hasattr(gr
, 'span'):
210 def set_layout(self
, layout
='quad'):
211 oscopy
.Figure
.set_layout(self
, layout
)
212 iter = self
._cbx
_store
.get_iter_first()
213 for g
in self
.graphs
:
214 iter = self
._cbx
_store
.iter_next(iter)
215 if self
._layout
== 'horiz':
216 g
.span
= SpanSelector(g
, g
.onselect
, 'horizontal',
218 g
.span
.visible
= self
._cbx
_store
.get_value(iter, IOSCOPY_COL_SPAN
)
219 self
.hbar
.set_sensitive(True)
221 self
.vbar
.set_sensitive(False)
223 elif self
._layout
== 'vert':
224 g
.span
= SpanSelector(g
, g
.onselect
, 'vertical',
226 g
.span
.visible
= self
._cbx
_store
.get_value(iter, IOSCOPY_COL_SPAN
)
227 self
.hbar
.set_sensitive(False)
229 self
.vbar
.set_sensitive(True)
231 elif self
._layout
== 'quad':
232 g
.span
= MyRectangleSelector(g
, g
.onselect
, rectprops
=dict(facecolor
='red', edgecolor
= 'black', alpha
=0.5, fill
=True),
234 g
.span
.active
= self
._cbx
_store
.get_value(iter, IOSCOPY_COL_SPAN
)
235 self
.hbar
.set_sensitive(True)
237 self
.vbar
.set_sensitive(True)
240 def graphs_cbx_changed(self
, graphs_cbx
, x10_toggle_btn
, span_toggle_btn
, store
):
241 iter = graphs_cbx
.get_active_iter()
242 if store
.get_string_from_iter(iter) == '0':
243 # Do all the graphs have same state?
244 store
.iter_next(iter)
245 val
= store
.get_value(iter, IOSCOPY_COL_X10
)
246 while iter is not None:
247 if val
!= store
.get_value(iter, IOSCOPY_COL_X10
):
248 # Yes, set the button into inconsistent state
249 x10_toggle_btn
.set_inconsistent(True)
251 iter = store
.iter_next(iter)
252 iter = store
.get_iter_first()
253 store
.iter_next(iter)
254 val
= store
.get_value(iter, IOSCOPY_COL_SPAN
)
255 self
.hbar
.set_adjustment(store
.get_value(iter, IOSCOPY_COL_HADJ
))
256 self
.vbar
.set_adjustment(store
.get_value(iter, IOSCOPY_COL_VADJ
))
257 while iter is not None:
258 if val
!= store
.get_value(iter, IOSCOPY_COL_SPAN
):
259 # Yes, set the button into inconsistent state
260 span_toggle_btn
.set_inconsistent(True)
262 iter = store
.iter_next(iter)
264 x10_toggle_btn
.set_inconsistent(False)
265 x10_toggle_btn
.set_active(store
.get_value(iter, IOSCOPY_COL_X10
))
266 span_toggle_btn
.set_inconsistent(False)
267 span_toggle_btn
.set_active(store
.get_value(iter, IOSCOPY_COL_SPAN
))
268 self
.hbar
.set_adjustment(store
.get_value(iter, IOSCOPY_COL_HADJ
))
269 self
.vbar
.set_adjustment(store
.get_value(iter, IOSCOPY_COL_VADJ
))
271 def x10_toggle_btn_toggled(self
, x10_toggle_btn
, graphs_cbx
, store
):
273 iter = graphs_cbx
.get_active_iter()
274 a
= x10_toggle_btn
.get_active()
275 val
= -.1 if a
else -1 # x10 zoom
276 x10_toggle_btn
.set_inconsistent(False)
279 store
.set_value(iter, IOSCOPY_COL_X10
, a
)
280 grnum
= int(store
.get_string_from_iter(iter))
281 if store
.get_string_from_iter(iter) == '0':
282 # Set the value for all graphs
283 iter = store
.iter_next(iter)
284 while iter is not None:
285 store
.set_value(iter, IOSCOPY_COL_X10
, a
)
286 grnum
= int(store
.get_string_from_iter(iter))
287 if grnum
> len(self
.graphs
):
289 g
= self
.graphs
[grnum
- 1]
291 iter = store
.iter_next(iter)
293 g
= self
.graphs
[grnum
- 1]
295 self
.canvas
.draw_idle()
297 def _zoom_x10(self
, g
, a
):
298 layout
= self
._layout
299 result
= g
.dataLim
.frozen()
300 if layout
== 'horiz' or layout
== 'quad':
301 g
.set_xlim(*result
.intervalx
)
302 if layout
== 'vert' or layout
== 'quad':
303 g
.set_ylim(*result
.intervaly
)
305 result
= g
.bbox
.expanded(0.1, 0.1).transformed(g
.transData
.inverted())
306 if layout
== 'horiz' or layout
== 'quad':
307 g
.set_xlim(*result
.intervalx
)
308 if layout
== 'vert' or layout
== 'quad':
309 g
.set_ylim(*result
.intervaly
)
311 def span_toggle_btn_toggled(self
, span_toggle_btn
, graphs_cbx
, store
):
312 iter = graphs_cbx
.get_active_iter()
313 a
= span_toggle_btn
.get_active()
314 span_toggle_btn
.set_inconsistent(False)
316 store
.set_value(iter, IOSCOPY_COL_SPAN
, a
)
317 grnum
= int(store
.get_string_from_iter(iter))
318 if store
.get_string_from_iter(iter) == '0':
319 # Set the value for all graphs
320 iter = store
.iter_next(iter)
321 while iter is not None:
322 store
.set_value(iter, IOSCOPY_COL_SPAN
, a
)
323 grnum
= int(store
.get_string_from_iter(iter))
324 if grnum
> len(self
.graphs
):
326 if hasattr(self
.graphs
[grnum
- 1].span
, 'active'):
327 self
.graphs
[grnum
- 1].span
.active
= a
328 elif hasattr(self
.graphs
[grnum
- 1].span
, 'visible'):
329 self
.graphs
[grnum
- 1].span
.visible
= a
330 iter = store
.iter_next(iter)
332 if hasattr(self
.graphs
[grnum
- 1].span
, 'active'):
333 self
.graphs
[grnum
- 1].span
.active
= a
334 elif hasattr(self
.graphs
[grnum
- 1].span
, 'visible'):
335 self
.graphs
[grnum
- 1].span
.visible
= a
338 def save_fig_btn_clicked(self
, save_fig_btn
):
339 fname
, format
= self
.get_filechooser().get_filename_from_user()
342 self
.canvas
.print_figure(fname
, format
=format
)
344 error_msg_gtk(str(e
), parent
=self
)
346 def get_filechooser(self
):
347 # From matplotlib/backends/backend_gtk.py
348 return FileChooserDialog(
349 title
=_('Save the figure'),
351 filetypes
=self
.canvas
.get_supported_filetypes(),
352 default_filetype
=self
.canvas
.get_default_filetype())
354 def _key_press(self
, event
):
355 if event
.inaxes
is not None:
358 self
._zoom
_on
_event
(event
, DEFAULT_ZOOM_FACTOR
)
359 self
.canvas
.draw_idle()
360 elif event
.key
== 'Z':
361 self
._zoom
_on
_event
(event
, 1. / DEFAULT_ZOOM_FACTOR
)
362 self
.canvas
.draw_idle()
363 elif event
.key
== 'l':
364 result
= g
.bbox
.translated(-DEFAULT_PAN_FACTOR
, 0).transformed(g
.transData
.inverted())
365 g
.set_xlim(*result
.intervalx
)
366 g
.set_ylim(*result
.intervaly
)
367 self
.canvas
.draw_idle()
368 elif event
.key
== 'r':
369 result
= g
.bbox
.translated(DEFAULT_PAN_FACTOR
, 0).transformed(g
.transData
.inverted())
370 g
.set_xlim(*result
.intervalx
)
371 g
.set_ylim(*result
.intervaly
)
372 self
.canvas
.draw_idle()
375 def _zoom_on_event(self
, event
, factor
):
377 if g
is None or factor
== 1:
380 result
= g
.bbox
.expanded(factor
, factor
).transformed(g
.transData
.inverted())
381 # Localisation of event.xdata in the new transform
382 if layout
== 'horiz' or layout
== 'quad':
383 g
.set_xlim(*result
.intervalx
)
384 if layout
== 'vert' or layout
== 'quad':
385 g
.set_ylim(*result
.intervaly
)
386 # Then place it under cursor
387 b
= g
.transData
.transform_point([event
.xdata
, event
.ydata
])
388 result
= g
.bbox
.translated(-(event
.x
- b
[0]), -(event
.y
- b
[1])).transformed(g
.transData
.inverted())
389 if layout
== 'horiz' or layout
== 'quad':
390 g
.set_xlim(*result
.intervalx
)
391 if layout
== 'vert' or layout
== 'quad':
392 g
.set_ylim(*result
.intervaly
)
393 # Limit to data boundaries
394 (dxmin
, dxmax
) = (g
.dataLim
.xmin
, g
.dataLim
.xmax
)
395 (xmin
, xmax
) = g
.get_xbound()
396 g
.set_xbound(max(dxmin
, xmin
), min(dxmax
, xmax
))
397 (dymin
, dymax
) = (g
.dataLim
.ymin
, g
.dataLim
.ymax
)
398 (ymin
, ymax
) = g
.get_ybound()
399 g
.set_ybound(max(dymin
, ymin
), min(dymax
, ymax
))
401 def drag_data_received_cb(self
, widget
, drag_context
, x
, y
, selection
,\
402 target_type
, time
, ctxtsignals
):
403 # Event handling issue: this drag and drop callback is
404 # processed before matplotlib callback _axes_enter. Therefore
405 # when dropping, self._current_graph is not valid: it contains
407 # The workaround is to retrieve the Graph by creating a Matplotlib
408 # LocationEvent considering inverse 'y' coordinates
409 if target_type
== self
._TARGET
_TYPE
_SIGNAL
:
411 my_y
= canvas
.allocation
.height
- y
412 event
= LocationEvent('axes_enter_event', canvas
, x
, my_y
)
414 for name
in selection
.data
.split():
415 signals
[name
] = ctxtsignals
[name
]
416 if event
.inaxes
is not None:
418 event
.inaxes
.insert(signals
)
422 oscopy
.Figure
.add(self
, args
)
423 store
= self
._cbx
_store
424 iter = store
.get_iter_first()
425 iter = store
.iter_next(iter) # First item always sensitive
426 while iter is not None:
427 grnum
= int(store
.get_string_from_iter(iter))
428 if grnum
> len(self
.graphs
):
429 store
.set_value(iter, IOSCOPY_COL_VIS
, False)
430 self
._mpsel
_set
_sens
[grnum
- 1](False) # master pan
432 store
.set_value(iter, IOSCOPY_COL_VIS
, True)
433 self
._mpsel
_set
_sens
[grnum
- 1](True) # master pan
434 iter = store
.iter_next(iter)
436 def delete(self
, args
):
437 oscopy
.Figure
.delete(self
, args
)
438 store
= self
._cbx
_store
439 iter = store
.get_iter_first()
440 iter = store
.iter_next(iter) # First item always sensitive
441 while iter is not None:
442 grnum
= int(store
.get_string_from_iter(iter))
443 if grnum
> len(self
.graphs
):
444 store
.set_value(iter, IOSCOPY_COL_VIS
, False)
445 self
._mpsel
_set
_sens
[grnum
- 1](False) # master pan
446 if grnum
> 1 and self
._mpsel
_get
_act
[grnum
- 1]():
447 self
._mpsel
_set
_act
[grnum
- 2](True)
449 store
.set_value(iter, IOSCOPY_COL_VIS
, True)
450 self
._mpsel
_set
_sens
[grnum
- 1](True) # master pan
451 iter = store
.iter_next(iter)
453 def _button_press(self
, event
):
454 if event
.button
== 3:
455 menu
= self
._create
_figure
_popup
_menu
(event
.canvas
.figure
, event
.inaxes
)
457 menu
.popup(None, None, None, event
.button
, event
.guiEvent
.time
)
459 def _mouse_scroll(self
, event
):
460 if event
.button
== 'up':
461 if event
.inaxes
is None:
463 self
._zoom
_on
_event
(event
, DEFAULT_ZOOM_FACTOR
)
464 self
.canvas
.draw_idle()
465 elif event
.button
== 'down':
466 if event
.inaxes
is None:
468 self
._zoom
_on
_event
(event
, 1. / DEFAULT_ZOOM_FACTOR
)
469 self
.canvas
.draw_idle()
472 def _axes_enter(self
, event
):
473 # self._figure_enter(event)
474 # self._current_graph = event.inaxes
476 # axes_num = event.canvas.figure.axes.index(event.inaxes) + 1
477 # fig_num = self._ctxt.figures.index(self._current_figure) + 1
478 # self._app_exec('%%oselect %d-%d' % (fig_num, axes_num))
481 def _axes_leave(self
, event
):
482 # Unused for better user interaction
483 # self._current_graph = None
486 def _figure_enter(self
, event
):
487 # self._current_figure = event.canvas.figure
488 # if hasattr(event, 'inaxes') and event.inaxes is not None:
489 # axes_num = event.canvas.figure.axes.index(event.inaxes) + 1
492 # fig_num = self._ctxt.figures.index(self._current_figure) + 1
493 # self._app_exec('%%oselect %d-%d' % (fig_num, axes_num))
494 self
.canvas
.grab_focus()
497 def _figure_leave(self
, event
):
498 # self._current_figure = None
501 def _show_coords(self
, event
):
504 x
= '%.3f %s%s' % (event
.xdata
,
505 oscopy
.factors_to_names
[a
.scale_factors
[0]][0],
507 y
= '%.3f %s%s' % (event
.ydata
,
508 oscopy
.factors_to_names
[a
.scale_factors
[1]][0],
510 self
._coords
_lbl
1.set_text(x
)
511 self
._coords
_lbl
2.set_text(y
)
513 self
._coords
_lbl
1.set_text('')
514 self
._coords
_lbl
2.set_text('')
517 def _create_figure_popup_menu(self
, figure
, graph
):
518 figmenu
= gui
.menus
.FigureMenu()
519 return figmenu
.create_menu(figure
, graph
)
521 def _update_scrollbars(self
, unused
):
522 # Unused is not used but can be either a MPL event or a togglebutton
523 (lower
, upper
) = (0, 1)
524 (xpgs_min
, ypgs_min
) = (1, 1)
525 (xvs
, yvs
) = ([], [])
526 for grnum
, gr
in enumerate(self
.graphs
):
527 (xv
, xpgs
, yv
, ypgs
) = self
._update
_graph
_adj
(grnum
, gr
)
528 xpgs_min
= min(xpgs_min
, xpgs
)
529 ypgs_min
= min(ypgs_min
, ypgs
)
532 # Then for all graphs...
533 for i
, get_act
in enumerate(self
._mpsel
_get
_act
):
537 hadj
= self
._cbx
_store
[0][IOSCOPY_COL_HADJ
]
538 hadj
.configure(xvs
[i
], lower
, upper
,
539 xpgs_min
/ 10.0, xpgs_min
, xpgs_min
)
541 vadj
= self
._cbx
_store
[0][IOSCOPY_COL_VADJ
]
542 vadj
.configure(-yvs
[i
], -upper
, lower
,
543 ypgs_min
/ 10.0, ypgs_min
, ypgs_min
)
545 def _update_graph_adj(self
, grnum
, g
):
546 (lower
, upper
) = (0, 1)
548 hadj
= self
._cbx
_store
[grnum
+ 1][IOSCOPY_COL_HADJ
]
549 vadj
= self
._cbx
_store
[grnum
+ 1][IOSCOPY_COL_VADJ
]
551 # Get data Bbox and view Bbox in pixels
552 (x0data
, y0data
, wdata
, hdata
) = g
.dataLim
.transformed(g
.transData
).bounds
553 (xvmin
, yvmin
, wv
, hv
) = g
.bbox
.bounds
555 # Compute page_size and value relatively to data bounds
556 xpage_size
= wv
/ wdata
557 ypage_size
= hv
/ hdata
558 xvalue
= (xvmin
- x0data
) / wdata
559 yvalue
= (yvmin
- y0data
+ hv
) / hdata
561 xstep_increment
= xpage_size
/ 10
562 xpage_increment
= xpage_size
563 hadj
.configure(xvalue
, lower
, upper
,
564 xstep_increment
, xpage_increment
, xpage_size
)
565 ystep_increment
= ypage_size
/ 10
566 ypage_increment
= ypage_size
567 # Need to revert scroll bar to have minimum on bottom
568 vadj
.configure(-yvalue
, -upper
, lower
,
569 ystep_increment
, ypage_increment
, ypage_size
)
570 return (xvalue
, xpage_size
, yvalue
, ypage_size
)
572 def hscroll_change_value(self
, widget
, scroll
, value
, cbx
, store
):
573 if self
.layout
== 'vert':
575 iter = cbx
.get_active_iter()
577 for mp
, get_act
in enumerate(self
._mpsel
_get
_act
):
579 break # Here is the master pan graph
580 master_pan_gr
= self
.graphs
[mp
]
582 grnum
= int(store
.get_string_from_iter(iter))
583 if store
.get_string_from_iter(iter) == '0':
584 # move only graphs having the same unit as selected master
585 adj
= widget
.get_adjustment()
586 val
= adj
.get_value()
587 delta
= (val
- self
._hadjpreval
) if self
._hadjpreval
is not None else 0
588 iter = store
.iter_next(iter)
589 while iter is not None:
590 grnum
= int(store
.get_string_from_iter(iter))
591 if grnum
> len(self
.graphs
):
593 g
= self
.graphs
[grnum
- 1]
595 if self
._hadjpreval
is None or g
.get_unit()[0] != master_pan_gr
.get_unit()[0]:
596 iter = store
.iter_next(iter)
598 hadj
= self
._cbx
_store
[grnum
][IOSCOPY_COL_HADJ
]
599 curval
= hadj
.get_value()
600 if ((val
< curval
and delta
< 0) or (val
> curval
and delta
> 0)) and val
< 1 - hadj
.get_page_size():
601 self
._translate
_to
(g
, val
, None)
602 hadj
.set_value(val
) # Because the callback is disabled
603 iter = store
.iter_next(iter)
604 self
._hadjpreval
= widget
.get_adjustment().get_value()
606 g
= self
.graphs
[grnum
- 1]
608 self
._translate
_to
(g
, widget
.get_value(), None)
609 self
.canvas
.draw_idle()
612 def vscroll_change_value(self
, widget
, scroll
, value
, cbx
, store
):
613 if self
.layout
== 'horiz':
615 iter = cbx
.get_active_iter()
617 for mp
, get_act
in enumerate(self
._mpsel
_get
_act
):
619 break # Here is the master pan graph
620 master_pan_gr
= self
.graphs
[mp
]
622 grnum
= int(store
.get_string_from_iter(iter))
623 if store
.get_string_from_iter(iter) == '0':
624 # Move only graphs having the same unit as selected master
625 adj
= widget
.get_adjustment()
626 val
= adj
.get_value()
627 delta
= (val
- self
._vadjpreval
) if self
._vadjpreval
is not None else 0
628 iter = store
.iter_next(iter)
629 while iter is not None:
630 grnum
= int(store
.get_string_from_iter(iter))
631 if grnum
> len(self
.graphs
):
633 g
= self
.graphs
[grnum
- 1]
635 if self
._vadjpreval
is None or g
.get_unit()[1] != master_pan_gr
.get_unit()[1]:
636 iter = store
.iter_next(iter)
638 vadj
= self
._cbx
_store
[grnum
][IOSCOPY_COL_VADJ
]
639 curval
= vadj
.get_value()
640 if ((val
< curval
and delta
< 0) or (val
> curval
and delta
> 0)) and val
< -vadj
.get_page_size():
641 self
._translate
_to
(g
, None, val
)
642 vadj
.set_value(val
) # Because the callback is disabled
643 iter = store
.iter_next(iter)
644 self
._vadjpreval
= widget
.get_adjustment().get_value()
646 g
= self
.graphs
[grnum
- 1]
648 self
._translate
_to
(g
, None, widget
.get_value())
649 self
.canvas
.draw_idle()
652 def _translate_to(self
, g
, xvalue
, yvalue
):
655 (x0data
, y0data
, wdata
, hdata
) = g
.dataLim
.transformed(g
.transData
).bounds
656 x0
, y0
, w
, h
= g
.bbox
.bounds
657 x0_new
= (xvalue
* wdata
+ x0data
) if xvalue
is not None else x0
658 # Need to revert scroll bar to have minimum on bottom
659 y0_new
= (-yvalue
* hdata
+ y0data
- h
) if yvalue
is not None else y0
660 result
= Bbox
.from_bounds(x0_new
, y0_new
, w
, h
).transformed(g
.transData
.inverted())
661 g
.set_xlim(*result
.intervalx
)
662 g
.set_ylim(*result
.intervaly
)
664 def disable_adj_update_on_draw(self
, widget
, event
):
666 if widget
== self
.hbar
and layout
not in ['horiz', 'quad']:
668 if widget
== self
.vbar
and layout
not in ['vert', 'quad']:
670 if self
._draw
_hid
is not None:
671 self
.canvas
.mpl_disconnect(self
._draw
_hid
)
672 self
._draw
_hid
= None
674 def enable_adj_update_on_draw(self
, widget
, event
):
676 if widget
== self
.hbar
and layout
not in ['horiz', 'quad']:
678 if widget
== self
.vbar
and layout
not in ['vert', 'quad']:
680 if self
._draw
_hid
is None and not event
.state
& gtk
.gdk
.BUTTON1_MASK
:
681 self
._draw
_hid
= self
.canvas
.mpl_connect('draw_event', self
._update
_scrollbars
)
683 def hadj_pressed(self
, widget
, event
):
684 self
._hadjpreval
= widget
.get_value()
686 def hadj_released(self
, widget
, event
):
687 self
._hadjpreval
= None
689 def vadj_pressed(self
, widget
, event
):
690 self
._vadjpreval
= widget
.get_value()
692 def vadj_released(self
, widget
, event
):
693 self
._vadjpreval
= None
695 layout
= property(oscopy
.Figure
.get_layout
, set_layout
)
697 def error_msg_gtk(msg
, parent
=None):
698 # From matplotlib/backends/backend_gtk.py
699 if parent
is not None: # find the toplevel gtk.Window
700 parent
= parent
.get_toplevel()
701 if parent
.flags() & gtk
.TOPLEVEL
== 0:
704 if not is_string_like(msg
):
705 msg
= ','.join(map(str,msg
))
707 dialog
= gtk
.MessageDialog(
709 type = gtk
.MESSAGE_ERROR
,
710 buttons
= gtk
.BUTTONS_OK
,
711 message_format
= msg
)