Python PDF Reports in Python/v3

How to make PDF reports with Python and Plotly Graphs.

Note: this page is part of the documentation for version 3 of, which is not the most recent version.
See our Version 4 Migration Guide for information about how to upgrade.

Creating PDF Reports with Plotly Graphs and Python

Since Plotly graphs can be embedded in HTML or exported as a static image, you can embed Plotly graphs in reports suited for print and for the web.

This notebook is a primer on creating PDF reports with Python from HTML with Plotly graphs. This notebook uses:

  • Plotly for interactive, web native graphs
  • IPython Notebook to create this notebook, combining text, HTML, and Python code
  • xhtml2pdf to convert HTML to PDF in Python
In [1]:
! pip install xhtml2pdf
Collecting xhtml2pdf
  Downloading xhtml2pdf-0.0.6.tar.gz (100kB)
    100% |████████████████████████████████| 102kB 917kB/s
[?25hRequirement already satisfied (use --upgrade to upgrade): html5lib in /Users/chriddyp/Repos/venvpy27/lib/python2.7/site-packages (from xhtml2pdf)
Collecting pyPdf2 (from xhtml2pdf)
  Downloading PyPDF2-1.25.1.tar.gz (194kB)
    100% |████████████████████████████████| 196kB 853kB/s
[?25hCollecting Pillow (from xhtml2pdf)
  Downloading Pillow-2.9.0-cp27-none-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl (2.9MB)
    100% |████████████████████████████████| 2.9MB 179kB/s
[?25hCollecting reportlab>=2.2 (from xhtml2pdf)
  Downloading reportlab-3.2.0.tar.gz (1.9MB)
    100% |████████████████████████████████| 1.9MB 237kB/s
[?25hRequirement already satisfied (use --upgrade to upgrade): six in /Users/chriddyp/Repos/venvpy27/lib/python2.7/site-packages (from html5lib->xhtml2pdf)
Requirement already satisfied (use --upgrade to upgrade): pip>=1.4.1 in /Users/chriddyp/Repos/venvpy27/lib/python2.7/site-packages (from reportlab>=2.2->xhtml2pdf)
Requirement already satisfied (use --upgrade to upgrade): setuptools>=2.2 in /Users/chriddyp/Repos/venvpy27/lib/python2.7/site-packages (from reportlab>=2.2->xhtml2pdf)
Building wheels for collected packages: xhtml2pdf, pyPdf2, reportlab
  Running bdist_wheel for xhtml2pdf
  Stored in directory: /Users/chriddyp/Library/Caches/pip/wheels/30/29/7d/2a654daefdfc4a22b8db5c40ed5484e0826795156f8db80fab
  Running bdist_wheel for pyPdf2
  Stored in directory: /Users/chriddyp/Library/Caches/pip/wheels/fb/dc/59/8509bdf92a52265bede0c75a70f2af01da24c109405799f623
  Running bdist_wheel for reportlab
  Stored in directory: /Users/chriddyp/Library/Caches/pip/wheels/f5/d5/b5/ac451e39e9ca2f401a62afd1f841522af903701caca19f1c16
Successfully built xhtml2pdf pyPdf2 reportlab
Installing collected packages: pyPdf2, Pillow, reportlab, xhtml2pdf
Successfully installed Pillow-2.9.0 pyPdf2-1.25.1 reportlab-3.2.0 xhtml2pdf-0.0.6

Part 1 - Create the HTML Template

In [2]:
# The public plotly graphs to include in the report. These can also be generated with `py.plot(figure, filename)`
graphs = [
In [3]:
from IPython.display import display, HTML

def report_block_template(report_type, graph_url, caption=''):
    if report_type == 'interactive':
        graph_block = '<iframe style="border: none;" src="{graph_url}.embed" width="100%" height="600px"></iframe>'
    elif report_type == 'static':
        graph_block = (''
            '<a href="{graph_url}" target="_blank">' # Open the interactive graph when you click on the image
                '<img style="height: 400px;" src="{graph_url}.png">'

    report_block = ('' +
        graph_block +
        '{caption}' + # Optional caption to include below the graph
        '<br>'      + # Line break
        '<a href="{graph_url}" style="color: rgb(190,190,190); text-decoration: none; font-weight: 200;" target="_blank">'+
            'Click to comment and see the interactive graph' + # Direct readers to Plotly for commenting, interactive graph
        '</a>' +
        '<br>' +
        '<hr>') # horizontal line                       

    return report_block.format(graph_url=graph_url, caption=caption)

interactive_report = ''
static_report = ''

for graph_url in graphs:
    _static_block = report_block_template('static', graph_url, caption='')
    _interactive_block = report_block_template('interactive', graph_url, caption='')

    static_report += _static_block
    interactive_report += _interactive_block

Display the interactive report, suited for the web

This version contains the interactive version of the Plotly graphs, served from Plotly's server

Display the static report, to be converted to PDF

This version contains the static version of the Plotly graphs, also served from Plotly's server

Looks good!

Part 2 - Convert the HTML to PDF with xhtml2pdf

In [6]:
from xhtml2pdf import pisa             # import python module

# Utility function
def convert_html_to_pdf(source_html, output_filename):
    # open output file for writing (truncated binary)
    result_file = open(output_filename, "w+b")

    # convert HTML to PDF
    pisa_status = pisa.CreatePDF(
            source_html,                # the HTML to convert
            dest=result_file)           # file handle to recieve result

    # close output file
    result_file.close()                 # close output file

    # return True on success and False on errors
    return pisa_status.err
In [7]:
convert_html_to_pdf(static_report, 'report.pdf')
WARNING:xhtml2pdf:Created temporary file /var/folders/d4/c4jpnb754pj3cfj5pv8y9m080000gn/T/tmp1ttqTi
In [8]:
! open report.pdf

Generating Images on the fly

The static report in the example above uses graphs that were already created in Plotly. Sometimes it's helpful to use graph images that are created on-the-fly. For example, if you're using plotly.js to create the web-reports you might not be saving the graphs to accounts on

To create static images of graphs on-the-fly, use the plotly.plotly.image class. This class generates images by making a request to the Plotly image server.

Here's an alternative template that uses py.image.get to generate the images and template them into an HTML and PDF report.

In [9]:
import plotly.plotly as py
import base64

width = 600
height = 600

template = (''
    '<img style="width: {width}; height: {height}" src="data:image/png;base64,{image}">'
    '{caption}'                              # Optional caption to include below the graph

# A collection of Plotly graphs
figures = [
    {'data': [{'x': [1,2,3], 'y': [3,1,6]}], 'layout': {'title': 'the first graph'}},
    {'data': [{'x': [1,2,3], 'y': [3,7,6], 'type': 'bar'}], 'layout': {'title': 'the second graph'}}

# Generate their images using `py.image.get`
images = [base64.b64encode(py.image.get(figure, width=width, height=height)).decode('utf-8') for figure in figures]

report_html = ''
for image in images:
    _ = template
    _ = _.format(image=image, caption='', width=width, height=height)
    report_html += _

convert_html_to_pdf(report_html, 'report-2.pdf')