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_gtkagg
import NavigationToolbar2GTKAgg
as NavigationToolbar
10 from matplotlib
.backends
.backend_gtk
import FileChooserDialog
11 from matplotlib
.widgets
import SpanSelector
, RectangleSelector
12 from matplotlib
.transforms
import Bbox
14 IOSCOPY_COL_TEXT
= 0 # Combo box text
15 IOSCOPY_COL_X10
= 1 # x10 mode status
16 IOSCOPY_COL_VIS
= 2 # Combobox items sensitive
17 IOSCOPY_COL_SPAN
= 3 # Span mode status
18 IOSCOPY_COL_HADJ
= 4 # Horizontal scrollbar adjustment
19 IOSCOPY_COL_VADJ
= 5 # Vertical scrollbar adjustment
21 DEFAULT_ZOOM_FACTOR
= 0.8
22 DEFAULT_PAN_FACTOR
= 10
24 class MyRectangleSelector(RectangleSelector
):
25 """ FIXME: To be removed once upstream has merged PR #658
26 https://github.com/matplotlib/matplotlib/pull/658
29 def ignore(self
, event
):
30 'return ``True`` if *event* should be ignored'
31 # If RectangleSelector is not active :
35 # If canvas was locked
36 if not self
.canvas
.widgetlock
.available(self
):
39 # Only do rectangle selection if event was triggered
40 # with a desired button
41 if self
.validButtons
is not None:
42 if not event
.button
in self
.validButtons
:
45 # If no button was pressed yet ignore the event if it was out
47 if self
.eventpress
== None:
48 return event
.inaxes
!= self
.ax
50 # If a button was pressed, check if the release-button is the
51 # same. If event is out of axis, limit the data coordinates to axes
53 if event
.button
== self
.eventpress
.button
and event
.inaxes
!= self
.ax
:
54 (xdata
, ydata
) = self
.ax
.transData
.inverted().transform_point((event
.x
, event
.y
))
55 x0
, x1
= self
.ax
.get_xbound()
56 y0
, y1
= self
.ax
.get_ybound()
57 xdata
= max(x0
, xdata
)
58 xdata
= min(x1
, xdata
)
59 ydata
= max(y0
, ydata
)
60 ydata
= min(y1
, ydata
)
65 # If a button was pressed, check if the release-button is the
67 return (event
.inaxes
!=self
.ax
or
68 event
.button
!= self
.eventpress
.button
)
70 class IOscopy_GTK_Figure(oscopy
.Figure
):
71 def __init__(self
, sigs
={}, fig
=None, title
=''):
72 oscopy
.Figure
.__init
__(self
, None, fig
)
73 self
._TARGET
_TYPE
_SIGNAL
= 10354
74 self
._to
_figure
= [("oscopy-signals", gtk
.TARGET_SAME_APP
,\
75 self
._TARGET
_TYPE
_SIGNAL
)]
76 self
._hadjpreval
= None
77 self
._vadjpreval
= None
82 hbox1
= gtk
.HBox() # The window
83 vbox1
= gtk
.VBox() # The Graphs
84 hbox1
.pack_start(vbox1
)
86 canvas
= FigureCanvas(self
)
87 canvas
.mpl_connect('button_press_event', self
._button
_press
)
88 canvas
.mpl_connect('scroll_event', self
._mouse
_scroll
)
89 canvas
.mpl_connect('axes_enter_event', self
._axes
_enter
)
90 canvas
.mpl_connect('axes_leave_event', self
._axes
_leave
)
91 canvas
.mpl_connect('figure_enter_event', self
._figure
_enter
)
92 canvas
.mpl_connect('figure_leave_event', self
._figure
_leave
)
93 canvas
.mpl_connect('key_press_event', self
._key
_press
)
94 canvas
.mpl_connect('motion_notify_event', self
._show
_coords
)
95 self
._draw
_hid
= canvas
.mpl_connect('draw_event', self
._update
_scrollbars
)
96 w
.connect('delete-event', lambda w
, e
: w
.hide() or True)
97 w
.drag_dest_set(gtk
.DEST_DEFAULT_MOTION |\
98 gtk
.DEST_DEFAULT_HIGHLIGHT |\
99 gtk
.DEST_DEFAULT_DROP
,
100 self
._to
_figure
, gtk
.gdk
.ACTION_COPY
)
102 hbar
= gtk
.HScrollbar()
103 hbar
.set_sensitive(False)
105 vbox1
.pack_start(hbar
, False, False)
106 vbar
= gtk
.VScrollbar()
107 vbar
.set_sensitive(False)
109 hbox1
.pack_start(vbar
, False, False)
111 vbox1
.pack_start(canvas
)
113 toolbar
= NavigationToolbar(canvas
, w
)
114 vbox1
.pack_start(toolbar
, False, False)
116 vbox2
= gtk
.VBox() # The right-side menu
117 store
= gtk
.ListStore(gobject
.TYPE_STRING
, # String displayed
118 gobject
.TYPE_BOOLEAN
, # x10 mode status
119 gobject
.TYPE_BOOLEAN
, # Combobox item sensitive
120 gobject
.TYPE_BOOLEAN
, # Span mode status
121 gobject
.TYPE_PYOBJECT
, # Horizontal Adjustment
122 gobject
.TYPE_PYOBJECT
, # Vertical Adjustment
124 iter = store
.append([_('All Graphs'), False, True, False, gtk
.Adjustment(), gtk
.Adjustment()])
126 iter = store
.append([_('Graph %d') % (i
+ 1), False, True if i
< len(self
.graphs
) else False, False, gtk
.Adjustment(), gtk
.Adjustment()])
127 self
._cbx
_store
= store
128 hbar
.set_adjustment(store
[0][IOSCOPY_COL_HADJ
])
129 vbar
.set_adjustment(store
[0][IOSCOPY_COL_VADJ
])
131 graphs_cbx
= gtk
.ComboBox(store
)
132 cell
= gtk
.CellRendererText()
133 graphs_cbx
.pack_start(cell
, True)
134 graphs_cbx
.add_attribute(cell
, 'text', IOSCOPY_COL_TEXT
)
135 graphs_cbx
.add_attribute(cell
, 'sensitive', IOSCOPY_COL_VIS
)
136 graphs_cbx
.set_active(0)
137 vbox2
.pack_start(graphs_cbx
, False, False)
139 # master pan radiobuttons
140 label
= gtk
.Label('master pan')
141 vbox2
.pack_start(label
, False, False)
143 rbtns
= [gtk
.RadioButton(None, '%d' % (i
+ 1)) for i
in xrange(4)]
145 for rb
in rbtns
[1:4]: rb
.set_group(rbtns
[0])
147 rb
.set_sensitive(False)
148 rbtnbox
.pack_start(rb
)
150 self
._mpsel
_get
_act
= [b
.get_active
for b
in rbtns
]
151 self
._mpsel
_set
_act
= [b
.set_active
for b
in rbtns
]
152 self
._mpsel
_set
_sens
= [b
.set_sensitive
for b
in rbtns
]
154 b
.connect('toggled', self
._update
_scrollbars
)
155 vbox2
.pack_start(rbtnbox
, False, False)
157 vbox2
.pack_start(gtk
.HSeparator(), False, False)
159 x10_toggle_btn
= gtk
.ToggleButton('x10 mode')
160 x10_toggle_btn
.set_mode(True)
161 x10_toggle_btn
.connect('toggled', self
.x10_toggle_btn_toggled
,
164 span_toggle_btn
= gtk
.ToggleButton(_('Span'))
165 span_toggle_btn
.set_mode(True)
166 span_toggle_btn
.connect('toggled', self
.span_toggle_btn_toggled
,
169 save_fig_btn
= gtk
.Button(_('Export'))
170 save_fig_btn
.connect('clicked', self
.save_fig_btn_clicked
)
172 coords_lbl1
= gtk
.Label('')
173 coords_lbl1
.set_alignment(0.1, 0.5)
174 coords_lbl2
= gtk
.Label('')
175 coords_lbl2
.set_alignment(0.1, 0.5)
177 self
._cbx
= graphs_cbx
178 self
._btn
= x10_toggle_btn
179 self
._coords
_lbl
1 = coords_lbl1
180 self
._coords
_lbl
2 = coords_lbl2
182 graphs_cbx
.connect('changed', self
.graphs_cbx_changed
, x10_toggle_btn
,
183 span_toggle_btn
, store
)
184 hbar
.connect('change-value', self
.hscroll_change_value
, graphs_cbx
,
186 hbar
.connect('enter-notify-event', self
.disable_adj_update_on_draw
)
187 hbar
.connect('leave-notify-event', self
.enable_adj_update_on_draw
)
188 hbar
.connect('button-press-event', self
.hadj_pressed
)
189 hbar
.connect('button-release-event', self
.hadj_released
)
190 vbar
.connect('change-value', self
.vscroll_change_value
, graphs_cbx
,
192 vbar
.connect('enter-notify-event', self
.disable_adj_update_on_draw
)
193 vbar
.connect('leave-notify-event', self
.enable_adj_update_on_draw
)
194 vbar
.connect('button-press-event', self
.vadj_pressed
)
195 vbar
.connect('button-release-event', self
.vadj_released
)
196 vbox2
.pack_start(x10_toggle_btn
, False, False)
197 vbox2
.pack_start(span_toggle_btn
, False, False)
198 vbox2
.pack_start(save_fig_btn
, False, False)
199 vbox2
.pack_end(coords_lbl1
, False, False)
200 vbox2
.pack_end(coords_lbl2
, False, False)
202 hbox1
.pack_start(vbox2
, False, False)
209 # # Update canvas for SpanSelector of Graphs
210 for gr
in self
.graphs
:
211 if hasattr(gr
, 'span'):
214 def set_layout(self
, layout
='quad'):
215 oscopy
.Figure
.set_layout(self
, layout
)
216 iter = self
._cbx
_store
.get_iter_first()
217 for g
in self
.graphs
:
218 iter = self
._cbx
_store
.iter_next(iter)
219 if self
._layout
== 'horiz':
220 g
.span
= SpanSelector(g
, g
.onselect
, 'horizontal',
222 g
.span
.visible
= self
._cbx
_store
.get_value(iter, IOSCOPY_COL_SPAN
)
223 self
.hbar
.set_sensitive(True)
225 self
.vbar
.set_sensitive(False)
227 elif self
._layout
== 'vert':
228 g
.span
= SpanSelector(g
, g
.onselect
, 'vertical',
230 g
.span
.visible
= self
._cbx
_store
.get_value(iter, IOSCOPY_COL_SPAN
)
231 self
.hbar
.set_sensitive(False)
233 self
.vbar
.set_sensitive(True)
235 elif self
._layout
== 'quad':
236 g
.span
= MyRectangleSelector(g
, g
.onselect
, rectprops
=dict(facecolor
='red', edgecolor
= 'black', alpha
=0.5, fill
=True),
238 g
.span
.active
= self
._cbx
_store
.get_value(iter, IOSCOPY_COL_SPAN
)
239 self
.hbar
.set_sensitive(True)
241 self
.vbar
.set_sensitive(True)
244 def graphs_cbx_changed(self
, graphs_cbx
, x10_toggle_btn
, span_toggle_btn
, store
):
245 iter = graphs_cbx
.get_active_iter()
246 if store
.get_string_from_iter(iter) == '0':
247 # Do all the graphs have same state?
248 store
.iter_next(iter)
249 val
= store
.get_value(iter, IOSCOPY_COL_X10
)
250 while iter is not None:
251 if val
!= store
.get_value(iter, IOSCOPY_COL_X10
):
252 # Yes, set the button into inconsistent state
253 x10_toggle_btn
.set_inconsistent(True)
255 iter = store
.iter_next(iter)
256 iter = store
.get_iter_first()
257 store
.iter_next(iter)
258 val
= store
.get_value(iter, IOSCOPY_COL_SPAN
)
259 self
.hbar
.set_adjustment(store
.get_value(iter, IOSCOPY_COL_HADJ
))
260 self
.vbar
.set_adjustment(store
.get_value(iter, IOSCOPY_COL_VADJ
))
261 while iter is not None:
262 if val
!= store
.get_value(iter, IOSCOPY_COL_SPAN
):
263 # Yes, set the button into inconsistent state
264 span_toggle_btn
.set_inconsistent(True)
266 iter = store
.iter_next(iter)
268 x10_toggle_btn
.set_inconsistent(False)
269 x10_toggle_btn
.set_active(store
.get_value(iter, IOSCOPY_COL_X10
))
270 span_toggle_btn
.set_inconsistent(False)
271 span_toggle_btn
.set_active(store
.get_value(iter, IOSCOPY_COL_SPAN
))
272 self
.hbar
.set_adjustment(store
.get_value(iter, IOSCOPY_COL_HADJ
))
273 self
.vbar
.set_adjustment(store
.get_value(iter, IOSCOPY_COL_VADJ
))
275 def x10_toggle_btn_toggled(self
, x10_toggle_btn
, graphs_cbx
, store
):
277 iter = graphs_cbx
.get_active_iter()
278 a
= x10_toggle_btn
.get_active()
279 val
= -.1 if a
else -1 # x10 zoom
280 x10_toggle_btn
.set_inconsistent(False)
283 store
.set_value(iter, IOSCOPY_COL_X10
, a
)
284 grnum
= int(store
.get_string_from_iter(iter))
285 if store
.get_string_from_iter(iter) == '0':
286 # Set the value for all graphs
287 iter = store
.iter_next(iter)
288 while iter is not None:
289 store
.set_value(iter, IOSCOPY_COL_X10
, a
)
290 grnum
= int(store
.get_string_from_iter(iter))
291 if grnum
> len(self
.graphs
):
293 g
= self
.graphs
[grnum
- 1]
295 iter = store
.iter_next(iter)
297 g
= self
.graphs
[grnum
- 1]
299 self
.canvas
.draw_idle()
301 def _zoom_x10(self
, g
, a
):
302 layout
= self
._layout
303 result
= g
.dataLim
.frozen()
304 if layout
== 'horiz' or layout
== 'quad':
305 g
.set_xlim(*result
.intervalx
)
306 if layout
== 'vert' or layout
== 'quad':
307 g
.set_ylim(*result
.intervaly
)
309 result
= g
.bbox
.expanded(0.1, 0.1).transformed(g
.transData
.inverted())
310 if layout
== 'horiz' or layout
== 'quad':
311 g
.set_xlim(*result
.intervalx
)
312 if layout
== 'vert' or layout
== 'quad':
313 g
.set_ylim(*result
.intervaly
)
315 def span_toggle_btn_toggled(self
, span_toggle_btn
, graphs_cbx
, store
):
316 iter = graphs_cbx
.get_active_iter()
317 a
= span_toggle_btn
.get_active()
318 span_toggle_btn
.set_inconsistent(False)
320 store
.set_value(iter, IOSCOPY_COL_SPAN
, a
)
321 grnum
= int(store
.get_string_from_iter(iter))
322 if store
.get_string_from_iter(iter) == '0':
323 # Set the value for all graphs
324 iter = store
.iter_next(iter)
325 while iter is not None:
326 store
.set_value(iter, IOSCOPY_COL_SPAN
, a
)
327 grnum
= int(store
.get_string_from_iter(iter))
328 if grnum
> len(self
.graphs
):
330 if hasattr(self
.graphs
[grnum
- 1].span
, 'active'):
331 self
.graphs
[grnum
- 1].span
.active
= a
332 elif hasattr(self
.graphs
[grnum
- 1].span
, 'visible'):
333 self
.graphs
[grnum
- 1].span
.visible
= a
334 iter = store
.iter_next(iter)
336 if hasattr(self
.graphs
[grnum
- 1].span
, 'active'):
337 self
.graphs
[grnum
- 1].span
.active
= a
338 elif hasattr(self
.graphs
[grnum
- 1].span
, 'visible'):
339 self
.graphs
[grnum
- 1].span
.visible
= a
342 def save_fig_btn_clicked(self
, save_fig_btn
):
343 fname
, format
= self
.get_filechooser().get_filename_from_user()
346 self
.canvas
.print_figure(fname
, format
=format
)
348 error_msg_gtk(str(e
), parent
=self
)
350 def get_filechooser(self
):
351 # From matplotlib/backends/backend_gtk.py
352 return FileChooserDialog(
353 title
=_('Save the figure'),
355 filetypes
=self
.canvas
.get_supported_filetypes(),
356 default_filetype
=self
.canvas
.get_default_filetype())
358 def _key_press(self
, event
):
359 if event
.inaxes
is not None:
362 self
._zoom
_on
_event
(event
, DEFAULT_ZOOM_FACTOR
)
363 self
.canvas
.draw_idle()
364 elif event
.key
== 'Z':
365 self
._zoom
_on
_event
(event
, 1. / DEFAULT_ZOOM_FACTOR
)
366 self
.canvas
.draw_idle()
367 elif event
.key
== 'l':
368 result
= g
.bbox
.translated(-DEFAULT_PAN_FACTOR
, 0).transformed(g
.transData
.inverted())
369 g
.set_xlim(*result
.intervalx
)
370 g
.set_ylim(*result
.intervaly
)
371 self
.canvas
.draw_idle()
372 elif event
.key
== 'r':
373 result
= g
.bbox
.translated(DEFAULT_PAN_FACTOR
, 0).transformed(g
.transData
.inverted())
374 g
.set_xlim(*result
.intervalx
)
375 g
.set_ylim(*result
.intervaly
)
376 self
.canvas
.draw_idle()
379 def _zoom_on_event(self
, event
, factor
):
381 if g
is None or factor
== 1:
384 result
= g
.bbox
.expanded(factor
, factor
).transformed(g
.transData
.inverted())
385 # Localisation of event.xdata in the new transform
386 if layout
== 'horiz' or layout
== 'quad':
387 g
.set_xlim(*result
.intervalx
)
388 if layout
== 'vert' or layout
== 'quad':
389 g
.set_ylim(*result
.intervaly
)
390 # Then place it under cursor
391 b
= g
.transData
.transform_point([event
.xdata
, event
.ydata
])
392 result
= g
.bbox
.translated(-(event
.x
- b
[0]), -(event
.y
- b
[1])).transformed(g
.transData
.inverted())
393 if layout
== 'horiz' or layout
== 'quad':
394 g
.set_xlim(*result
.intervalx
)
395 if layout
== 'vert' or layout
== 'quad':
396 g
.set_ylim(*result
.intervaly
)
397 # Limit to data boundaries
398 (dxmin
, dxmax
) = (g
.dataLim
.xmin
, g
.dataLim
.xmax
)
399 (xmin
, xmax
) = g
.get_xbound()
400 g
.set_xbound(max(dxmin
, xmin
), min(dxmax
, xmax
))
401 (dymin
, dymax
) = (g
.dataLim
.ymin
, g
.dataLim
.ymax
)
402 (ymin
, ymax
) = g
.get_ybound()
403 g
.set_ybound(max(dymin
, ymin
), min(dymax
, ymax
))
405 def drag_data_received_cb(self
, widget
, drag_context
, x
, y
, selection
,\
406 target_type
, time
, ctxtsignals
):
407 # Event handling issue: this drag and drop callback is
408 # processed before matplotlib callback _axes_enter. Therefore
409 # when dropping, self._current_graph is not valid: it contains
411 # The workaround is to retrieve the Graph by creating a Matplotlib
412 # LocationEvent considering inverse 'y' coordinates
413 if target_type
== self
._TARGET
_TYPE
_SIGNAL
:
415 my_y
= canvas
.allocation
.height
- y
416 event
= LocationEvent('axes_enter_event', canvas
, x
, my_y
)
418 for name
in selection
.data
.split():
419 signals
[name
] = ctxtsignals
[name
]
420 if event
.inaxes
is not None:
422 event
.inaxes
.insert(signals
)
426 oscopy
.Figure
.add(self
, args
)
427 store
= self
._cbx
_store
428 iter = store
.get_iter_first()
429 iter = store
.iter_next(iter) # First item always sensitive
430 while iter is not None:
431 grnum
= int(store
.get_string_from_iter(iter))
432 if grnum
> len(self
.graphs
):
433 store
.set_value(iter, IOSCOPY_COL_VIS
, False)
434 self
._mpsel
_set
_sens
[grnum
- 1](False) # master pan
436 store
.set_value(iter, IOSCOPY_COL_VIS
, True)
437 self
._mpsel
_set
_sens
[grnum
- 1](True) # master pan
438 iter = store
.iter_next(iter)
440 def delete(self
, args
):
441 oscopy
.Figure
.delete(self
, args
)
442 store
= self
._cbx
_store
443 iter = store
.get_iter_first()
444 iter = store
.iter_next(iter) # First item always sensitive
445 while iter is not None:
446 grnum
= int(store
.get_string_from_iter(iter))
447 if grnum
> len(self
.graphs
):
448 store
.set_value(iter, IOSCOPY_COL_VIS
, False)
449 self
._mpsel
_set
_sens
[grnum
- 1](False) # master pan
450 if grnum
> 1 and self
._mpsel
_get
_act
[grnum
- 1]():
451 self
._mpsel
_set
_act
[grnum
- 2](True)
453 store
.set_value(iter, IOSCOPY_COL_VIS
, True)
454 self
._mpsel
_set
_sens
[grnum
- 1](True) # master pan
455 iter = store
.iter_next(iter)
457 def _button_press(self
, event
):
458 if event
.button
== 3:
459 menu
= self
._create
_figure
_popup
_menu
(event
.canvas
.figure
, event
.inaxes
)
461 menu
.popup(None, None, None, event
.button
, event
.guiEvent
.time
)
463 def _mouse_scroll(self
, event
):
464 if event
.button
== 'up':
465 if event
.inaxes
is None:
467 self
._zoom
_on
_event
(event
, DEFAULT_ZOOM_FACTOR
)
468 self
.canvas
.draw_idle()
469 elif event
.button
== 'down':
470 if event
.inaxes
is None:
472 self
._zoom
_on
_event
(event
, 1. / DEFAULT_ZOOM_FACTOR
)
473 self
.canvas
.draw_idle()
476 def _axes_enter(self
, event
):
477 # self._figure_enter(event)
478 # self._current_graph = event.inaxes
480 # axes_num = event.canvas.figure.axes.index(event.inaxes) + 1
481 # fig_num = self._ctxt.figures.index(self._current_figure) + 1
482 # self._app_exec('%%oselect %d-%d' % (fig_num, axes_num))
485 def _axes_leave(self
, event
):
486 # Unused for better user interaction
487 # self._current_graph = None
490 def _figure_enter(self
, event
):
491 # self._current_figure = event.canvas.figure
492 # if hasattr(event, 'inaxes') and event.inaxes is not None:
493 # axes_num = event.canvas.figure.axes.index(event.inaxes) + 1
496 # fig_num = self._ctxt.figures.index(self._current_figure) + 1
497 # self._app_exec('%%oselect %d-%d' % (fig_num, axes_num))
498 self
.canvas
.grab_focus()
501 def _figure_leave(self
, event
):
502 # self._current_figure = None
505 def _show_coords(self
, event
):
508 x
= '%.3f %s%s' % (event
.xdata
,
509 oscopy
.factors_to_names
[a
.scale_factors
[0]][0],
511 y
= '%.3f %s%s' % (event
.ydata
,
512 oscopy
.factors_to_names
[a
.scale_factors
[1]][0],
514 self
._coords
_lbl
1.set_text(x
)
515 self
._coords
_lbl
2.set_text(y
)
517 self
._coords
_lbl
1.set_text('')
518 self
._coords
_lbl
2.set_text('')
521 def _create_figure_popup_menu(self
, figure
, graph
):
522 figmenu
= gui
.menus
.FigureMenu()
523 return figmenu
.create_menu(figure
, graph
)
525 def _update_scrollbars(self
, unused
):
526 # Unused is not used but can be either a MPL event or a togglebutton
527 (lower
, upper
) = (0, 1)
528 (xpgs_min
, ypgs_min
) = (1, 1)
529 (xvs
, yvs
) = ([], [])
530 for grnum
, gr
in enumerate(self
.graphs
):
531 (xv
, xpgs
, yv
, ypgs
) = self
._update
_graph
_adj
(grnum
, gr
)
532 xpgs_min
= min(xpgs_min
, xpgs
)
533 ypgs_min
= min(ypgs_min
, ypgs
)
536 # Then for all graphs...
537 for i
, get_act
in enumerate(self
._mpsel
_get
_act
):
541 hadj
= self
._cbx
_store
[0][IOSCOPY_COL_HADJ
]
542 hadj
.configure(xvs
[i
], lower
, upper
,
543 xpgs_min
/ 10.0, xpgs_min
, xpgs_min
)
545 vadj
= self
._cbx
_store
[0][IOSCOPY_COL_VADJ
]
546 vadj
.configure(-yvs
[i
], -upper
, lower
,
547 ypgs_min
/ 10.0, ypgs_min
, ypgs_min
)
549 def _update_graph_adj(self
, grnum
, g
):
550 (lower
, upper
) = (0, 1)
552 hadj
= self
._cbx
_store
[grnum
+ 1][IOSCOPY_COL_HADJ
]
553 vadj
= self
._cbx
_store
[grnum
+ 1][IOSCOPY_COL_VADJ
]
555 # Get data Bbox and view Bbox in pixels
556 (x0data
, y0data
, wdata
, hdata
) = g
.dataLim
.transformed(g
.transData
).bounds
557 (xvmin
, yvmin
, wv
, hv
) = g
.bbox
.bounds
559 # Compute page_size and value relatively to data bounds
560 xpage_size
= wv
/ wdata
561 ypage_size
= hv
/ hdata
562 xvalue
= (xvmin
- x0data
) / wdata
563 yvalue
= (yvmin
- y0data
+ hv
) / hdata
565 xstep_increment
= xpage_size
/ 10
566 xpage_increment
= xpage_size
567 hadj
.configure(xvalue
, lower
, upper
,
568 xstep_increment
, xpage_increment
, xpage_size
)
569 ystep_increment
= ypage_size
/ 10
570 ypage_increment
= ypage_size
571 # Need to revert scroll bar to have minimum on bottom
572 vadj
.configure(-yvalue
, -upper
, lower
,
573 ystep_increment
, ypage_increment
, ypage_size
)
574 return (xvalue
, xpage_size
, yvalue
, ypage_size
)
576 def hscroll_change_value(self
, widget
, scroll
, value
, cbx
, store
):
577 if self
.layout
== 'vert':
579 iter = cbx
.get_active_iter()
581 for mp
, get_act
in enumerate(self
._mpsel
_get
_act
):
583 break # Here is the master pan graph
584 master_pan_gr
= self
.graphs
[mp
]
586 grnum
= int(store
.get_string_from_iter(iter))
587 if store
.get_string_from_iter(iter) == '0':
588 # move only graphs having the same unit as selected master
589 adj
= widget
.get_adjustment()
590 val
= adj
.get_value()
591 delta
= (val
- self
._hadjpreval
) if self
._hadjpreval
is not None else 0
592 iter = store
.iter_next(iter)
593 while iter is not None:
594 grnum
= int(store
.get_string_from_iter(iter))
595 if grnum
> len(self
.graphs
):
597 g
= self
.graphs
[grnum
- 1]
599 if self
._hadjpreval
is None or g
.get_unit()[0] != master_pan_gr
.get_unit()[0]:
600 iter = store
.iter_next(iter)
602 hadj
= self
._cbx
_store
[grnum
][IOSCOPY_COL_HADJ
]
603 curval
= hadj
.get_value()
604 if ((val
< curval
and delta
< 0) or (val
> curval
and delta
> 0)) and val
< 1 - hadj
.get_page_size():
605 self
._translate
_to
(g
, val
, None)
606 hadj
.set_value(val
) # Because the callback is disabled
607 iter = store
.iter_next(iter)
608 self
._hadjpreval
= widget
.get_adjustment().get_value()
610 g
= self
.graphs
[grnum
- 1]
612 self
._translate
_to
(g
, widget
.get_value(), None)
613 self
.canvas
.draw_idle()
616 def vscroll_change_value(self
, widget
, scroll
, value
, cbx
, store
):
617 if self
.layout
== 'horiz':
619 iter = cbx
.get_active_iter()
621 for mp
, get_act
in enumerate(self
._mpsel
_get
_act
):
623 break # Here is the master pan graph
624 master_pan_gr
= self
.graphs
[mp
]
626 grnum
= int(store
.get_string_from_iter(iter))
627 if store
.get_string_from_iter(iter) == '0':
628 # Move only graphs having the same unit as selected master
629 adj
= widget
.get_adjustment()
630 val
= adj
.get_value()
631 delta
= (val
- self
._vadjpreval
) if self
._vadjpreval
is not None else 0
632 iter = store
.iter_next(iter)
633 while iter is not None:
634 grnum
= int(store
.get_string_from_iter(iter))
635 if grnum
> len(self
.graphs
):
637 g
= self
.graphs
[grnum
- 1]
639 if self
._vadjpreval
is None or g
.get_unit()[1] != master_pan_gr
.get_unit()[1]:
640 iter = store
.iter_next(iter)
642 vadj
= self
._cbx
_store
[grnum
][IOSCOPY_COL_VADJ
]
643 curval
= vadj
.get_value()
644 if ((val
< curval
and delta
< 0) or (val
> curval
and delta
> 0)) and val
< -vadj
.get_page_size():
645 self
._translate
_to
(g
, None, val
)
646 vadj
.set_value(val
) # Because the callback is disabled
647 iter = store
.iter_next(iter)
648 self
._vadjpreval
= widget
.get_adjustment().get_value()
650 g
= self
.graphs
[grnum
- 1]
652 self
._translate
_to
(g
, None, widget
.get_value())
653 self
.canvas
.draw_idle()
656 def _translate_to(self
, g
, xvalue
, yvalue
):
659 (x0data
, y0data
, wdata
, hdata
) = g
.dataLim
.transformed(g
.transData
).bounds
660 x0
, y0
, w
, h
= g
.bbox
.bounds
661 x0_new
= (xvalue
* wdata
+ x0data
) if xvalue
is not None else x0
662 # Need to revert scroll bar to have minimum on bottom
663 y0_new
= (-yvalue
* hdata
+ y0data
- h
) if yvalue
is not None else y0
664 result
= Bbox
.from_bounds(x0_new
, y0_new
, w
, h
).transformed(g
.transData
.inverted())
665 g
.set_xlim(*result
.intervalx
)
666 g
.set_ylim(*result
.intervaly
)
668 def disable_adj_update_on_draw(self
, widget
, event
):
670 if widget
== self
.hbar
and layout
not in ['horiz', 'quad']:
672 if widget
== self
.vbar
and layout
not in ['vert', 'quad']:
674 if self
._draw
_hid
is not None:
675 self
.canvas
.mpl_disconnect(self
._draw
_hid
)
676 self
._draw
_hid
= None
678 def enable_adj_update_on_draw(self
, widget
, event
):
680 if widget
== self
.hbar
and layout
not in ['horiz', 'quad']:
682 if widget
== self
.vbar
and layout
not in ['vert', 'quad']:
684 if self
._draw
_hid
is None and not event
.state
& gtk
.gdk
.BUTTON1_MASK
:
685 self
._draw
_hid
= self
.canvas
.mpl_connect('draw_event', self
._update
_scrollbars
)
687 def hadj_pressed(self
, widget
, event
):
688 self
._hadjpreval
= widget
.get_value()
690 def hadj_released(self
, widget
, event
):
691 self
._hadjpreval
= None
693 def vadj_pressed(self
, widget
, event
):
694 self
._vadjpreval
= widget
.get_value()
696 def vadj_released(self
, widget
, event
):
697 self
._vadjpreval
= None
699 layout
= property(oscopy
.Figure
.get_layout
, set_layout
)
701 def error_msg_gtk(msg
, parent
=None):
702 # From matplotlib/backends/backend_gtk.py
703 if parent
is not None: # find the toplevel gtk.Window
704 parent
= parent
.get_toplevel()
705 if parent
.flags() & gtk
.TOPLEVEL
== 0:
708 if not is_string_like(msg
):
709 msg
= ','.join(map(str,msg
))
711 dialog
= gtk
.MessageDialog(
713 type = gtk
.MESSAGE_ERROR
,
714 buttons
= gtk
.BUTTONS_OK
,
715 message_format
= msg
)