16
16
from kivy .vector import Vector
17
17
from matplotlib .backends .backend_agg import FigureCanvasAgg
18
18
from matplotlib import cbook
19
+ from matplotlib .colors import to_hex
19
20
from weakref import WeakKeyDictionary
20
21
from kivy .metrics import dp
21
22
import numpy as np
23
+ from kivy .utils import get_color_from_hex
24
+
22
25
23
26
class MatplotFigure (Widget ):
24
27
"""Widget to show a matplotlib figure in kivy.
@@ -57,6 +60,7 @@ class MatplotFigure(Widget):
57
60
fast_draw = BooleanProperty (True ) #True will don't draw axis
58
61
xsorted = BooleanProperty (False ) #to manage x sorted data
59
62
minzoom = NumericProperty (dp (40 ))
63
+ hover_instance = ObjectProperty (None , allownone = True )
60
64
61
65
def on_figure (self , obj , value ):
62
66
self .figcanvas = _FigureCanvas (self .figure , self )
@@ -130,6 +134,10 @@ def __init__(self, **kwargs):
130
134
self .anchor_x = None
131
135
self .anchor_y = None
132
136
137
+ #manage hover data
138
+ self .x_hover_data = None
139
+ self .y_hover_data = None
140
+
133
141
#manage back and next event
134
142
self ._nav_stack = cbook .Stack ()
135
143
self .set_history_buttons ()
@@ -245,20 +253,53 @@ def hover(self, event) -> None:
245
253
line = good_line [idx_best ]
246
254
self .x_cursor , self .y_cursor = line .get_data ()
247
255
x = self .x_cursor [good_index [idx_best ]]
248
- y = self .y_cursor [good_index [idx_best ]]
249
- self .set_cross_hair_visible (True )
256
+ y = self .y_cursor [good_index [idx_best ]]
257
+
258
+ if not self .hover_instance :
259
+ self .set_cross_hair_visible (True )
250
260
251
261
# update the cursor x,y data
252
262
ax = line .axes
253
263
self .horizontal_line .set_ydata (y )
254
264
self .vertical_line .set_xdata (x )
255
265
256
266
#x y label
257
- if self .cursor_xaxis_formatter :
258
- x = self .cursor_xaxis_formatter .format_data (x )
259
- if self .cursor_yaxis_formatter :
260
- y = self .cursor_yaxis_formatter .format_data (y )
261
- self .text .set_text (f"x={ x } , y={ y } " )
267
+ if self .hover_instance :
268
+ xy_pos = ax .transData .transform ([(x ,y )])
269
+ self .x_hover_data = x
270
+ self .y_hover_data = y
271
+ self .hover_instance .x_hover_pos = float (xy_pos [0 ][0 ]) + self .x
272
+ self .hover_instance .y_hover_pos = float (xy_pos [0 ][1 ]) + self .y
273
+ self .hover_instance .show_cursor = True
274
+
275
+ if self .cursor_xaxis_formatter :
276
+ x = self .cursor_xaxis_formatter .format_data (x )
277
+ if self .cursor_yaxis_formatter :
278
+ y = self .cursor_yaxis_formatter .format_data (y )
279
+ self .hover_instance .label_x_value = f"{ x } "
280
+ self .hover_instance .label_y_value = f"{ y } "
281
+
282
+ self .hover_instance .ymin_line = float (ax .bbox .bounds [1 ]) + self .y
283
+ self .hover_instance .ymax_line = float (ax .bbox .bounds [1 ] + ax .bbox .bounds [3 ]) + self .y
284
+
285
+ self .hover_instance .custom_label = line .get_label ()
286
+ self .hover_instance .custom_color = get_color_from_hex (to_hex (line .get_color ()))
287
+
288
+ if self .hover_instance .x_hover_pos > self .x + self .axes .bbox .bounds [2 ] + self .axes .bbox .bounds [0 ] or \
289
+ self .hover_instance .x_hover_pos < self .x + self .axes .bbox .bounds [0 ] or \
290
+ self .hover_instance .y_hover_pos > self .y + self .axes .bbox .bounds [1 ] + self .axes .bbox .bounds [3 ] or \
291
+ self .hover_instance .y_hover_pos < self .y + self .axes .bbox .bounds [1 ]:
292
+ self .hover_instance .hover_outside_bound = True
293
+ else :
294
+ self .hover_instance .hover_outside_bound = False
295
+
296
+ return
297
+ else :
298
+ if self .cursor_xaxis_formatter :
299
+ x = self .cursor_xaxis_formatter .format_data (x )
300
+ if self .cursor_yaxis_formatter :
301
+ y = self .cursor_yaxis_formatter .format_data (y )
302
+ self .text .set_text (f"x={ x } , y={ y } " )
262
303
263
304
#blit method (always use because same visual effect as draw)
264
305
if self .background is None :
@@ -281,6 +322,12 @@ def hover(self, event) -> None:
281
322
#if touch is too far, hide cross hair cursor
282
323
else :
283
324
self .set_cross_hair_visible (False )
325
+ if self .hover_instance :
326
+ self .hover_instance .x_hover_pos = self .x
327
+ self .hover_instance .y_hover_pos = self .y
328
+ self .hover_instance .show_cursor = False
329
+ self .x_hover_data = None
330
+ self .y_hover_data = None
284
331
285
332
def home (self ) -> None :
286
333
""" reset data axis
@@ -411,6 +458,27 @@ def _draw_bitmap(self):
411
458
self ._img_texture .blit_buffer (
412
459
bytes (self ._bitmap ), colorfmt = "rgba" , bufferfmt = 'ubyte' )
413
460
self ._img_texture .flip_vertical ()
461
+
462
+ if self .hover_instance :
463
+ #update hover pos if needed
464
+ if self .hover_instance .show_cursor and self .x_hover_data and self .y_hover_data :
465
+ xy_pos = self .axes .transData .transform ([(self .x_hover_data ,self .y_hover_data )])
466
+ self .hover_instance .x_hover_pos = float (xy_pos [0 ][0 ]) + self .x
467
+ self .hover_instance .y_hover_pos = float (xy_pos [0 ][1 ]) + self .y
468
+
469
+ # ymin,ymax=self.axes.get_ylim()
470
+ # ylim_pos = self.axes.transData.transform([(ymin,ymax)])
471
+ self .hover_instance .ymin_line = float (self .axes .bbox .bounds [1 ]) + self .y
472
+ self .hover_instance .ymax_line = float (self .axes .bbox .bounds [1 ] + self .axes .bbox .bounds [3 ] )+ self .y
473
+
474
+ if self .hover_instance .x_hover_pos > self .x + self .axes .bbox .bounds [2 ] + self .axes .bbox .bounds [0 ] or \
475
+ self .hover_instance .x_hover_pos < self .x + self .axes .bbox .bounds [0 ] or \
476
+ self .hover_instance .y_hover_pos > self .y + self .axes .bbox .bounds [1 ] + self .axes .bbox .bounds [3 ] or \
477
+ self .hover_instance .y_hover_pos < self .y + self .axes .bbox .bounds [1 ]:
478
+ self .hover_instance .hover_outside_bound = True
479
+ else :
480
+ self .hover_instance .hover_outside_bound = False
481
+
414
482
415
483
def transform_with_touch (self , event ):
416
484
""" manage touch behaviour. based on kivy scatter method"""
@@ -489,6 +557,26 @@ def transform_with_touch(self, event):
489
557
changed = True
490
558
return changed
491
559
560
+ def on_motion (self ,* args ):
561
+ '''Kivy Event to trigger mouse event on motion
562
+ `enter_notify_event`.
563
+ '''
564
+ pos = args [1 ]
565
+ newcoord = self .to_widget (pos [0 ], pos [1 ])
566
+ x = newcoord [0 ]
567
+ y = newcoord [1 ]
568
+ inside = self .collide_point (x ,y )
569
+ if inside :
570
+
571
+ # will receive all motion events.
572
+ if self .figcanvas and self .hover_instance :
573
+ #avoid in motion if touch is detected
574
+ if not len (self ._touches )== 0 :
575
+ return
576
+ FakeEvent .x = x
577
+ FakeEvent .y = y
578
+ self .hover (FakeEvent )
579
+
492
580
def on_touch_down (self , event ):
493
581
""" Manage Mouse/touch press """
494
582
x , y = event .x , event .y
@@ -819,6 +907,8 @@ def _onSize(self, o, size):
819
907
self .figcanvas .draw ()
820
908
if self .legend_instance :
821
909
self .legend_instance .update_size ()
910
+ if self .hover_instance :
911
+ self .hover_instance .figwidth = self .width
822
912
823
913
def update_lim (self ):
824
914
""" update axis lim if zoombox is used"""
@@ -984,6 +1074,10 @@ def blit(self, bbox=None):
984
1074
self .widget .bt_h = h
985
1075
self .widget ._draw_bitmap ()
986
1076
1077
+ class FakeEvent :
1078
+ x :None
1079
+ y :None
1080
+
987
1081
from kivy .factory import Factory
988
1082
989
1083
Factory .register ('MatplotFigure' , MatplotFigure )
0 commit comments