Search code examples
pythonmatplotlibpdffiguresavefig

Python - Insert 2 figures objects in one PDF page


I want to take 2 figures objects and save them together on one pdf page. This is my code that currently saves them on 2 separate pages:

if (output_pdf_name is not None):
    pdf = matplotlib.backends.backend_pdf.PdfPages(output_pdf_name)
    explained_seq_fetures_letters_list1 = explained_seq_fetures_letters1 if multiple_samples else [
        explained_seq_fetures_letters1]
    explained_seq_fetures_letters_list2 = explained_seq_fetures_letters2 if multiple_samples else [
        explained_seq_fetures_letters2]

    for explained_seq_fetures_letters_item1, explained_seq_fetures_letters_item2 in zip(explained_seq_fetures_letters_list1,explained_seq_fetures_letters_list2):
        # create Logo object
        explained_seq_fetures_letters_item = pd.DataFrame(explained_seq_fetures_letters_item1, columns=Logo_letters)
        IG_logo1 = create_DNA_logo(PWM_df=explained_seq_fetures_letters_item,
                                  secondary_color=secondary_color)

        pdf.savefig(IG_logo1.ax.figure)

        explained_seq_fetures_letters_item2 = explained_seq_fetures_letters_item2[explained_seq_fetures_letters_item2 != 0].reshape(-1, 1)
        explained_seq_fetures_letters_item = pd.DataFrame(explained_seq_fetures_letters_item2, columns=Logo_symbol)
        IG_logo2 = create_DNA_logo(PWM_df=explained_seq_fetures_letters_item,
                                  secondary_color=True)

        pdf.savefig(IG_logo2.ax.figure)

        plt.close('all')
    pdf.close()

IG_logo.ax.figure is figure object as you can see in the image: enter image description here

It is created by logomaker library.

This is an example of my figure: enter image description here

I tried to find a solution but it seems that there is no simple solution. Any ideas?

Thanks!


Solution

  • disclaimer: I am the author of the library borb (used in this answer)

    borb is a pure python library that makes it easy to work with a PDF document. It also has convenient wrappers to interact with other famous libraries (PIL, matplotlib, etc)

    There is a huge examples repo where one of the sections deals with exactly your problem; inserting matplotlib charts in a PDF. You can find that section here.

    For completeness I'll copy/paste the code below. Keep in mind this example first builds the chart object, you obviously won't be needing that part:

    from borb.pdf.document.document import Document
    from borb.pdf.page.page import Page
    from borb.pdf.canvas.layout.page_layout.multi_column_layout import SingleColumnLayout
    from borb.pdf.canvas.layout.page_layout.page_layout import PageLayout
    from borb.pdf.canvas.layout.text.paragraph import Paragraph
    from borb.pdf.pdf import PDF
    from borb.pdf.canvas.layout.image.chart import Chart
    
    from decimal import Decimal
    
    import matplotlib.pyplot as MatPlotLibPlot
    import numpy as np
    import pandas as pd
    
    
    def create_plot() -> None:
        # build DataFrame
        df = pd.DataFrame(
            {
                "X": range(1, 101),
                "Y": np.random.randn(100) * 15 + range(1, 101),
                "Z": (np.random.randn(100) * 15 + range(1, 101)) * 2,
            }
        )
    
        # plot
        fig = MatPlotLibPlot.figure()
        ax = fig.add_subplot(111, projection="3d")
        ax.scatter(df["X"], df["Y"], df["Z"], c="skyblue", s=60)
        ax.view_init(30, 185)
    
        # return
        return MatPlotLibPlot.gcf()
    
    
    def main():
        # create Document
        doc: Document = Document()
    
        # create Page
        page: Page = Page()
    
        # add Page to Document
        doc.append_page(page)
    
        # set a PageLayout
        layout: PageLayout = SingleColumnLayout(page)
    
        # add a Paragraph
        layout.add(Chart(create_plot(), width=Decimal(256), height=Decimal(256)))
    
        # store
        with open("output.pdf", "wb") as pdf_file_handle:
            PDF.dumps(pdf_file_handle, doc)
    
    
    if __name__ == "__main__":
        main()
    

    Other things worth mentioning:

    • PageLayout decides how your Page will be laid out. In this example I've opted for SingleColumnLayout which does what you'd expect, content takes up the entire page (minus margins) and flows from top to bottom.
    • You can try other PageLayout implementations (such as MultiColumnLayout) if you prefer more of a scientific journal kind of aesthetic.
    • You can nest the charts inside a Table object (another way to display them side by side).
    • You can add content as an attachment to a PDF. You could include your raw data (as csv) to the PDF, thus ensuring other people can easily build their own visualizations of the data in their preferred style.