I have a user choose 4 directories using tkinter in "loadingfiles." From there, it loads all of the files from each directory.
how would I be able to save all of the subplots into a PDF. I know I have to do it from the main function, but how would I go about this?
def loadingfiles():
#loads all of the files from each directory
#returns Files
def initialize_and_Calculate(Files=[],*args):
fig = plt.figure()
fig.set_size_inches(9,5,forward=True)
gs1=gridspec.GridSpec(1,2)
ax0=fig.add_subplot(gs1[0,0])
ax1=fig.add_subplot(gs1[0,1])
#treat x and y as a parameter within a file in a directory. Can be any numbers
ax0.plot(x,y,'-')
ax1.plot(x1,y1,'-')
fig.tight_layout()
def main():
Files=loadingfiles() #this loads the files in directory
L=len(Files)
for c in range (0,L): #this for loops runs the initialize_and_Calc. function per directory.
initialize_and_Calculate(Files[c]) #'File[0]' is directory 1. 'File[1]' is directory 2...and so on
plt.show()
if __name__=="__main__":
main()
If this doesn't make any sense, then how can I pass a 'fig' in a function. Say if I were to make a figure in my main function, how can I pass 'fig' to a function?
You may return the figure from the function and append it to a list. Then you may loop through the list and save the figures to a pdf file.
from matplotlib import gridspec
import matplotlib.pyplot as plt
from matplotlib.backends.backend_pdf import PdfPages
import numpy as np
def loadingfiles():
return range(4)
def initialize_and_Calculate(Files=[],*args):
fig = plt.figure()
fig.set_size_inches(9,5,forward=True)
gs1=gridspec.GridSpec(1,2)
ax0=fig.add_subplot(gs1[0,0])
ax1=fig.add_subplot(gs1[0,1])
x,y = zip(*np.cumsum(np.random.rand(20,2), axis=0))
ax0.plot(x,y,'-')
#ax1.plot(x1,y1,'-')
fig.tight_layout()
return fig
def main():
Files=loadingfiles()
figures = []
for c in range (0,len(Files)):
figures.append(initialize_and_Calculate(Files[c]))
with PdfPages('multipage_pdf.pdf') as pdf:
for f in figures:
pdf.savefig(f)
plt.show()
if __name__=="__main__":
main()
Of course you may also create the figure inside a loop in the main function and pass it as argument to the plotting function.
from matplotlib import gridspec
import matplotlib.pyplot as plt
from matplotlib.backends.backend_pdf import PdfPages
import numpy as np
def loadingfiles():
return range(4)
def initialize_and_Calculate(Files, fig ,*args):
fig.set_size_inches(9,5,forward=True)
gs1=gridspec.GridSpec(1,2)
ax0=fig.add_subplot(gs1[0,0])
ax1=fig.add_subplot(gs1[0,1])
x,y = zip(*np.cumsum(np.random.rand(20,2), axis=0))
ax0.plot(x,y,'-')
#ax1.plot(x1,y1,'-')
fig.tight_layout()
def main():
Files=loadingfiles()
figures = []
for c in range (0,len(Files)):
fig = plt.figure()
initialize_and_Calculate(Files[c], fig)
figures.append(fig)
with PdfPages('multipage_pdf.pdf') as pdf:
for f in figures:
pdf.savefig(f)
plt.show()
if __name__=="__main__":
main()
Related
I am trying to create a program that can visualize the change of a portfolio in real time. To do this, I update my data and create a new plot with it. When I run the code below in PyCharm, SciView stops displaying the plots after 30 iterations. Ideally, I would like to have it only show the most recent plot, but it would also be fine if it just truncated the history so that I at least always see the current plot. Is there any way to do this? I tried different ways to close the figures (e. g. using plt.close()), but did not achieve the desired result.
Code to reproduce:
import matplotlib.pyplot as plt
import numpy as np
import random
class RealTimeVisualizer:
def __init__(self, x, y):
self.x = x
self.y = y
def update_data(self, x_value, y_value):
"""
Appends values to the data arrays.
"""
self.x.append(x_value)
self.y.append(y_value)
def create_plot(self):
"""
Takes an x and a y (both 1D arrays and constructs a plot from it)
:return: a pyplot figure object
"""
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
# Draw x and y lists
ax.clear()
ax.plot(self.x, self.y)
# Format plot
plt.xticks(rotation=90)
plt.title('Portfolio')
plt.ylabel('Value')
plt.show()
plt.close('all')
if __name__ == '__main__':
portfolio_cash = 10000
tick = 0
real_time_visualizer = RealTimeVisualizer([tick], [portfolio_cash])
for i in np.arange(50):
tick += 1
portfolio_cash += random.randint(-50, 50)
real_time_visualizer.update_data(tick, portfolio_cash)
real_time_visualizer.create_plot()
Rather than creating a new plot and window every time, you can also update the current Matplotlib figure data in each iteration. You then need to view the plot in an interactive Matplotlib environment.
Live updating Matplotlib plots
You can use code similar to this to update the data inside the plot:
import matplotlib.pyplot as plt
import random
plt.ion() # Set pyplot to interactive mode
fig = plt.figure() # Create a figure
ax = fig.add_subplot(111) # Add a subplot to the figure
# Variables for our updating data
x = []
y = []
for i in range(50):
# Generate random data
x.append(i)
y.append(random.random())
# Update the plot with the new x, y data
ax.plot(x, y, 'ro-')
fig.canvas.draw()
fig.canvas.flush_events()
Allow for interactive Matplotlib mode when using SciView
Deactivate SciView or manually set your backend to another interactive GUI to see the updating plot.
This code snipped automatically chooses the correct backend (same list as in the Matplotlib code):
import matplotlib.pyplot as plt
candidates = ["macosx", "qt5agg", "gtk3agg", "tkagg", "wxagg"]
for candidate in candidates:
try:
plt.switch_backend(candidate)
print('Using backend: ' + candidate)
break
except (ImportError, ModuleNotFoundError):
pass
Applied to your code
Your code with suggested modifications would look like this:
import matplotlib.pyplot as plt
import numpy as np
import random
class RealTimeVisualizer:
def __init__(self, x, y):
self.x = x
self.y = y
def update_data(self, x_value, y_value):
"""
Appends values to the data arrays.
"""
self.x.append(x_value)
self.y.append(y_value)
def update_plot(self, fig, ax):
import _tkinter
try:
ax.plot(self.x, self.y, 'ro-')
fig.canvas.draw()
fig.canvas.flush_events()
# Capture an error in case the plotting window is being closed
except _tkinter.TclError:
pass
if __name__ == '__main__':
portfolio_cash = 10000
tick = 0
real_time_visualizer = RealTimeVisualizer([tick], [portfolio_cash])
# Choose the right backend
candidates = ["macosx", "qt5agg", "gtk3agg", "tkagg", "wxagg"]
for candidate in candidates:
try:
plt.switch_backend(candidate)
print('Using backend: ' + candidate)
break
except (ImportError, ModuleNotFoundError):
pass
# Create plot
plt.ion() # Set pyplot to interactive mode
fig = plt.figure() # Create a figure
ax = fig.add_subplot(111) # Add a subplot to the figure
for i in np.arange(50):
tick += 1
portfolio_cash += random.randint(-50, 50)
real_time_visualizer.update_data(tick, portfolio_cash)
real_time_visualizer.update_plot(fig, ax) # Update the plot the new data
Same issue here.
The workaround I found is to change the matplotlib backend to plot outside the PyCharm.
import matplotlib
matplotlib.use('qt5Agg')
matplotlib.pyplot.ioff()
Then you have to explicit open a new figure and show
for i in range(100):
plt.figure()
...
...
plt.show()
I have to create a group of matplotlib figures, which I would like to directly present in a PDF report without saving them as a file.
The data for my plots is stored in a Pandas DataFrame:
Right now I do not know other better option than first save the image and use it later.
I am doing something like that:
import matplotlib.pylab as plt
from reportlab.platypus import BaseDocTemplate, Image
for index, row in myDataFrame.iterrows():
fig = plt.figure()
plt.plot(row['Xvalues'], row['Yvalues'],'o', color='r')
fig.savefig('figure_%s.png' % (row['ID']))
plt.close(fig)
text = []
doc = BaseDocTemplate(pageName, pagesize=landscape(A4))
for f in listdir(myFolder):
if f.endswith('png'):
image1 = Image(f)
text.append(image1)
doc.build(text)
Here is the best solution provided by matplotlib itself:
from matplotlib.backends.backend_pdf import PdfPages
import matplotlib.pyplot as plt
with PdfPages('foo.pdf') as pdf:
#As many times as you like, create a figure fig and save it:
fig = plt.figure()
pdf.savefig(fig)
....
fig = plt.figure()
pdf.savefig(fig)
VoilĂ
Find a full example here: multipage pdf matplotlib
I think you can save the figure into a buffer using io.BytessIO and use that in platypus. Something like this perhaps?
import io
import matplotlib.pylab as plt
from reportlab.platypus import BaseDocTemplate, Image
buffers = []
for index, row in myDataFrame.iterrows():
fig = plt.figure()
plt.plot(row['Xvalues'], row['Yvalues'],'o', color='r')
mybuffer = io.BytesIO()
fig.savefig(mybuffer, format = 'pdf')
mybuffer.seek(0)
buffers.append(mybuffer)
plt.close(fig)
text = []
doc = BaseDocTemplate(pageName, pagesize=landscape(A4))
doc.build(buffers)
using my package autobasedoc https://pypi.org/project/autobasedoc/ your example would look like that:
from autobasedoc import autorpt as ar
from autobasedoc import autoplot as ap
#ap.autoPdfImg
def my_plot(index, row, canvaswidth=5): #[inch]
fig, ax = ap.plt.subplots(figsize=(canvaswidth,canvaswidth))
fig.suptitle(f"My simple plot {index}", fontproperties=fontprop)
ax.plot(row['Xvalues'], row['Yvalues'],label=f"legendlabel{index}")
return fig
doc = ar.AutoDocTemplate(pageName)
content = []
for index, row in myDataFrame.iterrows():
content.append(my_plot(index, row))
doc.build(content)
I have a main program main.py in which I call various functions with the idea that each function plots something to 1 figure. i.e. all the function plots append detail to the 1 main plot.
Currently I have it set up as, for example:
main.py:
import matplotlib.pylab as plt
a,b,c = 1,2,3
fig = func1(a,b,c)
d,e,f = 4,5,6
fig = func2(d,e,f)
plt.show()
func1:
def func1(a,b,c):
import matplotlib.pylab as plt
## Do stuff with a,b and c ##
fig = plt.figure()
plt.plot()
return fig
func2:
def func2(d,e,f):
import matplotlib.pylab as plt
## Do stuff with d,e and f ##
fig = plt.figure()
plt.plot()
return fig
This approach is halfway there but it plots separate figures for each function instead of overlaying them.
How can I obtain 1 figure with the results of all plots overlaid on top of each other?
It is much better to use the OO interface for this puprose. See http://matplotlib.org/faq/usage_faq.html#coding-styles
import matplotlib.pyplot as plt
a = [1,2,3]
b = [3,2,1]
def func1(ax, x):
ax.plot(x)
def func2(ax, x):
ax.plot(x)
fig, ax = plt.subplots()
func1(ax, a)
func2(ax, b)
It seems silly for simple functions like this, but following this style will make things much much less painful when you want to do something more sophisticated.
This should work. Note that I only create one figure and use the pyplot interface to plot to it without ever explicitly obtaining a reference to the figure object.
import matplotlib.pyplot as plt
a = [1,2,3]
b = [3,2,1]
def func1(x):
plt.plot(x)
def func2(x):
plt.plot(x)
fig = plt.figure()
func1(a)
func2(b)
I am creating two Python scripts to produce some plots for a technical report. In the first script I am defining functions that produce plots from raw data on my hard-disk. Each function produces one specific kind of plot that I need. The second script is more like a batch file which is supposed to loop around those functions and store the produced plots on my hard-disk.
What I need is a way to return a plot in Python. So basically I want to do this:
fig = some_function_that_returns_a_plot(args)
fig.savefig('plot_name')
But what I do not know is how to make a plot a variable that I can return. Is this possible? Is so, how?
You can define your plotting functions like
import numpy as np
import matplotlib.pyplot as plt
# an example graph type
def fig_barh(ylabels, xvalues, title=''):
# create a new figure
fig = plt.figure()
# plot to it
yvalues = 0.1 + np.arange(len(ylabels))
plt.barh(yvalues, xvalues, figure=fig)
yvalues += 0.4
plt.yticks(yvalues, ylabels, figure=fig)
if title:
plt.title(title, figure=fig)
# return it
return fig
then use them like
from matplotlib.backends.backend_pdf import PdfPages
def write_pdf(fname, figures):
doc = PdfPages(fname)
for fig in figures:
fig.savefig(doc, format='pdf')
doc.close()
def main():
a = fig_barh(['a','b','c'], [1, 2, 3], 'Test #1')
b = fig_barh(['x','y','z'], [5, 3, 1], 'Test #2')
write_pdf('test.pdf', [a, b])
if __name__=="__main__":
main()
If you don't want the picture to be displayed and only get a variable in return, then you can try the following (with some additional stuff to remove axis):
def myplot(t,x):
fig = Figure(figsize=(2,1), dpi=80)
canvas = FigureCanvasAgg(fig)
ax = fig.add_subplot()
ax.fill_between(t,x)
ax.autoscale(tight=True)
ax.axis('off')
canvas.draw()
buf = canvas.buffer_rgba()
X = np.asarray(buf)
return X
The returned variable X can be used with OpenCV for example and do a
cv2.imshow('',X)
These import must be included:
from matplotlib.figure import Figure
from matplotlib.backends.backend_agg import FigureCanvasAgg
The currently accepted answer didn't work for me as such, as I was using scipy.stats.probplot() to plot. I used matplotlib.pyplot.gca() to access an Axes instance directly instead:
"""
For my plotting ideas, see:
https://pythonfordatascience.org/independent-t-test-python/
For the dataset, see:
https://github.com/Opensourcefordatascience/Data-sets
"""
# Import modules.
from scipy import stats
import matplotlib.pyplot as plt
import pandas as pd
from tempfile import gettempdir
from os import path
from slugify import slugify
# Define plot func.
def get_plots(df):
# plt.figure(): Create a new P-P plot. If we're inside a loop, and want
# a new plot for every iteration, this is important!
plt.figure()
stats.probplot(diff, plot=plt)
plt.title('Sepal Width P-P Plot')
pp_p = plt.gca() # Assign an Axes instance of the plot.
# Plot histogram. This uses pandas.DataFrame.plot(), which returns
# an instance of the Axes directly.
hist_p = df.plot(kind = 'hist', title = 'Sepal Width Histogram Plot',
figure=plt.figure()) # Create a new plot again.
return pp_p, hist_p
# Import raw data.
df = pd.read_csv('https://raw.githubusercontent.com/'
'Opensourcefordatascience/Data-sets/master//Iris_Data.csv')
# Subset the dataset.
setosa = df[(df['species'] == 'Iris-setosa')]
setosa.reset_index(inplace= True)
versicolor = df[(df['species'] == 'Iris-versicolor')]
versicolor.reset_index(inplace= True)
# Calculate a variable for analysis.
diff = setosa['sepal_width'] - versicolor['sepal_width']
# Create plots, save each of them to a temp file, and show them afterwards.
# As they're just Axes instances, we need to call get_figure() at first.
for plot in get_plots(diff):
outfn = path.join(gettempdir(), slugify(plot.title.get_text()) + '.png')
print('Saving a plot to "' + outfn + '".')
plot.get_figure().savefig(outfn)
plot.get_figure().show()
I would like to:
pylab.figure()
pylab.plot(x)
pylab.figure()
pylab.plot(y)
# ...
for i, figure in enumerate(pylab.MagicFunctionReturnsListOfAllFigures()):
figure.savefig('figure%d.png' % i)
What is the magic function that returns a list of current figures in pylab?
Websearch didn't help...
Pyplot has get_fignums method that returns a list of figure numbers. This should do what you want:
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(100)
y = -x
plt.figure()
plt.plot(x)
plt.figure()
plt.plot(y)
for i in plt.get_fignums():
plt.figure(i)
plt.savefig('figure%d.png' % i)
The following one-liner retrieves the list of existing figures:
import matplotlib.pyplot as plt
figs = list(map(plt.figure, plt.get_fignums()))
Edit: As Matti Pastell's solution shows, there is a much better way: use plt.get_fignums().
import numpy as np
import pylab
import matplotlib._pylab_helpers
x=np.random.random((10,10))
y=np.random.random((10,10))
pylab.figure()
pylab.plot(x)
pylab.figure()
pylab.plot(y)
figures=[manager.canvas.figure
for manager in matplotlib._pylab_helpers.Gcf.get_all_fig_managers()]
print(figures)
# [<matplotlib.figure.Figure object at 0xb788ac6c>, <matplotlib.figure.Figure object at 0xa143d0c>]
for i, figure in enumerate(figures):
figure.savefig('figure%d.png' % i)
This should help you (from the pylab.figure doc):
call signature::
figure(num=None, figsize=(8, 6),
dpi=80, facecolor='w', edgecolor='k')
Create a new figure and return a
:class:matplotlib.figure.Figure
instance. If num = None, the
figure number will be incremented and
a new figure will be created.** The
returned figure objects have a
number attribute holding this number.
If you want to recall your figures in a loop then a good aproach would be to store your figure instances in a list and to call them in the loop.
>> f = pylab.figure()
>> mylist.append(f)
etc...
>> for fig in mylist:
>> fig.savefig()
Assuming you haven't manually specified num in any of your figure constructors (so all of your figure numbers are consecutive) and all of the figures that you would like to save actually have things plotted on them...
import matplotlib.pyplot as plt
plot_some_stuff()
# find all figures
figures = []
for i in range(maximum_number_of_possible_figures):
fig = plt.figure(i)
if fig.axes:
figures.append(fig)
else:
break
Has the side effect of creating a new blank figure, but better if you don't want to rely on an unsupported interface
I tend to name my figures using strings rather than using the default (and non-descriptive) integer. Here is a way to retrieve that name and save your figures with a descriptive filename:
import matplotlib.pyplot as plt
figures = []
figures.append(plt.figure(num='map'))
# Make a bunch of figures ...
assert figures[0].get_label() == 'map'
for figure in figures:
figure.savefig('{0}.png'.format(figure.get_label()))