Skip to content

Removing axes created by twiny() leads to an error #18925

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
lars-sowa-snkeos opened this issue Nov 10, 2020 · 4 comments · Fixed by #21076
Closed

Removing axes created by twiny() leads to an error #18925

lars-sowa-snkeos opened this issue Nov 10, 2020 · 4 comments · Fixed by #21076

Comments

@lars-sowa-snkeos
Copy link

Bug report

Bug summary

Removing axes created with twiny() leads to an error. I have found a very similar bug to my issue here: #14911. Interestingly enough this works with twinx(). The code in the original bug report also works in my installation.

Code for reproduction

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
ax_twin = ax.twiny()
ax_twin.remove()
plt.show()

Actual outcome

Exception in Tkinter callback
Traceback (most recent call last):
  File "c:\programdata\miniconda3\lib\tkinter\__init__.py", line 1883, in __call__
    return self.func(*args)
  File "c:\programdata\miniconda3\lib\tkinter\__init__.py", line 804, in callit
    func(*args)
  File "c:\programdata\miniconda3\lib\site-packages\matplotlib\backends\_backend_tk.py", line 253, in idle_draw
    self.draw()
  File "c:\programdata\miniconda3\lib\site-packages\matplotlib\backends\backend_tkagg.py", line 9, in draw
    super(FigureCanvasTkAgg, self).draw()
  File "c:\programdata\miniconda3\lib\site-packages\matplotlib\backends\backend_agg.py", line 407, in draw
    self.figure.draw(self.renderer)
  File "c:\programdata\miniconda3\lib\site-packages\matplotlib\artist.py", line 41, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "c:\programdata\miniconda3\lib\site-packages\matplotlib\figure.py", line 1863, in draw
    mimage._draw_list_compositing_images(
  File "c:\programdata\miniconda3\lib\site-packages\matplotlib\image.py", line 131, in _draw_list_compositing_images
    a.draw(renderer)
  File "c:\programdata\miniconda3\lib\site-packages\matplotlib\artist.py", line 41, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "c:\programdata\miniconda3\lib\site-packages\matplotlib\cbook\deprecation.py", line 411, in wrapper
    return func(*inner_args, **inner_kwargs)
  File "c:\programdata\miniconda3\lib\site-packages\matplotlib\axes\_base.py", line 2707, in draw
    self._update_title_position(renderer)
  File "c:\programdata\miniconda3\lib\site-packages\matplotlib\axes\_base.py", line 2638, in _update_title_position
    bb = ax.xaxis.get_tightbbox(renderer)
  File "c:\programdata\miniconda3\lib\site-packages\matplotlib\axis.py", line 1109, in get_tightbbox
    ticks_to_draw = self._update_ticks()
  File "c:\programdata\miniconda3\lib\site-packages\matplotlib\axis.py", line 1021, in _update_ticks
    major_locs = self.get_majorticklocs()
  File "c:\programdata\miniconda3\lib\site-packages\matplotlib\axis.py", line 1283, in get_majorticklocs
    return self.major.locator()
  File "c:\programdata\miniconda3\lib\site-packages\matplotlib\ticker.py", line 2276, in __call__
    return self.tick_values(vmin, vmax)
  File "c:\programdata\miniconda3\lib\site-packages\matplotlib\ticker.py", line 2284, in tick_values
    locs = self._raw_ticks(vmin, vmax)
  File "c:\programdata\miniconda3\lib\site-packages\matplotlib\ticker.py", line 2223, in _raw_ticks
    nbins = np.clip(self.axis.get_tick_space(),
  File "c:\programdata\miniconda3\lib\site-packages\matplotlib\axis.py", line 2243, in get_tick_space
    length = ((ends[1][0] - ends[0][0]) / self.axes.figure.dpi) * 72
AttributeError: 'NoneType' object has no attribute 'dpi'

Expected outcome

A plot with the twiny axes removed

Matplotlib version

  • Operating system: Windows 10
  • Matplotlib version: 3.3.2
  • Matplotlib backend (print(matplotlib.get_backend())): TkAgg
  • Python version: 3.8.3

Python is from miniconda, all packages installed via pip.

@tacaswell tacaswell added this to the v3.4.0 milestone Nov 10, 2020
@tacaswell
Copy link
Member

The fix here is to sort out why #14997 only worked on the x-axis and not the y-axis...

@anntzer
Copy link
Contributor

anntzer commented Nov 10, 2020

From a quick look, I think that's unrelated to #14997 and actually due to the fact that the _shared_x_axes/_shared_y_axes/_twinned_axes groupers don't get updated when an axes is removed, and then the removed axes show up when _update_title_position calls _twinned_axes.get_siblings. (And the this only affects twiny because of implementation details of XAxis.get_tick_space.)

Likely Figure.delaxes also needs to update the groupers, but note that on top of that add_axes (which explicitly can re-add a previously removed axes) should also update the groupers back if such an axes is readded.

@anntzer
Copy link
Contributor

anntzer commented Nov 11, 2020

A related problem I noticed independently: setting a tick locator on a removed shared axes will affect the unremoved axes in a somewhat weird way. A realistic example is the following

from pylab import *
from matplotlib.ticker import MultipleLocator

datasets = [  # Made up, imagine you have many.
    [1, 2, 3, 4],
    [1, 2, 4, 8],
    [1, 3, 5, 7],
]

# I want to plot one dataset per subplot, and arrange axes in a square-ish way.
axs = figure(constrained_layout=True).subplots(2, 2, sharex=True, sharey=True).ravel()
# Remove axes that won't be used
for ax in axs[len(datasets):]:
    ax.remove()
# Do some common setup for axes.
for ax in axs:
    ax.xaxis.set(major_locator=MultipleLocator(1))
    ax.yaxis.set(major_locator=MultipleLocator(1))
# Plot the data.
for ax, dataset in zip(axs, datasets):
    ax.plot(dataset)
show()

which yields
test (note the missing ticks).
I believe what basically happens is that the MultipleLocator set on the last (now removed) axes wins and is used for everyone, but the axes limits of that axes are no longer updated and so the MultipleLocator thinks it is being used with axes limits of (0, 1) (the default axes limits) and decides to yield ticks at [-1, 0, 1, 2] (with the usual "1-over" excess tick). Again, removing the removed axes from _shared_x_axes would prevent its locator from "winning".

(I have similar patterns quite often, but usually remove the datasets last (which avoids the problem as axes removal knows tickers need to be readjusted) or remember that I only need to set the locators once.)

@QuLogic QuLogic modified the milestones: v3.4.0, v3.5.0 Jan 27, 2021
@thriveth
Copy link

I still have this problem, if there is not a fix, is there possibly a known work-around?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants