Issue
Long time lurker, first time poster, hope someone can help!
I am an artist trying to wrap my head around generative/procedural design using numpy and various assorted tools in jupyter notebook.
I have some code https://github.com/GreySoulX/Circle-generator/blob/main/BrokenCircles.ipynb (see below) that will generate a number of concentric circles of random radius and output them as SVG code. I can get it to display, and I can even get the SVG output with the basic code, but when put it all in a functions and call it with interactive() my saved files come out empty rather that what is shown in my notebook with widgets.VBox() .
Any idea where I can fix this? Am I just missing this by a million miles?
import numpy as np
import matplotlib.patches as mpatches
from matplotlib.collections import PatchCollection
import matplotlib.pyplot as plt
from IPython.display import display, Markdown, clear_output
from ipywidgets import widgets
from ipywidgets import interact, interact_manual, interactive, Button
def make_circles(n_circles=100,min_radius=0.05, max_radius=9.0, debug=False, Refresh=False ):
x_bounds = [-10, 10]
y_bounds = [-10, 10]
circles = []
for i in range(n_circles):
c = np.array([0, 0])
r = np.unique(np.sort(np.round(np.random.uniform(min_radius,max_radius,1),2)))
circles.append((c, r))
circles
circle_patches = []
for c, r in circles:
circle_patches.append(mpatches.Circle(c, r, fill=None, edgecolor='black'))
fig, ax = plt.subplots(figsize=(20, 20))
if not debug:
plt.grid(False)
plt.axis('off')
ax.set_aspect('equal')
ax.set_xlim(x_bounds)
ax.set_ylim(y_bounds)
collection = PatchCollection(circle_patches, match_original=True)
ax.add_collection(collection)
return fig, ax
w = interactive(make_circles,
n_circles=(1,300,1),
min_radius=(0.00, 2.0, 0.1),
max_radius=(2.0, 20, 0.5))
#------Button----------
button = widgets.Button(description='Save as...')
out = widgets.Output()
def on_button_clicked(_):
#link function with output
with out:
#what happens when we hit button
#clear_output()
print('Saving Files...')
plt.savefig('SomeFile.svg', bbox_inches = 'tight', pad_inches = 0)
plt.savefig('SomeFile.png', bbox_inches = 'tight', pad_inches = 0)
# linking button and function together using a button's method
button.on_click(on_button_clicked)
# displaying Circles and button
widgets.VBox([button,out,w])
#------End Button------------
Solution
What's happening
This is an issue with how the inline backend handles closing figures and what plt.savefig
does internally. The way static figures are displayed in notebooks (i.e. when not using ipympl) is that at the end of cell execution (or in this case at the end of the callback from the slider) the current figure is closed and displayed.
However plt.savefig
expects there to be a currently open figure as it calls plt.gcf
(get current figure) internally which either grabs the most recently active figure or creates a new empty figure if no figures are active.
So when you did this not in functions the figure wasn't closed until the cell was finished executing and so plt.savefig
was able to find the figure. However when you moved to functions it was no longer able to find the current figure.
There are two basic solutions to this.
Solutions
1. Global fig
You can lift figure to the global scope and use fig.savefig
- this makes sure that both the plot updating method and the saving method are refering to the same fig
.
def make_circles(n_circles=100,min_radius=0.05, max_radius=9.0, debug=False, Refresh=False ):
global fig
...
fig, ax = plt.subplots()
....
def on_button_clicked(_):
global fig
...
fig.savefig('SomeFile.svg', bbox_inches = 'tight', pad_inches = 0)
2 - interactive backend
Use one of the interactive backends such as %matplotlib qt
or %matplotlib ipympl
. If you are working in a notebook or jupyterlab then i'd recommend ipympl which you can install with pip install ipympl
.
With these backends the same closing of the figure does not automatically happen, so you can structure your code like this:
%matplotlib ipympl
fig, ax = plt.subplots()
def make_circles(n_circles=100,min_radius=0.05, max_radius=9.0, debug=False, Refresh=False ):
ax.cla() # clear all the artists off the axes
...
# use the same axes for `add_collection`
def on_button_clicked(_):
# you can now use `plt.savefig` but I would still recommend using `fig.savefig`
Answered By - Ianhi
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.