Update Line2D properties from line on different axes in matplotlib

Viewed 293

In matplotlib, the update_from method of a Line2D object can be used to copy properties from another line (see e.g. this answer). This is not working if the two lines live on different axes. The following code:

fig, (ax1, ax2) = plt.subplots(2, 1)
line1, = ax1.plot(range(10), "r.")
line2, = ax2.plot(*line1.get_xydata().T)
line2.update_from(line1)

raises

AttributeError: 'NoneType' object has no attribute 'extents'

while the traceback leaves me puzzled.

My questions are:

  • Why is this error raised?
  • How can I copy (all) Line2D properties of line1 to line2 instead?

EDIT

After a bit more testing I can say that the AttributeError above is for example raised in a Jupyter notebook session with the %matplotlib inline backend. With the %matplotlib notebook backend or in a regular Python script (e.g. with the "qt5agg" backend), the code passes without an error but line2 is "invisible" afterwards.

enter image description here

For completeness, the above image was created using (Anaconda) Python 3.7.9 and matplotlib 3.3.1 with:

import matplotlib as mpl 
import matplotlib.pyplot as plt 

mpl.use("qt5agg")

fig, (ax1, ax2) = plt.subplots(2, 1)
line1, = ax1.plot(range(10), "r.")
line2, = ax2.plot(*line1.get_xydata().T)
line2.update_from(line1)

plt.savefig("test.png")

The problem remains that I cannot copy the Line2D properties from line1 to line2.

EDIT 2

Throwing a plt.tight_layout() into the mix brings back the AttributeError.

EDIT 3

As requested in the comments, here is the traceback for the error I get with plt.tight_layout() (EDIT 2):

Traceback (most recent call last):
  File "test.py", line 11, in <module>
    plt.tight_layout()
  File "/home/janjoswig/.pyenv/versions/miniconda3-4.7.12/envs/md379/lib/python3.7/site-packages/matplotlib/cbook/deprecation.py", line 451, in wrapper
    return func(*args, **kwargs)
  File "/home/janjoswig/.pyenv/versions/miniconda3-4.7.12/envs/md379/lib/python3.7/site-packages/matplotlib/pyplot.py", line 1490, in tight_layout
    gcf().tight_layout(pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect)
  File "/home/janjoswig/.pyenv/versions/miniconda3-4.7.12/envs/md379/lib/python3.7/site-packages/matplotlib/cbook/deprecation.py", line 411, in wrapper
    return func(*inner_args, **inner_kwargs)
  File "/home/janjoswig/.pyenv/versions/miniconda3-4.7.12/envs/md379/lib/python3.7/site-packages/matplotlib/figure.py", line 2615, in tight_layout
    pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect)
  File "/home/janjoswig/.pyenv/versions/miniconda3-4.7.12/envs/md379/lib/python3.7/site-packages/matplotlib/tight_layout.py", line 308, in get_tight_layout_figure
    pad=pad, h_pad=h_pad, w_pad=w_pad)
  File "/home/janjoswig/.pyenv/versions/miniconda3-4.7.12/envs/md379/lib/python3.7/site-packages/matplotlib/tight_layout.py", line 84, in auto_adjust_subplotpars
    bb += [ax.get_tightbbox(renderer, for_layout_only=True)]
  File "/home/janjoswig/.pyenv/versions/miniconda3-4.7.12/envs/md379/lib/python3.7/site-packages/matplotlib/axes/_base.py", line 4199, in get_tightbbox
    if np.all(clip_extent.extents == axbbox.extents):
AttributeError: 'NoneType' object has no attribute 'extents'
1 Answers

It seems update_from updates too much, including the transformation and the clipbox. Maybe the error comes from the object being totally invisible after clipping to the wrong clipbox?

A workaround can be to save both before updating and setting them back:

from matplotlib import pyplot as plt

fig, (ax1, ax2) = plt.subplots(2, 1)
line1, = ax1.plot(range(10), "r.")
line2, = ax2.plot(*line1.get_xydata().T)
old_transform = line2.get_transform()
old_clipbox = line2.clipbox
line2.update_from(line1)
line2.set_transform(old_transform)
line2.clipbox = old_clipbox
plt.tight_layout()
plt.draw()

using update_from between different axes

Related