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 MatplotFigureScatter(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 )
@@ -116,6 +122,14 @@ def __init__(self, **kwargs):
116
122
#background
117
123
self .background = None
118
124
self .background_patch_copy = None
125
+
126
+ #manage adjust x and y
127
+ self .anchor_x = None
128
+ self .anchor_y = None
129
+
130
+ #manage back and next event
131
+ self ._nav_stack = cbook .Stack ()
132
+ self .set_history_buttons ()
119
133
120
134
self .bind (size = self ._onSize )
121
135
@@ -392,6 +406,66 @@ def home(self) -> None:
392
406
ax .figure .canvas .draw_idle ()
393
407
ax .figure .canvas .flush_events ()
394
408
409
+ def back (self , * args ):
410
+ """
411
+ Move back up the view lim stack.
412
+ For convenience of being directly connected as a GUI callback, which
413
+ often get passed additional parameters, this method accepts arbitrary
414
+ parameters, but does not use them.
415
+ """
416
+ self ._nav_stack .back ()
417
+ self .set_history_buttons ()
418
+ self ._update_view ()
419
+
420
+ def forward (self , * args ):
421
+ """
422
+ Move forward in the view lim stack.
423
+ For convenience of being directly connected as a GUI callback, which
424
+ often get passed additional parameters, this method accepts arbitrary
425
+ parameters, but does not use them.
426
+ """
427
+ self ._nav_stack .forward ()
428
+ self .set_history_buttons ()
429
+ self ._update_view ()
430
+
431
+ def push_current (self ):
432
+ """Push the current view limits and position onto the stack."""
433
+ self ._nav_stack .push (
434
+ WeakKeyDictionary (
435
+ {ax : (ax ._get_view (),
436
+ # Store both the original and modified positions.
437
+ (ax .get_position (True ).frozen (),
438
+ ax .get_position ().frozen ()))
439
+ for ax in self .figure .axes }))
440
+ self .set_history_buttons ()
441
+
442
+ def update (self ):
443
+ """Reset the Axes stack."""
444
+ self ._nav_stack .clear ()
445
+ self .set_history_buttons ()
446
+
447
+ def _update_view (self ):
448
+ """
449
+ Update the viewlim and position from the view and position stack for
450
+ each Axes.
451
+ """
452
+ nav_info = self ._nav_stack ()
453
+ if nav_info is None :
454
+ return
455
+ # Retrieve all items at once to avoid any risk of GC deleting an Axes
456
+ # while in the middle of the loop below.
457
+ items = list (nav_info .items ())
458
+ for ax , (view , (pos_orig , pos_active )) in items :
459
+ ax ._set_view (view )
460
+ # Restore both the original and modified positions
461
+ ax ._set_position (pos_orig , 'original' )
462
+ ax ._set_position (pos_active , 'active' )
463
+ self .figure .canvas .draw_idle ()
464
+ self .figure .canvas .flush_events ()
465
+
466
+ def set_history_buttons (self ):
467
+ """Enable or disable the back/forward button."""
468
+
395
469
def reset_touch (self ) -> None :
396
470
""" reset touch
397
471
@@ -450,13 +524,23 @@ def transform_with_touch(self, event):
450
524
451
525
if len (self ._touches ) == self .translation_touches :
452
526
if self .touch_mode == 'pan' :
527
+ if self ._nav_stack () is None :
528
+ self .push_current ()
453
529
self .apply_pan (self .axes , event )
530
+
531
+ if self .touch_mode == 'pan_x' or self .touch_mode == 'pan_y' \
532
+ or self .touch_mode == 'adjust_x' or self .touch_mode == 'adjust_y' :
533
+ if self ._nav_stack () is None :
534
+ self .push_current ()
535
+ self .apply_pan (self .axes , event , mode = self .touch_mode )
454
536
455
537
elif self .touch_mode == 'drag_legend' :
456
538
if self .legend_instance :
457
539
self .apply_drag_legend (self .axes , event )
458
540
459
541
elif self .touch_mode == 'zoombox' :
542
+ if self ._nav_stack () is None :
543
+ self .push_current ()
460
544
real_x , real_y = event .x - self .pos [0 ], event .y - self .pos [1 ]
461
545
self .draw_box (event , self .x_init ,self .y_init , event .x , real_y )
462
546
@@ -601,7 +685,11 @@ def on_touch_up(self, event):
601
685
event .ungrab (self )
602
686
del self ._last_touch_pos [event ]
603
687
self ._touches .remove (event )
604
-
688
+ if self .touch_mode == 'pan' or self .touch_mode == 'zoombox' or \
689
+ self .touch_mode == 'pan_x' or self .touch_mode == 'pan_y' \
690
+ or self .touch_mode == 'adjust_x' or self .touch_mode == 'adjust_y' :
691
+ self .push_current ()
692
+
605
693
x , y = event .x , event .y
606
694
if abs (self ._box_size [0 ]) > 1 or abs (self ._box_size [1 ]) > 1 or self .touch_mode == 'zoombox' :
607
695
self .reset_box ()
@@ -619,6 +707,9 @@ def on_touch_up(self, event):
619
707
if self .do_update :
620
708
self .update_lim ()
621
709
710
+ self .anchor_x = None
711
+ self .anchor_y = None
712
+
622
713
ax = self .axes
623
714
self .background = None
624
715
ax .figure .canvas .draw_idle ()
@@ -644,8 +735,10 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0),new_line=None):
644
735
relx = (cur_xlim [1 ] - xdata ) / (cur_xlim [1 ] - cur_xlim [0 ])
645
736
rely = (cur_ylim [1 ] - ydata ) / (cur_ylim [1 ] - cur_ylim [0 ])
646
737
647
- ax .set_xlim ([xdata - new_width * (1 - relx ), xdata + new_width * (relx )])
648
- ax .set_ylim ([ydata - new_height * (1 - rely ), ydata + new_height * (rely )])
738
+ if self .do_zoom_x :
739
+ ax .set_xlim ([xdata - new_width * (1 - relx ), xdata + new_width * (relx )])
740
+ if self .do_zoom_y :
741
+ ax .set_ylim ([ydata - new_height * (1 - rely ), ydata + new_height * (rely )])
649
742
650
743
if self .fast_draw :
651
744
#use blit method
@@ -675,7 +768,7 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0),new_line=None):
675
768
ax .figure .canvas .draw_idle ()
676
769
ax .figure .canvas .flush_events ()
677
770
678
- def apply_pan (self , ax , event ):
771
+ def apply_pan (self , ax , event , mode = 'pan' ):
679
772
""" pan method """
680
773
681
774
trans = ax .transData .inverted ()
@@ -686,10 +779,46 @@ def apply_pan(self, ax, event):
686
779
687
780
cur_xlim = ax .get_xlim ()
688
781
cur_ylim = ax .get_ylim ()
689
- cur_xlim -= dx / 2
690
- cur_ylim -= dy / 2
691
- ax .set_xlim (cur_xlim )
692
- ax .set_ylim (cur_ylim )
782
+ if not mode == 'pan_y' and not mode == 'adjust_y' :
783
+ if mode == 'adjust_x' :
784
+ if self .anchor_x is None :
785
+ midpoint = (cur_xlim [1 ] - cur_xlim [0 ])/ 2
786
+ if xdata > midpoint :
787
+ self .anchor_x = 'left'
788
+ else :
789
+ self .anchor_x = 'right'
790
+ if self .anchor_x == 'left' :
791
+ if xdata > cur_xlim [0 ]:
792
+ cur_xlim -= dx / 2
793
+ ax .set_xlim (None ,cur_xlim [1 ])
794
+ else :
795
+ if xdata < cur_xlim [1 ]:
796
+ cur_xlim -= dx / 2
797
+ ax .set_xlim (cur_xlim [0 ],None )
798
+ else :
799
+ cur_xlim -= dx / 2
800
+ ax .set_xlim (cur_xlim )
801
+
802
+ if not mode == 'pan_x' and not mode == 'adjust_x' :
803
+ if mode == 'adjust_y' :
804
+ if self .anchor_y is None :
805
+ midpoint = (cur_ylim [1 ] - cur_ylim [0 ])/ 2
806
+ if ydata > midpoint :
807
+ self .anchor_y = 'top'
808
+ else :
809
+ self .anchor_y = 'bottom'
810
+
811
+ if self .anchor_y == 'top' :
812
+ if ydata > cur_ylim [0 ]:
813
+ cur_ylim -= dy / 2
814
+ ax .set_ylim (None ,cur_ylim [1 ])
815
+ else :
816
+ if ydata < cur_ylim [1 ]:
817
+ cur_ylim -= dy / 2
818
+ ax .set_ylim (cur_ylim [0 ],None )
819
+ else :
820
+ cur_ylim -= dy / 2
821
+ ax .set_ylim (cur_ylim )
693
822
694
823
if self .fast_draw :
695
824
#use blit method
@@ -786,8 +915,10 @@ def zoom_factory(self, event, ax, base_scale=1.1):
786
915
relx = (cur_xlim [1 ] - xdata ) / (cur_xlim [1 ] - cur_xlim [0 ])
787
916
rely = (cur_ylim [1 ] - ydata ) / (cur_ylim [1 ] - cur_ylim [0 ])
788
917
789
- ax .set_xlim ([xdata - new_width * (1 - relx ), xdata + new_width * (relx )])
790
- ax .set_ylim ([ydata - new_height * (1 - rely ), ydata + new_height * (rely )])
918
+ if self .do_zoom_x :
919
+ ax .set_xlim ([xdata - new_width * (1 - relx ), xdata + new_width * (relx )])
920
+ if self .do_zoom_y :
921
+ ax .set_ylim ([ydata - new_height * (1 - rely ), ydata + new_height * (rely )])
791
922
792
923
ax .figure .canvas .draw_idle ()
793
924
ax .figure .canvas .flush_events ()
0 commit comments