From 7d6c12ac5eaf3e2c8c8faab388147f129ca7564f Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Fri, 26 Jul 2019 15:31:33 +0200 Subject: [PATCH] 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. --- lib/matplotlib/legend.py | 15 +++++++++------ lib/matplotlib/tests/test_legend.py | 22 +++++++++++++--------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index 016c5030a22c..ee754bd37379 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -22,6 +22,7 @@ """ import logging +import time import numpy as np @@ -1112,13 +1113,9 @@ def _find_best_position(self, width, height, renderer, consider=None): # should always hold because function is only called internally assert self.isaxes + start_time = time.perf_counter() + verts, bboxes, lines, offsets = self._auto_legend_data() - if self._loc_used_default and verts.shape[0] > 200000: - # this size results in a 3+ second render time on a good machine - cbook._warn_external( - 'Creating legend with loc="best" can be slow with large ' - 'amounts of data.' - ) bbox = Bbox.from_bounds(0, 0, width, height) if consider is None: @@ -1145,6 +1142,12 @@ def _find_best_position(self, width, height, renderer, consider=None): candidates.append((badness, idx, (l, b))) _, _, (l, b) = min(candidates) + + if self._loc_used_default and time.perf_counter() - start_time > 1: + cbook._warn_external( + 'Creating legend with loc="best" can be slow with large ' + 'amounts of data.') + return l, b def contains(self, event): diff --git a/lib/matplotlib/tests/test_legend.py b/lib/matplotlib/tests/test_legend.py index 332f72aad46d..aa3f51b69a83 100644 --- a/lib/matplotlib/tests/test_legend.py +++ b/lib/matplotlib/tests/test_legend.py @@ -550,24 +550,28 @@ def test_alpha_handles(): def test_warn_big_data_best_loc(): fig, ax = plt.subplots() - ax.plot(np.arange(200001), label='Is this big data?') + fig.canvas.draw() # So that we can call draw_artist later. + for idx in range(1000): + ax.plot(np.arange(5000), label=idx) + with rc_context({'legend.loc': 'best'}): + legend = ax.legend() with pytest.warns(UserWarning) as records: - with rc_context({'legend.loc': 'best'}): - ax.legend() - fig.canvas.draw() + fig.draw_artist(legend) # Don't bother drawing the lines -- it's slow. # The _find_best_position method of Legend is called twice, duplicating # the warning message. assert len(records) == 2 for record in records: assert str(record.message) == ( - 'Creating legend with loc="best" can be slow with large' - ' amounts of data.') + 'Creating legend with loc="best" can be slow with large ' + 'amounts of data.') def test_no_warn_big_data_when_loc_specified(): fig, ax = plt.subplots() - ax.plot(np.arange(200001), label='Is this big data?') + fig.canvas.draw() + for idx in range(1000): + ax.plot(np.arange(5000), label=idx) + legend = ax.legend('best') with pytest.warns(None) as records: - ax.legend('best') - fig.canvas.draw() + fig.draw_artist(legend) assert len(records) == 0