33"""
44
55import math
6+ import copy
67
78import matplotlib
89matplotlib .use ('Agg' )
@@ -42,7 +43,9 @@ class MatplotFigure(Widget):
4243 pos_x_rect_hor = NumericProperty (0 )
4344 pos_y_rect_hor = NumericProperty (0 )
4445 pos_x_rect_ver = NumericProperty (0 )
45- pos_y_rect_ver = NumericProperty (0 )
46+ pos_y_rect_ver = NumericProperty (0 )
47+ invert_rect_ver = BooleanProperty (False )
48+ invert_rect_hor = BooleanProperty (False )
4649
4750 def on_figure (self , obj , value ):
4851 self .figcanvas = _FigureCanvas (self .figure , self )
@@ -53,6 +56,15 @@ def on_figure(self, obj, value):
5356 self .width = w
5457 self .height = h
5558
59+ if self .figure .axes [0 ]:
60+ #add copy patch
61+ ax = self .figure .axes [0 ]
62+ patch_cpy = copy .copy (ax .patch )
63+ patch_cpy .set_visible (False )
64+ ax .spines [:].set_zorder (10 )
65+ patch_cpy .set_zorder (9 )
66+ self .background_patch_copy = ax .add_patch (patch_cpy )
67+
5668 # Texture
5769 self ._img_texture = Texture .create (size = (w , h ))
5870
@@ -85,7 +97,10 @@ def __init__(self, **kwargs):
8597 #clear touches on touch up
8698 self ._touches = []
8799 self ._last_touch_pos = {}
88-
100+
101+ #background
102+ self .background = None
103+ self .background_patch_copy = None
89104
90105 self .bind (size = self ._onSize )
91106
@@ -108,8 +123,7 @@ def register_lines(self,lines:list) -> None:
108123
109124 #white background for blit method (fast draw)
110125 props = dict (boxstyle = 'square' ,edgecolor = 'w' , facecolor = 'w' , alpha = 1.0 )
111- self .text_background = self .axes .text (0.5 , 1.01 , 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' , color = 'w' , transform = self .axes .transAxes , bbox = props )
112-
126+
113127 #cursor text
114128 self .text = self .axes .text (0.52 , 1.01 , '' , transform = self .axes .transAxes , bbox = props )
115129
@@ -211,19 +225,22 @@ def hover(self, event) -> None:
211225 #x y label
212226 self .text .set_text (f"x={ x } , y={ y } " )
213227
214- #blit method (always use because same visual effect as draw)
215- self .axes .draw_artist (self .axes .patch )
216- self .axes .draw_artist (self .text_background )
228+ #blit method (always use because same visual effect as draw)
229+ if self .background is None :
230+ self .set_cross_hair_visible (False )
231+ self .axes .figure .canvas .draw_idle ()
232+ self .axes .figure .canvas .flush_events ()
233+ self .background = self .axes .figure .canvas .copy_from_bbox (self .axes .figure .bbox )
234+ self .set_cross_hair_visible (True )
235+
236+ self .axes .figure .canvas .restore_region (self .background )
217237 self .axes .draw_artist (self .text )
218- self .axes .draw_artist (list (self .axes .spines .values ())[0 ])
219238
220- for line in self .axes .lines :
221- self .axes .draw_artist (line )
239+ self .axes .draw_artist ( self . horizontal_line )
240+ self .axes .draw_artist (self . vertical_line )
222241
223- mybbox = self .my_blit_box (ax .bbox .bounds [0 ],ax .bbox .bounds [1 ],ax .bbox .bounds [2 ],ax .bbox .bounds [3 ]+ 50 )
224-
225242 #draw (blit method)
226- self .axes .figure .canvas .blit (mybbox )
243+ self .axes .figure .canvas .blit (self . axes . bbox )
227244 self .axes .figure .canvas .flush_events ()
228245
229246 #if touch is too far, hide cross hair cursor
@@ -236,12 +253,18 @@ def home(self) -> None:
236253 Return:
237254 None
238255 """
239- ax = self .axes
240- ax .set_xlim (self .xmin , self .xmax )
241- ax .set_ylim (self .ymin , self .ymax )
242-
243- ax .figure .canvas .draw_idle ()
244- ax .figure .canvas .flush_events ()
256+ #do nothing is all min/max are not set
257+ if self .xmin is not None and \
258+ self .xmax is not None and \
259+ self .ymin is not None and \
260+ self .ymax is not None :
261+
262+ ax = self .axes
263+ ax .set_xlim (self .xmin , self .xmax )
264+ ax .set_ylim (self .ymin , self .ymax )
265+
266+ ax .figure .canvas .draw_idle ()
267+ ax .figure .canvas .flush_events ()
245268
246269 def reset_touch (self ) -> None :
247270 """ reset touch
@@ -251,11 +274,6 @@ def reset_touch(self) -> None:
251274 """
252275 self ._touches = []
253276 self ._last_touch_pos = {}
254-
255- @staticmethod
256- def my_blit_box (x0 , y0 , width , height ):
257- """ build custom matplotlib bbox """
258- return Bbox .from_bounds (x0 , y0 , width , height )
259277
260278 def _get_scale (self ):
261279 """ kivy scatter _get_scale method """
@@ -396,7 +414,10 @@ def on_touch_down(self, event):
396414 event .grab (self )
397415 self ._touches .append (event )
398416 self ._last_touch_pos [event ] = event .pos
399-
417+ if len (self ._touches )> 1 :
418+ #new touch, reset background
419+ self .background = None
420+
400421 return True
401422
402423 else :
@@ -455,6 +476,7 @@ def on_touch_up(self, event):
455476 self .update_lim ()
456477
457478 ax = self .axes
479+ self .background = None
458480 ax .figure .canvas .draw_idle ()
459481 ax .figure .canvas .flush_events ()
460482
@@ -463,7 +485,7 @@ def on_touch_up(self, event):
463485 def apply_zoom (self , scale_factor , ax , anchor = (0 , 0 ),new_line = None ):
464486 """ zoom touch method """
465487
466- x = anchor [0 ]
488+ x = anchor [0 ]- self . pos [ 0 ]
467489 y = anchor [1 ]- self .pos [1 ]
468490
469491 trans = ax .transData .inverted ()
@@ -483,11 +505,13 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0),new_line=None):
483505
484506 if self .fast_draw :
485507 #use blit method
486- ax .draw_artist (ax .patch )
487-
488- #if you want the left spline during on_move (slower)
489- if self .draw_left_spline :
490- ax .draw_artist (list (ax .spines .values ())[0 ])
508+ if self .background is None :
509+ self .background_patch_copy .set_visible (True )
510+ ax .figure .canvas .draw_idle ()
511+ ax .figure .canvas .flush_events ()
512+ self .background = ax .figure .canvas .copy_from_bbox (ax .figure .bbox )
513+ self .background_patch_copy .set_visible (False )
514+ ax .figure .canvas .restore_region (self .background )
491515
492516 for line in ax .lines :
493517 ax .draw_artist (line )
@@ -514,11 +538,14 @@ def apply_pan(self, ax, event):
514538 ax .set_ylim (cur_ylim )
515539
516540 if self .fast_draw :
517- #use blit method
518- ax .draw_artist (ax .patch )
519- #if you want the left spline during on_move (slower)
520- if self .draw_left_spline :
521- ax .draw_artist (list (ax .spines .values ())[0 ])
541+ #use blit method
542+ if self .background is None :
543+ self .background_patch_copy .set_visible (True )
544+ ax .figure .canvas .draw_idle ()
545+ ax .figure .canvas .flush_events ()
546+ self .background = ax .figure .canvas .copy_from_bbox (ax .figure .bbox )
547+ self .background_patch_copy .set_visible (False )
548+ ax .figure .canvas .restore_region (self .background )
522549
523550 for line in ax .lines :
524551 ax .draw_artist (line )
@@ -611,6 +638,8 @@ def reset_box(self):
611638 self ._pos_y_rect_ver = 0
612639 self ._alpha_hor = 0
613640 self ._alpha_ver = 0
641+ self .invert_rect_hor = False
642+ self .invert_rect_ver = False
614643
615644 def draw_box (self , event , x0 , y0 , x1 , y1 ) -> None :
616645 """ Draw zoombox method
@@ -680,10 +709,10 @@ def draw_box(self, event, x0, y0, x1, y1) -> None:
680709 x0 = x1_min [0 ][0 ]+ pos_x
681710
682711 x0_max = self .axes .transData .transform ([(xmax ,ymin )])
683- x1 = x0_max [0 ][0 ]+ pos_x
712+ x1 = x0_max [0 ][0 ]+ pos_x
684713
685- self ._alpha_ver = 1
686-
714+ self ._alpha_ver = 1
715+
687716 elif abs (y1 - y0 )< dp (20 ) and abs (x1 - x0 )> self .minzoom :
688717 self .pos_x_rect_hor = x0
689718 self .pos_y_rect_hor = y0
@@ -699,6 +728,15 @@ def draw_box(self, event, x0, y0, x1, y1) -> None:
699728 else :
700729 self ._alpha_hor = 0
701730 self ._alpha_ver = 0
731+
732+ if x1 > x0 :
733+ self .invert_rect_ver = False
734+ else :
735+ self .invert_rect_ver = True
736+ if y1 > y0 :
737+ self .invert_rect_hor = False
738+ else :
739+ self .invert_rect_hor = True
702740
703741 self ._box_pos = x0 , y0
704742 self ._box_size = x1 - x0 , y1 - y0
@@ -754,39 +792,47 @@ def blit(self, bbox=None):
754792 source: 'border.png'
755793 pos: self._box_pos
756794 size: self._box_size
757- border: 3, 3, 3, 3
758-
795+ border:
796+ dp(1) if root.invert_rect_hor else -dp(1), \
797+ dp(1) if root.invert_rect_ver else -dp(1), \
798+ dp(1) if root.invert_rect_hor else -dp(1), \
799+ dp(1) if root.invert_rect_ver else -dp(1)
800+
759801 canvas.after:
760802 #horizontal rectangle left
761803 Color:
762804 rgba:0, 0, 0, self._alpha_hor
763805 Line:
764806 width: dp(1)
765807 rectangle:
766- (self.pos_x_rect_hor-dp(3), self.pos_y_rect_hor-dp(20), dp(4),dp(40))
808+ (self.pos_x_rect_hor+dp(1) if root.invert_rect_ver \
809+ else self.pos_x_rect_hor-dp(4),self.pos_y_rect_hor-dp(20), dp(4),dp(40))
767810
768811 #horizontal rectangle right
769812 Color:
770813 rgba:0, 0, 0, self._alpha_hor
771814 Line:
772815 width: dp(1)
773816 rectangle:
774- (self.pos_x_rect_hor-dp(1)+self._box_size[0], self.pos_y_rect_hor-dp(20), dp(4),dp(40))
817+ (self.pos_x_rect_hor-dp(4)+self._box_size[0] if root.invert_rect_ver \
818+ else self.pos_x_rect_hor+dp(1)+self._box_size[0], self.pos_y_rect_hor-dp(20), dp(4),dp(40))
775819
776820 #vertical rectangle bottom
777821 Color:
778822 rgba:0, 0, 0, self._alpha_ver
779823 Line:
780824 width: dp(1)
781825 rectangle:
782- (self.pos_x_rect_ver-dp(20), self.pos_y_rect_ver-dp(1), dp(40),dp(4))
826+ (self.pos_x_rect_ver-dp(20),self.pos_y_rect_ver+dp(1) if root.invert_rect_hor else \
827+ self.pos_y_rect_ver-dp(4), dp(40),dp(4))
783828
784829 #vertical rectangle top
785830 Color:
786831 rgba:0, 0, 0, self._alpha_ver
787832 Line:
788833 width: dp(1)
789834 rectangle:
790- (self.pos_x_rect_ver-dp(20), self.pos_y_rect_ver-dp(3)+self._box_size[1], dp(40),dp(4))
791-
835+ (self.pos_x_rect_ver-dp(20),self.pos_y_rect_ver-dp(4)+self._box_size[1] \
836+ if root.invert_rect_hor else self.pos_y_rect_ver+dp(1)+self._box_size[1], \
837+ dp(40),dp(4))
792838 ''' )
0 commit comments