Skip to content

Commit ad01fff

Browse files
committed
Make slowness warning for legend(loc="best") more accurate.
... by actually timing the call duration. Locally I can best-locate legends even with plots with hundreds of thousands of points basically instantly, so the old warning was spurious. The new test is obviously a bit brittle because it depends on how fast the machine running it is. It's also slower than the test before (intentionally, because now you *actually* need a slow-to-place legend to trigger the warning). The warning is only emitted after the legend has been placed, but that seems fine -- if the best-placement is so slow that you ctrl-c the process, you'll have a traceback anyways. Also, spawning a separate thread to always emit the warning after exactly 5s will likely just make things worse performance-wise on average. The 5s delay is the same as used in font_manager.py.
1 parent 43e7d3b commit ad01fff

File tree

2 files changed

+22
-15
lines changed

2 files changed

+22
-15
lines changed

lib/matplotlib/legend.py

+9-6
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"""
2323

2424
import logging
25+
import time
2526

2627
import numpy as np
2728

@@ -1112,13 +1113,9 @@ def _find_best_position(self, width, height, renderer, consider=None):
11121113
# should always hold because function is only called internally
11131114
assert self.isaxes
11141115

1116+
start_time = time.perf_counter()
1117+
11151118
verts, bboxes, lines, offsets = self._auto_legend_data()
1116-
if self._loc_used_default and verts.shape[0] > 200000:
1117-
# this size results in a 3+ second render time on a good machine
1118-
cbook._warn_external(
1119-
'Creating legend with loc="best" can be slow with large '
1120-
'amounts of data.'
1121-
)
11221119

11231120
bbox = Bbox.from_bounds(0, 0, width, height)
11241121
if consider is None:
@@ -1145,6 +1142,12 @@ def _find_best_position(self, width, height, renderer, consider=None):
11451142
candidates.append((badness, idx, (l, b)))
11461143

11471144
_, _, (l, b) = min(candidates)
1145+
1146+
if self._loc_used_default and time.perf_counter() - start_time > 5:
1147+
cbook._warn_external(
1148+
'Creating legend with loc="best" can be slow with large '
1149+
'amounts of data.')
1150+
11481151
return l, b
11491152

11501153
def contains(self, event):

lib/matplotlib/tests/test_legend.py

+13-9
Original file line numberDiff line numberDiff line change
@@ -550,24 +550,28 @@ def test_alpha_handles():
550550

551551
def test_warn_big_data_best_loc():
552552
fig, ax = plt.subplots()
553-
ax.plot(np.arange(200001), label='Is this big data?')
553+
fig.canvas.draw() # So that we can call draw_artist later.
554+
for idx in range(2000):
555+
ax.plot(np.arange(20000), label=idx)
556+
with rc_context({'legend.loc': 'best'}):
557+
legend = ax.legend()
554558
with pytest.warns(UserWarning) as records:
555-
with rc_context({'legend.loc': 'best'}):
556-
ax.legend()
557-
fig.canvas.draw()
559+
fig.draw_artist(legend) # Don't bother drawing the lines -- it's slow.
558560
# The _find_best_position method of Legend is called twice, duplicating
559561
# the warning message.
560562
assert len(records) == 2
561563
for record in records:
562564
assert str(record.message) == (
563-
'Creating legend with loc="best" can be slow with large'
564-
' amounts of data.')
565+
'Creating legend with loc="best" can be slow with large '
566+
'amounts of data.')
565567

566568

567569
def test_no_warn_big_data_when_loc_specified():
568570
fig, ax = plt.subplots()
569-
ax.plot(np.arange(200001), label='Is this big data?')
571+
fig.canvas.draw()
572+
for idx in range(2000):
573+
ax.plot(np.arange(20000), label=idx)
574+
legend = ax.legend('best')
570575
with pytest.warns(None) as records:
571-
ax.legend('best')
572-
fig.canvas.draw()
576+
fig.draw_artist(legend)
573577
assert len(records) == 0

0 commit comments

Comments
 (0)