From 5a41aecf2f2c45e57a54abac7ecb0d56147e2f82 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Mon, 25 Feb 2019 07:20:32 +0100 Subject: [PATCH] Change Locator MAXTICKS checking to emitting a log at WARNING level. I chose not to create a new helper method and instead change the behavior of raise_if_exceeds, so that third-party locators also benefit from the change. --- doc/api/next_api_changes/2019-02-25-AL.rst | 6 ++++++ lib/matplotlib/tests/test_dates.py | 14 ++++++++++---- lib/matplotlib/ticker.py | 18 ++++++++++++++---- 3 files changed, 30 insertions(+), 8 deletions(-) create mode 100644 doc/api/next_api_changes/2019-02-25-AL.rst diff --git a/doc/api/next_api_changes/2019-02-25-AL.rst b/doc/api/next_api_changes/2019-02-25-AL.rst new file mode 100644 index 000000000000..59b7de3e319a --- /dev/null +++ b/doc/api/next_api_changes/2019-02-25-AL.rst @@ -0,0 +1,6 @@ +API changes +``````````` + +When more than `.Locator.MAXTICKS` ticks are generated, the behavior of +`.Locator.raise_if_exceeds` changed from raising a RuntimeError to emitting a +log at WARNING level. diff --git a/lib/matplotlib/tests/test_dates.py b/lib/matplotlib/tests/test_dates.py index 8e9cb55f3bbd..69c050bec937 100644 --- a/lib/matplotlib/tests/test_dates.py +++ b/lib/matplotlib/tests/test_dates.py @@ -135,12 +135,13 @@ def test_date_axvline(): fig.autofmt_xdate() -def test_too_many_date_ticks(): +def test_too_many_date_ticks(caplog): # Attempt to test SF 2715172, see # https://sourceforge.net/tracker/?func=detail&aid=2715172&group_id=80706&atid=560720 # setting equal datetimes triggers and expander call in # transforms.nonsingular which results in too many ticks in the - # DayLocator. This should trigger a Locator.MAXTICKS RuntimeError + # DayLocator. This should emit a log at WARNING level. + caplog.set_level("WARNING") t0 = datetime.datetime(2000, 1, 20) tf = datetime.datetime(2000, 1, 20) fig = plt.figure() @@ -152,8 +153,13 @@ def test_too_many_date_ticks(): 'Attempting to set identical left == right' in str(rec[0].message) ax.plot([], []) ax.xaxis.set_major_locator(mdates.DayLocator()) - with pytest.raises(RuntimeError): - fig.savefig('junk.png') + fig.canvas.draw() + # The warning is emitted multiple times because the major locator is also + # called both when placing the minor ticks (for overstriking detection) and + # during tick label positioning. + assert caplog.records and all( + record.name == "matplotlib.ticker" and record.levelname == "WARNING" + for record in caplog.records) @image_comparison(['RRuleLocator_bounds.png']) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 0c936d2079f1..82fa6aa43baa 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -1690,11 +1690,21 @@ def __call__(self): raise NotImplementedError('Derived must override') def raise_if_exceeds(self, locs): - """Raise a RuntimeError if ``len(locs) > self.MAXTICKS``.""" + """ + Log at WARNING level if *locs* is longer than `Locator.MAXTICKS`. + + This is intended to be called immediately before returning *locs* from + ``__call__`` to inform users in case their Locator returns a huge + number of ticks, causing Matplotlib to run out of memory. + + The "strange" name of this method dates back to when it would raise an + exception instead of emitting a log. + """ if len(locs) >= self.MAXTICKS: - raise RuntimeError("Locator attempting to generate {} ticks from " - "{} to {}: exceeds Locator.MAXTICKS".format( - len(locs), locs[0], locs[-1])) + _log.warning( + "Locator attempting to generate %s ticks ([%s, ..., %s]), " + "which exceeds Locator.MAXTICKS (%s).", + len(locs), locs[0], locs[-1], self.MAXTICKS) return locs def nonsingular(self, v0, v1):