Skip to content

Commit 68d5b15

Browse files
committed
fix issue #11. improve cursor draw speed by using restore_region method. fix some visual aspect with zoom box action (image border + small rectangle position). When pan/zoom, left axis width doesn't change in fast draw. In main.py, don't used 'disable_on_activity' when platform is android (avoid wrong touch behavior). Rename some graph_widget name to make it worked with pip install.
1 parent 1140335 commit 68d5b15

File tree

21 files changed

+887
-468
lines changed

21 files changed

+887
-468
lines changed

example_3D/graph_widget_3d.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from matplotlib.transforms import Bbox
1818
from kivy.metrics import dp
1919

20-
class MatplotFigure(Widget):
20+
class MatplotFigure3D(Widget):
2121
"""Widget to show a matplotlib figure in kivy.
2222
The figure is rendered internally in an AGG backend then
2323
the rgba data is obtained and blitted into a kivy texture.
@@ -56,7 +56,7 @@ def on_figure(self, obj, value):
5656
self._img_texture = Texture.create(size=(w, h))
5757

5858
def __init__(self, **kwargs):
59-
super(MatplotFigure, self).__init__(**kwargs)
59+
super(MatplotFigure3D, self).__init__(**kwargs)
6060

6161
#figure info
6262
self.figure = None
@@ -755,10 +755,10 @@ def blit(self, bbox=None):
755755

756756
from kivy.factory import Factory
757757

758-
Factory.register('MatplotFigure', MatplotFigure)
758+
Factory.register('MatplotFigure', MatplotFigure3D)
759759

760760
Builder.load_string('''
761-
<MatplotFigure>
761+
<MatplotFigure3D>
762762
canvas:
763763
Color:
764764
rgba: (1, 1, 1, 1)

example_3D/main.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
1+
from kivy.utils import platform
2+
13
#avoid conflict between mouse provider and touch (very important with touch device)
2-
from kivy.config import Config
3-
Config.set('input', 'mouse', 'mouse,disable_on_activity')
4+
#no need for android platform
5+
if platform != 'android':
6+
from kivy.config import Config
7+
Config.set('input', 'mouse', 'mouse,disable_on_activity')
48

59
from kivy.lang import Builder
610
from kivy.app import App
711
from graph_generator import GraphGenerator
812

913
KV = '''
10-
#:import MatplotFigure graph_widget_3d
14+
#:import MatplotFigure3D graph_widget_3d
1115
1216
Screen
1317
figure_wgt:figure_wgt
1418
BoxLayout:
1519
orientation:'vertical'
1620
17-
MatplotFigure:
21+
MatplotFigure3D:
1822
id:figure_wgt
1923
2024
'''

example_basic/graph_widget.py

+92-46
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44

55
import math
6+
import copy
67

78
import matplotlib
89
matplotlib.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
''')

example_basic/main.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
from kivy.utils import platform
2+
13
#avoid conflict between mouse provider and touch (very important with touch device)
2-
from kivy.config import Config
3-
Config.set('input', 'mouse', 'mouse,disable_on_activity')
4+
#no need for android platform
5+
if platform != 'android':
6+
from kivy.config import Config
7+
Config.set('input', 'mouse', 'mouse,disable_on_activity')
48

59
from kivy.lang import Builder
610
from kivy.app import App

example_basic_with_pip_install/main.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
from kivy.utils import platform
2+
13
#avoid conflict between mouse provider and touch (very important with touch device)
2-
from kivy.config import Config
3-
Config.set('input', 'mouse', 'mouse,disable_on_activity')
4+
#no need for android platform
5+
if platform != 'android':
6+
from kivy.config import Config
7+
Config.set('input', 'mouse', 'mouse,disable_on_activity')
48

59
from kivy.lang import Builder
610
from kivy.app import App

0 commit comments

Comments
 (0)