1
+ from typing import List , Dict , Any
2
+
3
+ import numpy as np
4
+
5
+ from cachetools import LFUCache
6
+
1
7
from matplotlib .lines import Line2D as _Line2D
2
8
from matplotlib .image import AxesImage as _AxesImage
3
9
@@ -24,18 +30,54 @@ def __setattr__(self, key, value):
24
30
else :
25
31
super ().__setattr__ (key , value )
26
32
27
- def _query_and_transform (self , renderer ):
33
+ def _query_and_transform (self , renderer , * , xunits : List [str ], yunits : List [str ]) -> Dict [str , Any ]:
34
+ """
35
+ Helper to centralize the data querying and python-side transforms
36
+
37
+ Parameters
38
+ ----------
39
+ renderer : RendererBase
40
+ xunits, yunits : List[str]
41
+ The list of keys that need to be run through the x and y unit machinery.
42
+ """
43
+ # extract what we need to about the axes to query the data
28
44
ax = self ._wrapped_instance .axes
45
+ # TODO do we want to trust the implicit renderer on the Axes?
29
46
ax_bbox = ax .get_window_extent (renderer )
30
- return {
31
- # doing this here is nice because we can write it once, but we really want to
32
- # push this computation down a layer
33
- k : self .nus .get (k , lambda x : x )(v )
34
- for k , v in self .data .query ([* ax .get_xlim (), * ax .get_ylim ()], ax_bbox .size ).items ()
35
- }
47
+
48
+ # actually query the underlying data. This returns both the (raw) data
49
+ # and key to use for caching.
50
+ data , cache_key = self .data .query (
51
+ # TODO do this need to be (de) unitized
52
+ (* ax .get_xlim (), * ax .get_ylim ()),
53
+ tuple (np .round (ax_bbox .size ).astype (int )),
54
+ # TODO sort out how to spell the x/y scale
55
+ # TODO is scale enoguh? What do we have to do about non-trivial projection?
56
+ xscale = None ,
57
+ yscale = None ,
58
+ )
59
+ # see if we can short-circuit
60
+ try :
61
+ return self ._cache [cache_key ]
62
+ except KeyError :
63
+ ...
64
+ # TODO decide if units go pre-nu or post-nu?
65
+ for x_like in xunits :
66
+ data [x_like ] = ax .xaxis .convert_units (data [x_like ])
67
+ for y_like in yunits :
68
+ data [y_like ] = ax .xaxis .convert_units (data [y_like ])
69
+
70
+ # doing the nu work here is nice because we can write it once, but we
71
+ # really want to push this computation down a layer
72
+ # TODO sort out how this interaporates with the transform stack
73
+ data = {k : self .nus .get (k , lambda x : x )(v ) for k , v in data .items ()}
74
+ self ._cache [cache_key ] = data
75
+ return data
36
76
37
77
def __init__ (self , data , nus ):
38
78
self .data = data
79
+ self ._cache = LFUCache (64 )
80
+ # TODO make sure mutating this will invalidate the cache!
39
81
self .nus = nus or {}
40
82
41
83
@@ -48,7 +90,7 @@ def __init__(self, data, nus=None, /, **kwargs):
48
90
self ._wrapped_instance = self ._wrapped_class ([], [], ** kwargs )
49
91
50
92
def draw (self , renderer ):
51
- data = self ._query_and_transform (renderer )
93
+ data = self ._query_and_transform (renderer , xunits = [ "x" ], yunits = [ "y" ] )
52
94
self ._wrapped_instance .set_data (data ["x" ], data ["y" ])
53
95
return self ._wrapped_instance .draw (renderer )
54
96
@@ -62,7 +104,7 @@ def __init__(self, data, nus=None, /, **kwargs):
62
104
self ._wrapped_instance = self ._wrapped_class (None , ** kwargs )
63
105
64
106
def draw (self , renderer ):
65
- data = self ._query_and_transform (renderer )
107
+ data = self ._query_and_transform (renderer , xunits = [ "xextent" ], yunits = [ "yextent" ] )
66
108
self ._wrapped_instance .set_array (data ["image" ])
67
- self ._wrapped_instance .set_extent (data ["extent" ])
109
+ self ._wrapped_instance .set_extent ([ * data ["xextent" ], * data [ "yextent" ] ])
68
110
return self ._wrapped_instance .draw (renderer )
0 commit comments