Skip to content

Commit 0f46f27

Browse files
authored
Merge pull request matplotlib#31244 from scottshambaugh/sticky_edges_speedup
PERF: Sticky edges speedup
2 parents 7c94eb0 + ac439c8 commit 0f46f27

2 files changed

Lines changed: 54 additions & 26 deletions

File tree

lib/matplotlib/artist.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ def __init__(self):
223223
self._snap = None
224224
self._sketch = mpl.rcParams['path.sketch']
225225
self._path_effects = mpl.rcParams['path.effects']
226-
self._sticky_edges = _XYPair([], [])
226+
self._sticky_edges = None
227227
self._in_layout = True
228228
self._in_autoscale = False
229229

@@ -1229,6 +1229,8 @@ def sticky_edges(self):
12291229
>>> artist.sticky_edges.y[:] = (ymin, ymax)
12301230
12311231
"""
1232+
if self._sticky_edges is None:
1233+
self._sticky_edges = _XYPair([], [])
12321234
return self._sticky_edges
12331235

12341236
def update_from(self, other):
@@ -1243,8 +1245,11 @@ def update_from(self, other):
12431245
self._label = other._label
12441246
self._sketch = other._sketch
12451247
self._path_effects = other._path_effects
1246-
self.sticky_edges.x[:] = other.sticky_edges.x.copy()
1247-
self.sticky_edges.y[:] = other.sticky_edges.y.copy()
1248+
if other._sticky_edges is not None:
1249+
self.sticky_edges.x[:] = other._sticky_edges.x.copy()
1250+
self.sticky_edges.y[:] = other._sticky_edges.y.copy()
1251+
else:
1252+
self._sticky_edges = None
12481253
self.pchanged()
12491254
self.stale = True
12501255

lib/matplotlib/axes/_base.py

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3054,20 +3054,29 @@ def autoscale_view(self, tight=None, scalex=True, scaley=True):
30543054
if tight is not None:
30553055
self._tight = bool(tight)
30563056

3057+
x_shared_axes = self._shared_axes["x"].get_siblings(self)
3058+
y_shared_axes = self._shared_axes["y"].get_siblings(self)
3059+
30573060
x_stickies = y_stickies = np.array([])
30583061
if self.use_sticky_edges:
30593062
if self._xmargin and scalex and self.get_autoscalex_on():
3060-
edges = []
3061-
for ax in self._shared_axes["x"].get_siblings(self):
3062-
for artist in ax.get_children():
3063-
edges.extend(artist.sticky_edges.x)
3064-
x_stickies = np.sort(edges)
3063+
x_sticky = []
3064+
for ax in x_shared_axes:
3065+
for artist in ax._get_data_children():
3066+
sticky_edges = artist._sticky_edges
3067+
if sticky_edges is not None and sticky_edges.x:
3068+
x_sticky.extend(sticky_edges.x)
3069+
if x_sticky:
3070+
x_stickies = np.sort(x_sticky)
30653071
if self._ymargin and scaley and self.get_autoscaley_on():
3066-
edges = []
3067-
for ax in self._shared_axes["y"].get_siblings(self):
3068-
for artist in ax.get_children():
3069-
edges.extend(artist.sticky_edges.y)
3070-
y_stickies = np.sort(edges)
3072+
y_sticky = []
3073+
for ax in y_shared_axes:
3074+
for artist in ax._get_data_children():
3075+
sticky_edges = artist._sticky_edges
3076+
if sticky_edges is not None and sticky_edges.y:
3077+
y_sticky.extend(sticky_edges.y)
3078+
if y_sticky:
3079+
y_stickies = np.sort(y_sticky)
30713080
if self.get_xscale() == 'log':
30723081
x_stickies = x_stickies[x_stickies > 0]
30733082
if self.get_yscale() == 'log':
@@ -3078,11 +3087,9 @@ def handle_single_axis(
30783087

30793088
if not (scale and axis._get_autoscale_on()):
30803089
return # nothing to do...
3081-
3082-
shared = shared_axes.get_siblings(self)
30833090
# Base autoscaling on finite data limits when there is at least one
30843091
# finite data limit among all the shared_axes and intervals.
3085-
values = [val for ax in shared
3092+
values = [val for ax in shared_axes
30863093
for val in getattr(ax.dataLim, f"interval{name}")
30873094
if np.isfinite(val)]
30883095
if values:
@@ -3098,20 +3105,22 @@ def handle_single_axis(
30983105
x0, x1 = locator.nonsingular(x0, x1)
30993106
# Find the minimum minpos for use in the margin calculation.
31003107
minimum_minpos = min(
3101-
getattr(ax.dataLim, f"minpos{name}") for ax in shared)
3108+
getattr(ax.dataLim, f"minpos{name}") for ax in shared_axes)
31023109

31033110
# Prevent margin addition from crossing a sticky value. A small
31043111
# tolerance must be added due to floating point issues with
31053112
# streamplot; it is defined relative to x1-x0 but has
31063113
# no absolute term (e.g. "+1e-8") to avoid issues when working with
31073114
# datasets where all values are tiny (less than 1e-8).
3108-
tol = 1e-5 * abs(x1 - x0)
3109-
# Index of largest element < x0 + tol, if any.
3110-
i0 = stickies.searchsorted(x0 + tol) - 1
3111-
x0bound = stickies[i0] if i0 != -1 else None
3112-
# Index of smallest element > x1 - tol, if any.
3113-
i1 = stickies.searchsorted(x1 - tol)
3114-
x1bound = stickies[i1] if i1 != len(stickies) else None
3115+
x0bound = x1bound = None
3116+
if len(stickies):
3117+
tol = 1e-5 * abs(x1 - x0)
3118+
# Index of largest element < x0 + tol, if any.
3119+
i0 = stickies.searchsorted(x0 + tol) - 1
3120+
x0bound = stickies[i0] if i0 != -1 else None
3121+
# Index of smallest element > x1 - tol, if any.
3122+
i1 = stickies.searchsorted(x1 - tol)
3123+
x1bound = stickies[i1] if i1 != len(stickies) else None
31153124

31163125
# Add the margin in figure space and then transform back, to handle
31173126
# non-linear scales.
@@ -3136,10 +3145,10 @@ def handle_single_axis(
31363145
# End of definition of internal function 'handle_single_axis'.
31373146

31383147
handle_single_axis(
3139-
scalex, self._shared_axes["x"], 'x', self.xaxis, self._xmargin,
3148+
scalex, x_shared_axes, 'x', self.xaxis, self._xmargin,
31403149
x_stickies, self.set_xbound)
31413150
handle_single_axis(
3142-
scaley, self._shared_axes["y"], 'y', self.yaxis, self._ymargin,
3151+
scaley, y_shared_axes, 'y', self.yaxis, self._ymargin,
31433152
y_stickies, self.set_ybound)
31443153

31453154
def _update_title_position(self, renderer):
@@ -4542,6 +4551,20 @@ def drag_pan(self, button, key, x, y):
45424551
self.set_xlim(points[:, 0])
45434552
self.set_ylim(points[:, 1])
45444553

4554+
def _get_data_children(self):
4555+
"""
4556+
Return artists that represent data (plot lines, collections, images,
4557+
patches, etc.) as opposed to auxiliary artists needed to draw the
4558+
Axes itself (spines, titles, axis objects, etc.).
4559+
4560+
Data children are the artists that can contribute to autoscaling
4561+
and sticky edges.
4562+
4563+
Note: This is a preliminary definition and has not been thought
4564+
through completely. We may want to revise this later.
4565+
"""
4566+
return [*self._children, *self._axis_map.values()]
4567+
45454568
def get_children(self):
45464569
# docstring inherited.
45474570
return [

0 commit comments

Comments
 (0)