1

In Jupyter Notebook, it appears that when I call .scatter on a 3d axes object created in another cell, it doesn't scatter anything on it.

Concretely, consider the following import statements,

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

assuming the following block of code is in one cell of Jupyter Notebook,

fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection = "3d")
ax.scatter([0], [0], [0], s = 5)  

then without asking, an image appears and I can see a point on it.

However, if

 fig = plt.figure()
 ax = fig.add_subplot(1, 1, 1, projection = "3d")

and

ax.scatter([0], [0], [0], s = 5) 

are in different cells, then it looks like the second returns <mpl_toolkits.mplot3d.art3d.Path3DCollection at 0xblablabla>, and no image appears, even if I later call plt.show(). If I later call plt.savefig("test.png"), then an empty image (with no empty axis box drawn) is saved.

It don't understand this behavior. I would like to be able to successively add things to an axes object, possibly in different cells.

Plop
  • 131
  • 6

1 Answers1

2

At conclusion of running a cell, modern Jupyter closes the plotting object and displays the plot object if it detects one being built in a cell and existing when the end of the cell is encountered.
That explains why after your second code block, this statement by you "then without asking, an image appears and I can see a point on it." plt.show() in most cases is no longer needed and is superfluous. Jupyter senses an matplotlib axes object being built and displays it.

Next addressing these two things:

  • "I call .scatter on a 3d axes object created in another cell, it doesn't scatter anything on it."
  • "...are in different cells, then it looks like the second returns <mpl_toolkits.mplot3d.art3d.Path3DCollection at 0xblablabla>, and no image appears"

So if you want to start building a plot like you say, using in an earlier cell but not complete it, as you suggest with this code:

fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection = "3d")

While you not adding any data in there yet, you have created a plot axis object, and so if that is a cell with the imports already previously done, the Jupyter sees the plot axes and displays them (as discussed above).
The easiest way to handle this is to tell Matplotlib/Jupyter before the end of the cell is reached that you want to close the plot object. That way there doesn't exist a plot object that had been being built in that cell and nothing is displayed as output from that cell.
So putting all that together with the imports, you can run some of the initial code for a plot in a single cell but have no axes be displayed by putting the following as your first cell:

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D 
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection = "3d")
plt.close()

(I use plt.close() in a similar manner in an answer to 'How can I display image data in Jupyter notebook AND control its position? '. Here uses it to allow you to make a plot as a function.)

Now you can put the next cell to add the data to your axes object and tell Jupyter to display it by invoking it as the last object in next cell. (ax.figure or fig.figure will both work for your example.) So the second cell would be:

ax.scatter([0], [0], [0], s = 5)
ax.figure

Note ax.scatter([0], [0], [0], s = 5) alone in the second cell doesn't work. You'll see something like you report in your post because what is returned is a call to scatter, like <mpl_toolkits.mplot3d.art3d.Path3DCollection at 0x7f9216de2860>, and the result of the last expression evaluated in a notebook is returned and displayed. Instead, to show the plot as output of the second cell, you need to invoke the figure associated. Since you aren't building a new one there, Jupyter doesn't automatically display the associated axes. (See here and here for more about that.)


Saving a plot as an image later.

You brought up you were struggling with that, too:

" If I later call plt.savefig("test.png"), then an empty image (with no empty axis box drawn) is saved."

You just aren't referencing the right thing. There's no longer the object that plt. methods would act on, by default, at that point.
To explain this better, let's make the situation simpler first. Imagine you ran the first two code blocks in your post as a single cell. As discussed above the object associated with that cell that plt.() methods would act on got closed and doesn't exist after that cell was run. However, you can reference the assigned object from your code because fortunately you put handles on those by assigning them to variables when you made them. So you can save the associated plot figure after-the-fact by referencing it and adding the savefig() method, like so:

fig.savefig("test.png")

That will work many cells away from the earlier one as long as you didn't make a new fig object or didn't clear the namespace or restart your kernel.

Wayne
  • 6,607
  • 8
  • 36
  • 93
  • I think this answers my question, but I don't get why an Axes object cannot be passed from cell to cell like any other object. And I am very confident that I have already done things like this (done things on an Axes object defined in another cell) for 2d plots without any problem (though I am going to test it again). – Plop Jul 18 '23 at 09:16
  • What do you mean by "There's no longer plt at that point."? I don't really understand if plt is the imported package or the current figure, and I don't understand how matplotlib and python figure this out on their own. – Plop Jul 18 '23 at 09:18
  • You were right, I think I got a little fuzzy on the language there. I think I more meant whatever `plt.` methods would by default act on aren't in existence at that point. I'll try to edit that in a bit. And I honestly don't know how exactly 'matplotlib and python figure this out' either; however, you don't need to really know. Follow more current practices and examples and/or stumble through like you have here. Hopefully, you got some sense of what you were encountering and some ideas on how to deal. Example: defining one plot in the same cell would be more in line with best practices. – Wayne Jul 18 '23 at 12:40
  • Ok. Thank you. I tried with 2d plots and the same happened. I was quite surprised to not having noticed it before... – Plop Jul 18 '23 at 14:51
  • Hmmm... In fact, when using fig.savefig("lala.png") instead of plt.savefig, everything works. I mistakingly thought that Jupyter just forgot about the ax object from one cell to the other. So: from one cell to another, both the figure and its axes objects continue to exist, however, what matplotlib thinks to be the current thing to plot ceases to exist from one cell to another. – Plop Jul 18 '23 at 14:59
  • 1
    Yes, now you are getting it. The building plot object that the `plt.` methods access, gets dealt with on a cell by cell basis. That is why when you defined axes & didn't put any data in, you still saw Jupyter make an empty plot because it was processing what it expected & then it finishes dealing with it in the cell & then it's cleaned up. However, if you assign the associated objects to variables then you can use that handle later because they are in the namespace of the notebook. This may help you when dealing with multiple plots in a notebook b/c now you know you can use different handles. – Wayne Jul 18 '23 at 15:16