33
44import numpy as np
55
6+ from matplotlib .backend_bases import PickEvent
7+ import matplotlib .artist as martist
8+
69from .containers import DataContainer , ArrayContainer , DataUnion
710from .description import Desc , desc_like
8- from .conversion_edge import Edge , Graph , TransformEdge
11+ from .conversion_edge import Edge , FuncEdge , Graph , TransformEdge
912
1013
1114class Artist :
@@ -18,6 +21,9 @@ def __init__(
1821 kwargs_cont = ArrayContainer (** kwargs )
1922 self ._container = DataUnion (container , kwargs_cont )
2023
24+ self ._children : list [tuple [float , Artist ]] = []
25+ self ._picker = None
26+
2127 edges = edges or []
2228 self ._visible = True
2329 self ._graph = Graph (edges )
@@ -41,6 +47,77 @@ def get_visible(self):
4147 def set_visible (self , visible ):
4248 self ._visible = visible
4349
50+ def pickable (self ) -> bool :
51+ return self ._picker is not None
52+
53+ def get_picker (self ):
54+ return self ._picker
55+
56+ def set_picker (self , picker ):
57+ self ._picker = picker
58+
59+ def contains (self , mouseevent , graph = None ):
60+ """
61+ Test whether the artist contains the mouse event.
62+
63+ Parameters
64+ ----------
65+ mouseevent : `~matplotlib.backend_bases.MouseEvent`
66+
67+ Returns
68+ -------
69+ contains : bool
70+ Whether any values are within the radius.
71+ details : dict
72+ An artist-specific dictionary of details of the event context,
73+ such as which points are contained in the pick radius. See the
74+ individual Artist subclasses for details.
75+ """
76+ return False , {}
77+
78+ def get_children (self ):
79+ return [a [1 ] for a in self ._children ]
80+
81+ def pick (self , mouseevent , graph : Graph | None = None ):
82+ """
83+ Process a pick event.
84+
85+ Each child artist will fire a pick event if *mouseevent* is over
86+ the artist and the artist has picker set.
87+
88+ See Also
89+ --------
90+ set_picker, get_picker, pickable
91+ """
92+ if graph is None :
93+ graph = self ._graph
94+ else :
95+ graph = graph + self ._graph
96+ # Pick self
97+ if self .pickable ():
98+ picker = self .get_picker ()
99+ if callable (picker ):
100+ inside , prop = picker (self , mouseevent )
101+ else :
102+ inside , prop = self .contains (mouseevent , graph )
103+ if inside :
104+ PickEvent (
105+ "pick_event" , mouseevent .canvas , mouseevent , self , ** prop
106+ )._process ()
107+
108+ # Pick children
109+ for a in self .get_children ():
110+ # make sure the event happened in the same Axes
111+ ax = getattr (a , "axes" , None )
112+ if mouseevent .inaxes is None or ax is None or mouseevent .inaxes == ax :
113+ # we need to check if mouseevent.inaxes is None
114+ # because some objects associated with an Axes (e.g., a
115+ # tick label) can be outside the bounding box of the
116+ # Axes and inaxes will be None
117+ # also check that ax is None so that it traverse objects
118+ # which do not have an axes property but children might
119+ a .pick (mouseevent , graph )
120+
44121
45122class CompatibilityArtist :
46123 """A compatibility shim to ducktype as a classic Matplotlib Artist.
@@ -59,7 +136,7 @@ class CompatibilityArtist:
59136 useful for avoiding accidental dependency.
60137 """
61138
62- def __init__ (self , artist : Artist ):
139+ def __init__ (self , artist : martist . Artist ):
63140 self ._artist = artist
64141
65142 self ._axes = None
@@ -134,7 +211,7 @@ def draw(self, renderer, graph=None):
134211 self ._artist .draw (renderer , graph + self ._graph )
135212
136213
137- class CompatibilityAxes :
214+ class CompatibilityAxes ( Artist ) :
138215 """A compatibility shim to add to traditional matplotlib axes.
139216
140217 At this time features are implemented on an "as needed" basis, and many
@@ -152,12 +229,11 @@ class CompatibilityAxes:
152229 """
153230
154231 def __init__ (self , axes ):
232+ super ().__init__ (ArrayContainer ())
155233 self ._axes = axes
156234 self .figure = None
157235 self ._clippath = None
158- self ._visible = True
159236 self .zorder = 2
160- self ._children : list [tuple [float , Artist ]] = []
161237
162238 @property
163239 def axes (self ):
@@ -187,6 +263,18 @@ def axes(self, ax):
187263 desc_like (xy , coordinates = "display" ),
188264 transform = self ._axes .transAxes ,
189265 ),
266+ FuncEdge .from_func (
267+ "xunits" ,
268+ lambda : self ._axes .xaxis .units ,
269+ {},
270+ {"xunits" : Desc ((), "units" )},
271+ ),
272+ FuncEdge .from_func (
273+ "yunits" ,
274+ lambda : self ._axes .yaxis .units ,
275+ {},
276+ {"yunits" : Desc ((), "units" )},
277+ ),
190278 ],
191279 aliases = (("parent" , "axes" ),),
192280 )
@@ -210,7 +298,7 @@ def get_animated(self):
210298 return False
211299
212300 def draw (self , renderer , graph = None ):
213- if not self .visible :
301+ if not self .get_visible () :
214302 return
215303 if graph is None :
216304 graph = Graph ([])
@@ -228,9 +316,3 @@ def set_xlim(self, min_=None, max_=None):
228316
229317 def set_ylim (self , min_ = None , max_ = None ):
230318 self .axes .set_ylim (min_ , max_ )
231-
232- def get_visible (self ):
233- return self ._visible
234-
235- def set_visible (self , visible ):
236- self ._visible = visible
0 commit comments