15
15
from kivy .uix .widget import Widget
16
16
from kivy .vector import Vector
17
17
from matplotlib .backends .backend_agg import FigureCanvasAgg
18
+ from matplotlib import cbook
19
+ from weakref import WeakKeyDictionary
18
20
from kivy .metrics import dp
19
21
import numpy as np
20
22
@@ -48,6 +50,10 @@ class MatplotFigure(Widget):
48
50
legend_instance = ObjectProperty (None )
49
51
legend_do_scroll_x = BooleanProperty (True )
50
52
legend_do_scroll_y = BooleanProperty (True )
53
+ do_pan_x = BooleanProperty (True )
54
+ do_pan_y = BooleanProperty (True )
55
+ do_zoom_x = BooleanProperty (True )
56
+ do_zoom_y = BooleanProperty (True )
51
57
52
58
def on_figure (self , obj , value ):
53
59
self .figcanvas = _FigureCanvas (self .figure , self )
@@ -110,6 +116,14 @@ def __init__(self, **kwargs):
110
116
#background
111
117
self .background = None
112
118
self .background_patch_copy = None
119
+
120
+ #manage adjust x and y
121
+ self .anchor_x = None
122
+ self .anchor_y = None
123
+
124
+ #manage back and next event
125
+ self ._nav_stack = cbook .Stack ()
126
+ self .set_history_buttons ()
113
127
114
128
self .bind (size = self ._onSize )
115
129
@@ -274,6 +288,66 @@ def home(self) -> None:
274
288
ax .figure .canvas .draw_idle ()
275
289
ax .figure .canvas .flush_events ()
276
290
291
+ def back (self , * args ):
292
+ """
293
+ Move back up the view lim stack.
294
+ For convenience of being directly connected as a GUI callback, which
295
+ often get passed additional parameters, this method accepts arbitrary
296
+ parameters, but does not use them.
297
+ """
298
+ self ._nav_stack .back ()
299
+ self .set_history_buttons ()
300
+ self ._update_view ()
301
+
302
+ def forward (self , * args ):
303
+ """
304
+ Move forward in the view lim stack.
305
+ For convenience of being directly connected as a GUI callback, which
306
+ often get passed additional parameters, this method accepts arbitrary
307
+ parameters, but does not use them.
308
+ """
309
+ self ._nav_stack .forward ()
310
+ self .set_history_buttons ()
311
+ self ._update_view ()
312
+
313
+ def push_current (self ):
314
+ """Push the current view limits and position onto the stack."""
315
+ self ._nav_stack .push (
316
+ WeakKeyDictionary (
317
+ {ax : (ax ._get_view (),
318
+ # Store both the original and modified positions.
319
+ (ax .get_position (True ).frozen (),
320
+ ax .get_position ().frozen ()))
321
+ for ax in self .figure .axes }))
322
+ self .set_history_buttons ()
323
+
324
+ def update (self ):
325
+ """Reset the Axes stack."""
326
+ self ._nav_stack .clear ()
327
+ self .set_history_buttons ()
328
+
329
+ def _update_view (self ):
330
+ """
331
+ Update the viewlim and position from the view and position stack for
332
+ each Axes.
333
+ """
334
+ nav_info = self ._nav_stack ()
335
+ if nav_info is None :
336
+ return
337
+ # Retrieve all items at once to avoid any risk of GC deleting an Axes
338
+ # while in the middle of the loop below.
339
+ items = list (nav_info .items ())
340
+ for ax , (view , (pos_orig , pos_active )) in items :
341
+ ax ._set_view (view )
342
+ # Restore both the original and modified positions
343
+ ax ._set_position (pos_orig , 'original' )
344
+ ax ._set_position (pos_active , 'active' )
345
+ self .figure .canvas .draw_idle ()
346
+ self .figure .canvas .flush_events ()
347
+
348
+ def set_history_buttons (self ):
349
+ """Enable or disable the back/forward button."""
350
+
277
351
def reset_touch (self ) -> None :
278
352
""" reset touch
279
353
@@ -331,14 +405,25 @@ def transform_with_touch(self, event):
331
405
changed = False
332
406
333
407
if len (self ._touches ) == self .translation_touches :
408
+
334
409
if self .touch_mode == 'pan' :
410
+ if self ._nav_stack () is None :
411
+ self .push_current ()
335
412
self .apply_pan (self .axes , event )
336
-
413
+
414
+ if self .touch_mode == 'pan_x' or self .touch_mode == 'pan_y' \
415
+ or self .touch_mode == 'adjust_x' or self .touch_mode == 'adjust_y' :
416
+ if self ._nav_stack () is None :
417
+ self .push_current ()
418
+ self .apply_pan (self .axes , event , mode = self .touch_mode )
419
+
337
420
elif self .touch_mode == 'drag_legend' :
338
421
if self .legend_instance :
339
422
self .apply_drag_legend (self .axes , event )
340
423
341
424
elif self .touch_mode == 'zoombox' :
425
+ if self ._nav_stack () is None :
426
+ self .push_current ()
342
427
real_x , real_y = event .x - self .pos [0 ], event .y - self .pos [1 ]
343
428
self .draw_box (event , self .x_init ,self .y_init , event .x , real_y )
344
429
@@ -483,6 +568,10 @@ def on_touch_up(self, event):
483
568
event .ungrab (self )
484
569
del self ._last_touch_pos [event ]
485
570
self ._touches .remove (event )
571
+ if self .touch_mode == 'pan' or self .touch_mode == 'zoombox' or \
572
+ self .touch_mode == 'pan_x' or self .touch_mode == 'pan_y' \
573
+ or self .touch_mode == 'adjust_x' or self .touch_mode == 'adjust_y' :
574
+ self .push_current ()
486
575
487
576
x , y = event .x , event .y
488
577
if abs (self ._box_size [0 ]) > 1 or abs (self ._box_size [1 ]) > 1 or self .touch_mode == 'zoombox' :
@@ -501,6 +590,9 @@ def on_touch_up(self, event):
501
590
if self .do_update :
502
591
self .update_lim ()
503
592
593
+ self .anchor_x = None
594
+ self .anchor_y = None
595
+
504
596
ax = self .axes
505
597
self .background = None
506
598
ax .figure .canvas .draw_idle ()
@@ -526,8 +618,10 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0),new_line=None):
526
618
relx = (cur_xlim [1 ] - xdata ) / (cur_xlim [1 ] - cur_xlim [0 ])
527
619
rely = (cur_ylim [1 ] - ydata ) / (cur_ylim [1 ] - cur_ylim [0 ])
528
620
529
- ax .set_xlim ([xdata - new_width * (1 - relx ), xdata + new_width * (relx )])
530
- ax .set_ylim ([ydata - new_height * (1 - rely ), ydata + new_height * (rely )])
621
+ if self .do_zoom_x :
622
+ ax .set_xlim ([xdata - new_width * (1 - relx ), xdata + new_width * (relx )])
623
+ if self .do_zoom_y :
624
+ ax .set_ylim ([ydata - new_height * (1 - rely ), ydata + new_height * (rely )])
531
625
532
626
if self .fast_draw :
533
627
#use blit method
@@ -547,7 +641,7 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0),new_line=None):
547
641
ax .figure .canvas .draw_idle ()
548
642
ax .figure .canvas .flush_events ()
549
643
550
- def apply_pan (self , ax , event ):
644
+ def apply_pan (self , ax , event , mode = 'pan' ):
551
645
""" pan method """
552
646
553
647
trans = ax .transData .inverted ()
@@ -558,10 +652,46 @@ def apply_pan(self, ax, event):
558
652
559
653
cur_xlim = ax .get_xlim ()
560
654
cur_ylim = ax .get_ylim ()
561
- cur_xlim -= dx / 2
562
- cur_ylim -= dy / 2
563
- ax .set_xlim (cur_xlim )
564
- ax .set_ylim (cur_ylim )
655
+ if not mode == 'pan_y' and not mode == 'adjust_y' :
656
+ if mode == 'adjust_x' :
657
+ if self .anchor_x is None :
658
+ midpoint = (cur_xlim [1 ] - cur_xlim [0 ])/ 2
659
+ if xdata > midpoint :
660
+ self .anchor_x = 'left'
661
+ else :
662
+ self .anchor_x = 'right'
663
+ if self .anchor_x == 'left' :
664
+ if xdata > cur_xlim [0 ]:
665
+ cur_xlim -= dx / 2
666
+ ax .set_xlim (None ,cur_xlim [1 ])
667
+ else :
668
+ if xdata < cur_xlim [1 ]:
669
+ cur_xlim -= dx / 2
670
+ ax .set_xlim (cur_xlim [0 ],None )
671
+ else :
672
+ cur_xlim -= dx / 2
673
+ ax .set_xlim (cur_xlim )
674
+
675
+ if not mode == 'pan_x' and not mode == 'adjust_x' :
676
+ if mode == 'adjust_y' :
677
+ if self .anchor_y is None :
678
+ midpoint = (cur_ylim [1 ] - cur_ylim [0 ])/ 2
679
+ if ydata > midpoint :
680
+ self .anchor_y = 'top'
681
+ else :
682
+ self .anchor_y = 'bottom'
683
+
684
+ if self .anchor_y == 'top' :
685
+ if ydata > cur_ylim [0 ]:
686
+ cur_ylim -= dy / 2
687
+ ax .set_ylim (None ,cur_ylim [1 ])
688
+ else :
689
+ if ydata < cur_ylim [1 ]:
690
+ cur_ylim -= dy / 2
691
+ ax .set_ylim (cur_ylim [0 ],None )
692
+ else :
693
+ cur_ylim -= dy / 2
694
+ ax .set_ylim (cur_ylim )
565
695
566
696
if self .fast_draw :
567
697
#use blit method
@@ -649,8 +779,10 @@ def zoom_factory(self, event, ax, base_scale=1.1):
649
779
relx = (cur_xlim [1 ] - xdata ) / (cur_xlim [1 ] - cur_xlim [0 ])
650
780
rely = (cur_ylim [1 ] - ydata ) / (cur_ylim [1 ] - cur_ylim [0 ])
651
781
652
- ax .set_xlim ([xdata - new_width * (1 - relx ), xdata + new_width * (relx )])
653
- ax .set_ylim ([ydata - new_height * (1 - rely ), ydata + new_height * (rely )])
782
+ if self .do_zoom_x :
783
+ ax .set_xlim ([xdata - new_width * (1 - relx ), xdata + new_width * (relx )])
784
+ if self .do_zoom_y :
785
+ ax .set_ylim ([ydata - new_height * (1 - rely ), ydata + new_height * (rely )])
654
786
655
787
ax .figure .canvas .draw_idle ()
656
788
ax .figure .canvas .flush_events ()
0 commit comments