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.