matplotlibstreamlitpyfpdf

How to save subplot as PDF using FPDF and Streamlit


I have a subplot that I want to save in a PDF. I am trying to do this inside a Streamlit app and to create the PDF I am using FPDF library. The subplot is working without any problems. The problem is that I can't save it as PDF because an error is shown saying that the figure that I want to save is not defined. This is how I created the figure containing the subplot:

fig1, (ax1, ax2) = plt.subplots(1, 2)

hip_angle, _, _ = angle_calculation(merged)
hip_angle_2, _, _ = angle_calculation(merged_2)

sub_plotting(freq_left, hip_angle_2, 'Hip angle - Left leg', ax1)
sub_plotting(freq_right, hip_angle, 'Hip angle - Right leg', ax2)
ax1.set_xlabel('Gait phase %')
ax2.set_xlabel('Gait phase %')
ax1.set_ylabel('Angle (degrees)')
ax2.set_ylabel('Angle (degrees)')
ax1.set_xticks(np.arange(0, 110, step=10))
ax2.set_xticks(np.arange(0, 110, step=10))
ax1.set_ylim([-20, 50])
ax2.set_ylim([-20, 50])

ax1.set_aspect('auto')
ax2.set_aspect('auto')

fig1.set_figheight(8)
fig1.set_figwidth(18)
st.pyplot(fig1)

# Here I am just creating a button to download the figure

fig = 'Hip angle.png'
plt.savefig(fig)
with open(fig, "rb") as f:
    btn = st.download_button(
        label="Descarregar imagem",
        data=f,
        file_name=fig,
        mime="image/png")

This is what I am using to try to save the figure in the PDF.

checkbox = st.checkbox('Name', value='')
if checkbox:
    name = st.text_input('Nome do utente', value=' ')

    st.download_button(
        label="Descarregar relatório",
        data=create_pdf(fig1),
        file_name="Relatório.pdf",
        mime="application/pdf",
    )

As I mentioned, there's an error saying that the fig1 is not defined. This is the function that I am using to create the PDF template.

def create_pdf(figure1):

    pdf = FPDF()
    pdf.add_page()

    figure = io.BytesIO()
    figure1.savefig(figure, format="png")
    saved_fig = tempfile.NamedTemporaryFile()

    with open(f"{saved_fig.name}.png", 'wb') as sf:
        sf.write(figure.getvalue())

    pdf.set_xy(30, 50)
    pdf.image(figure1, w=140, h=110)
    figure.close()

    return bytes(pdf.output())

Solution

  • Here is a simplified code that works with pdf download. We create the pdf file first based on png file. Once created we use it for download.

    Code
    import matplotlib.pyplot as plt
    import streamlit as st 
    from fpdf import FPDF
    
    
    def create_pdf(img_fn, pdf_fn):
        """
        Create pdf written to pdf_fn with the image file img_fn.
        """
        pdf = FPDF()
        pdf.add_page()
    
        # Save to pdf
        pdf.set_xy(30, 50)
        pdf.image(img_fn, w=140, h=110)
        pdf.output(pdf_fn)
    
    
    def main():
        x = [1, 2, 3, 4, 5, 6]
        y = [1, 5, 3, 5, 7, 8]
    
        fig1, (ax1, ax2) = plt.subplots(1, 2)
    
        ax1.plot(x, y)
        ax2.scatter(x, y)
    
        st.pyplot(fig1)
    
        # Save to png
        img_fn = 'Hip angle.png'
        fig1.savefig(img_fn)
    
        # Prepare file for download.
        dfn = 'angle.png'
        with open(img_fn, "rb") as f:
            st.download_button(
                label="Descarregar imagem",
                data=f,
                file_name=dfn,
                mime="image/png")
    
        # pdf download
        checkbox = st.checkbox('Name', value='')
        if checkbox:
            pdf_fn = 'mypdf.pdf'
            create_pdf(img_fn, pdf_fn)
    
            with open(pdf_fn, 'rb') as h_pdf:
                st.download_button(
                    label="Descarregar relatório",
                    data=h_pdf,
                    file_name="Relatório.pdf",
                    mime="application/pdf",
                )
    
    
    if __name__ == '__main__':
        main()
    
    Output

    enter image description here

    Downloaded png output:

    enter image description here

    Downloaded pdf output

    enter image description here