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.
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'
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.
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
frompylabimport*frommatplotlib.tickerimportMultipleLocatordatasets= [ # 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 usedforaxinaxs[len(datasets):]:
ax.remove()
# Do some common setup for axes.foraxinaxs:
ax.xaxis.set(major_locator=MultipleLocator(1))
ax.yaxis.set(major_locator=MultipleLocator(1))
# Plot the data.forax, datasetinzip(axs, datasets):
ax.plot(dataset)
show()
which yields (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.)
Activity
tacaswell commented on Nov 10, 2020
The fix here is to sort out why #14997 only worked on the x-axis and not the y-axis...
anntzer commented on 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 commented on 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
which yields
(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.)
thriveth commented on Aug 17, 2021
I still have this problem, if there is not a fix, is there possibly a known work-around?