Skip to content

3D Scatter Plot with Colorbar is not saved correctly with savefig #18885

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
JakubPolanowski opened this issue Nov 3, 2020 · 7 comments
Closed
Labels
Release critical For bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions. status: confirmed bug topic: color/colorbar topic: mplot3d
Milestone

Comments

@JakubPolanowski
Copy link

Bug report

Bug summary

When saving a 3D scatter plot with a color bar using savefig (either plt.savefig or fig.savefig), the data point colors are incorrect on the saved image, however the inline image (I'm using jupyter lab notebooks) is fine, the issue is only present when saving the figure. Note that this only happens when a colorbar is added to the figure, when a figure with points colored according to a set of values is saved, the issue does not occur. So it would seem that this issue is caused by some interaction of savefig and colorbar.

I've tried both matplotlib version 3.3.1 and 3.3.2 and in both versions this occurs. I've also tried downgrading to version 3.2.1, in version 3.2.1 this does not occur, so it seems some change in 3.3.1 is the cause of this issue.

Code for reproduction

Requirements: matplotlib 3.3.1 or 3.3.2

The following requirements are purely for setting up an example dataset which I used in the following code snippet to generate an example dataset to plot, as such I'm not listing the version numbers.

Optional: pandas, numpy

Also note that I used JupyterLab notebook to run the code, although given that I was able to 'fix' the issue by downgrading to matplotlib version 3.2.1 I don't think there is any link between running the code in JupyterLab and the issue itself.

#importing packages 
import matplotlib.pyplot as plt

import numpy as np
import itertools
import pandas as pd

# example data setup 
data = list(itertools.product(*[np.arange(0, 5, 1), np.arange(0, 5, 1), np.arange(0, 5, 1)]))
df = pd.DataFrame(data, columns=['x', 'y', 'z'])

df['c'] = df['x'] + df['y']

# plotting

## no color bar example (no issue)

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d', proj_type = 'ortho')

sc = ax.scatter(
    df['x'], 
    df['y'],
    df['z'],
    c=df['c'],
    s=40  # size is purely to make it easier to see
)  

plt.savefig('example no colorbar save.png')  # dpi has no effect, purely to make it easier to zoom into the figure

## color bar example (ISSUE)

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d', proj_type = 'ortho')

sc = ax.scatter(
    df['x'], 
    df['y'],
    df['z'],
    c=df['c'],
    s=40  # size is purely to make it easier to see
)  

fig.colorbar(sc)

plt.savefig('example colorbar save.png', dpi=300)  # dpi has no effect, purely to make it easier to zoom into the figure

Actual outcome

example colorbar save

This is being the saved figure with a color bar (note that if you zoom in the outlines of the data points are correct - see expected outcome below - but the fill of the points is incorrect).

Note that if no colorbar is added, the plot saves correctly as shown below.

example no colorbar save

Expected outcome

This is a screenshot of the colorbar chart as it is shown inline in the JupyterLab notebook.

example figure color barscreenshot

And this is a screenshot of the non colorbar chart as it is shown inline in the JupyterLab notebook, note this one is essentially the exact same as the saved version, so the issue is purely with the colorbar/savefig interaction.

example figure screenshot

Matplotlib version

  • Operating system: Windows 10
  • Matplotlib version: 3.3.2
  • Matplotlib backend (print(matplotlib.get_backend())):
  • Python version: 3.8.5
  • Jupyter version (if applicable): 2.2.6
  • Other libraries: pandas, numpy, itertools (python built in library)

Everything has been installed via conda, with all packages listed being install from the default channel.

@anntzer anntzer added this to the v3.3.3 milestone Nov 3, 2020
@anntzer anntzer added the Release critical For bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions. label Nov 3, 2020
@anntzer
Copy link
Contributor

anntzer commented Nov 3, 2020

bisects to #17378 (@timhoffm), although even before that there was already a slight difference between the colorbar and non-colorbar versions: the colorbar version has fully opaque markers whereas the non-colorbar one has partially transparent markers.

@JakubPolanowski
Copy link
Author

bisects to #17378 (@timhoffm), although even before that there was already a slight difference between the colorbar and non-colorbar versions: the colorbar version has fully opaque markers whereas the non-colorbar one has partially transparent markers.

Is there any somewhat simple workaround? Aside from just downgrading?

@timhoffm
Copy link
Member

timhoffm commented Nov 9, 2020

Is there any somewhat simple workaround? Aside from just downgrading?

Um, probably not.

although even before that there was already a slight difference between the colorbar and non-colorbar versions: the colorbar version has fully opaque markers whereas the non-colorbar one has partially transparent markers.

This is a good hint: Path3DCollection.do_3d_projection() adds the transparency, and since #17378 it reorders the points. Apparently, when using a colorbar, the Path3DCollection facecolors are updated/overwritten through a different code path.

In fact, I can break the example without the colorbar by adding sc.changed() (which in the case of a colorbar is triggered via mappable.autoscale_None()). It seems that Collection.update_scalarmappable is the actual culprit.

We need to find a way to do the color updates therein consistently with coloring and reordering in Path3DCollection.do_3d_projection().

@QuLogic
Copy link
Member

QuLogic commented Nov 10, 2020

Since we always recalculate on draw, I suggest something like: 6c0c30b applied to all 3D artists.

@tacaswell
Copy link
Member

I do not understand why the output from inline (which is under the hood calling savefig!) works...

@tacaswell
Copy link
Member

To answer my own question, it is because of the bbox_inches='tight' if you add that (which makes us do two internal renders) or save the figure a second time it "works". I suspect Tim's suggestion of sc.changed is the right place to start...

@tacaswell
Copy link
Member

Well, that was fun to track down! See the long docstring on the helper in #18929 if you are interested. In short, do_3d_projection and update_scalarmappable were both writing to the same place so we need to make sure they run in the right order.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Release critical For bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions. status: confirmed bug topic: color/colorbar topic: mplot3d
Projects
None yet
Development

No branches or pull requests

5 participants