Learn how to use Dash Bio for next-gen sequencing & quality control. 🧬 Access the recorded webinar.

How to download a pdf file generated from a plot?

Hi

I would like to add a button in order to download a plot in, let says, pdf format. I succeeded to write the pdf file in the assets/plots/ folder but I am unable to pass the url of this file to the button in order to download the file.

Hereafter is a minimal example with what I have tried to do. If you click on the button, the file exits, you can browse to http://127.0.0.1:8050/assets/plots/figure.pdf and you get the file. But the button to not send the file.

#!/usr/bin/env python3
# -*- coding=utf-8 -*-

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output

from plotly.io import write_image
import flask

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

app.layout = html.Div(children=[
    html.A(
        id="img-download", 
        href="", 
        children=[html.Button("Download Image", id="download-btn")], 
        target="_blank",
    ),
    dcc.Graph(
        id='graph',
        figure={
            'data': [
                {'x': [1, 2, 3], 'y': [4, 1, 2], 'type': 'scatter'},
            ],
            'layout': {'title': 'So Title'}
        }
    )
])

@app.callback(Output('img-download', 'href'),
              [Input('download-btn', 'n_clicks'),
               Input('graph', 'figure')])
def make_image(n_clicks, figure):
    """ Make a picture """

    fmt = "pdf"
    filename = "figure.%s" % fmt

    if n_clicks is not None:
        write_image(figure, "assets/plots/" + filename)

        url = flask.request.host + app.get_asset_url("plots/" + filename)
        print(url)
        return url

if __name__ == '__main__':
    app.run_server(debug=False)

I also inspected the html code generated (inside chrome). It seems the link is ok, but still it does not work.

Thank you for your help

@gvallverdu Try to add the download attribute in you ‘a’ tag:

html.A(
    id="img-download", 
    href="", 
    children=[html.Button("Download Image", id="download-btn")],
    download="my-figure.pdf",
    target="_blank",
)

Hi,
No it does not work. Some seconds after I clicked on the button the seems to refresh but nothing happen. However, the picture is still well produced in assets/plots/.

Here you go with a working example, inspired from this very popular topic.

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output

from plotly.io import write_image
import plotly.graph_objs as go
import flask
import base64

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

app.layout = html.Div(children=[
    dcc.Loading(html.A(
        id="img-download", 
        href="", 
        children=[html.Button("Download Image", id="download-btn")], 
        target="_blank",
        download="my-figure.pdf"
    )),
    dcc.Graph(
        id='graph',
        figure={
            'data': [
                {'x': [1, 2, 3], 'y': [4, 1, 2], 'type': 'scatter'},
            ],
            'layout': {'title': 'So Title'}
        }
    )
])

@app.callback(Output('img-download', 'href'),
              [Input('graph', 'figure')])
def make_image(figure):
    """ Make a picture """

    fmt = "pdf"
    mimetype = "application/pdf"
    filename = "figure.%s" % fmt

    write_image(figure, "assets/plots/" + filename)
    data = base64.b64encode(open("assets/plots/" + filename, "rb").read()).decode("utf-8")
    pdf_string = f"data:{mimetype};base64,{data}"

    return pdf_string

if __name__ == '__main__':
    app.run_server(debug=False)
3 Likes

Thanks for your help.

Hereafter I put a workaround. In that case, I use a button to make the picture and a link to download this picture when the picture is produced.

Nevertheless, I am not sure it works online. I did not succeed in installing orca on my server (I do not have neither conda nor npm).

#!/usr/bin/env python3
# -*- coding=utf-8 -*-

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output

from plotly.io import write_image
from flask import Flask, send_from_directory
from urllib.parse import quote as urlquote

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

server = Flask(__name__)
app = dash.Dash(__name__, external_stylesheets=external_stylesheets, server=server)

app.layout = html.Div(children=[
    html.Button("Make Image", id="make-img-btn"),
    html.Div(id="download-img-btn"),
    dcc.Graph(
        id='graph',
        figure={
            'data': [
                {'x': [1, 2, 3], 'y': [4, 1, 2], 'type': 'scatter'},
            ],
            'layout': {'title': 'So Title'}
        }
    )
])

PLOTS_DIRECTORY = "./plots"

@server.route("/download/<path:path>")
def download(path):
    """Serve a file from the upload directory."""
    return send_from_directory(PLOTS_DIRECTORY, path, as_attachment=True)


@app.callback(Output('download-img-btn', 'children'),
              [Input('make-img-btn', 'n_clicks'),
               Input('graph', 'figure')])
def make_image(n_clicks, figure):
    """ Make a picture """

    fmt = "pdf"
    filename = "figure.%s" % fmt

    if n_clicks is not None:
        write_image(figure, PLOTS_DIRECTORY + filename)

        url = "/download/" + urlquote(filename)

        return html.A(download=filename, href=url, children=["Download " + filename])

if __name__ == '__main__':
    app.run_server(debug=True)
3 Likes

Hi there! Is it possible to do something like this but without generating the pdf file in the server? Thanks!

So where exactly would the pdf be produced? Would it be a static file?

Hello everyone, Is it possible to download the Pdf through Django, without using Flask ?

Please, could you append multiple figures ?

You could use my Download component. Here is the above example adopted,

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
from plotly.io import write_image
from dash_extensions import Download
from dash_extensions.snippets import send_bytes

# Create example figure.
fmt = "pdf"
fig = {'data': [{'x': [1, 2, 3], 'y': [4, 1, 2], 'type': 'scatter'}], 'layout': {'title': 'So Title'}}
# Create app.
app = dash.Dash(__name__, prevent_initial_callbacks=True)
app.layout = html.Div(children=[
    html.Button("Make Image", id="make-img-btn"), dcc.Graph(id='graph', figure=fig), Download(id='download')
])


@app.callback(Output('download', 'data'), [Input('make-img-btn', 'n_clicks'), Input('graph', 'figure')])
def make_image(n_clicks, figure):
    return send_bytes(lambda x: write_image(figure, x, format=fmt), "figure.{}".format(fmt))


if __name__ == '__main__':
    app.run_server()

It is available in the dash-extensions package,

pip install dash-extensions==0.0.18

1 Like

Works fine, but is there a solution to change the encode of downloaded pdf ?, because it gives it as bytes !

I kept trying I was able to do what I wanted. It turned out not to be that difficult. It was only a slight modification of your callback:

@app.callback(Output('img-download', 'href'),
              [Input('graph', 'figure')])
def make_image(figure):
    """ Make a picture """

    fmt = "pdf"
    mimetype = "application/pdf"
    filename = "figure.%s" % fmt

    data = base64.b64encode(to_image(figure, format=fmt)).decode("utf-8")
    pdf_string = f"data:{mimetype};base64,{data}"

    return pdf_string

Hi,

I tried the same way to convert it but while opening pdf,getting error saying can’t display content as content was not decoded correctly. I used exact same sample graph given in above post.

Thanks in advance!

Regards,
Irfan

What code are you using? I just tried the example i posted above, and it works for me (Chrome, Linux Mint).

Hi! The next code works for me. It’s the one from @RenaudLN with the modified callback.

# -*- coding: utf-8 -*-
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output

from plotly.io import to_image
import base64

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

app.layout = html.Div(children=[
    dcc.Loading(html.A(
        id="img-download", 
        href="", 
        children=[html.Button("Download Image", id="download-btn")], 
        target="_blank",
        download="my-figure.pdf"
    )),
    dcc.Graph(
        id='graph',
        figure={
            'data': [
                {'x': [1, 2, 3], 'y': [4, 1, 2], 'type': 'scatter'},
            ],
            'layout': {'title': 'So Title'}
        }
    )
])

@app.callback(Output('img-download', 'href'),
              [Input('graph', 'figure')])
def make_image(figure):
    """ Make a picture """

    fmt = "pdf"
    mimetype = "application/pdf"

    data = base64.b64encode(to_image(figure, format=fmt)).decode("utf-8")
    pdf_string = f"data:{mimetype};base64,{data}"

    return pdf_string

if __name__ == '__main__':
    app.run_server(debug=False)

Thanks Rauduc ! I figured out the issue while while converting one image to pdf. But what actually i am trying here is,to convert multiple images to PDF. I am creating base64 string for each image and then concatenating them but seem ending with incorrect content. I am trying like below:
@app.callback(Output(‘img-download’, ‘href’),
[Input(‘download-btn’, ‘n_clicks’)],
[State(‘graph’, ‘figure’),State(‘scatterplot2’,‘figure’)])
def make_image(n_clicks,figure1,figure2):
“”" Make a picture “”"

fmt = "pdf"
mimetype = "application/pdf"
filename = "figure.%s" % fmt

data1 = base64.b64encode(to_image(figure1, format=fmt)).decode(“utf-8”)

data2 = base64.b64encode(to_image(figure2, format=fmt)).decode(“utf-8”)

data = ‘’.join([data1,data2])

 pdf_string = f"data:{mimetype};base64,{data}"
   

return pdf_string

Mm, I’m not sure if it’s possible to download several files from just one link. I would imagine that it’s not. Maybe what you could do is compress the files into one, and download than one.

Hey EveryOne I am Sarvesh I am working On Two Python framework Django and Dash, Once I got task to generate pdf of dashboard and multiple graph so I go through the lots of things in dash and even in javascript and I generated pdf with multiple graph. Hope all of u find very used full

import pandas as pd 
import datetime as dt

import plotly.offline as pyo 
import plotly.graph_objs as go 
import plotly.express as px


import dash_table
import dash_core_components as dcc 
import dash_html_components as html 
import dash_bootstrap_components as dbc

from dash.dependencies import Input, Output, State
from django_plotly_dash import DjangoDash


labels = ['Oxygen','Hydrogen','Carbon_Dioxide','Nitrogen']
values = [4500, 2500, 1053, 500]

app = DjangoDash('Report',add_bootstrap_links=True)
app.css.append_css({ "external_url" : "/static/assets/css/dashstyle.css" })

html.Script(src='https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.2/html2pdf.bundle.js')

card1 = dbc.Card([
    dcc.Checklist(
         options=[{'label':'Top Five Highest Frequent Customers',}],
         className='printCheckBox',
         labelStyle={'fontWeight':'600',},
         inputStyle={'marginRight':'10px',},
         inputClassName='chkremove',
     ),
    dbc.CardBody([
        html.P(['2 His Majesties Letter to the Lord Thresurer & other of the Lords to deliver the Charge of the Tower & Prisones thereunto S.r Wm Wade Knight which was done by the Earle of Dorsett and the Earle of Devonshire on Thursday in the Afternoon, at the Clock being the 15 of Aug. 1605'])
    ])
],className='cardDesign')

card2 = dbc.Card([
    dcc.Checklist(
         options=[{'label':'Top Five Highest Frequent Customers',}],
         className='printCheckBox',
         labelStyle={'fontWeight':'600',},
         inputStyle={'marginRight':'10px',},
         inputClassName='chkremove',
     ),
    dbc.CardBody([
               dcc.Graph(figure=dict(
                   data=[go.Pie(labels=labels,values=values)],
                   layout=dict(autosize=True)
               ),
               responsive=True,
               )
    ])        
],className='cardDesign')

card2_1 = dbc.Card([
    dbc.CardBody([
               dcc.Graph(figure=dict(
                   data=[go.Pie(labels=labels,values=values)]
               ))
    ])
 ],className='cardDesign')

card2_2 = dbc.Card([
    dbc.CardBody([
               dcc.Graph(figure=dict(
                   data=[go.Pie(labels=labels,values=values)]
               ))
    ])
 ],className='cardDesign')

card3 = dbc.Card([
    dcc.Checklist(
         options=[{'label':'Top Five Highest Frequent Customers',}],
         className='printCheckBox',
         labelStyle={'fontWeight':'600',},
         inputStyle={'marginRight':'10px',},
         inputClassName='chkremove',
     ),
    dbc.CardBody([
               dcc.Graph(figure=dict(
                   data=[go.Bar(x=labels,y=values)]
               ))
    ])        
],className='cardDesign')

app.layout = html.Div([
    html.Div([

        dbc.Row([
            dbc.Col([
                html.H3(['Report PDF Generator'])
            ]),
        ]),

        dbc.Row([
            dbc.Col([
                card1
            ]),
        ]),

        dbc.Row([
            dbc.Col([
                card2
            ]),
        ]),

        dbc.Row([
        dbc.Col([
            dbc.Row([
                dbc.Col([card2_1],xs=12, sm=12, md=12, lg=6, xl=6),
                dbc.Col([card2_2],xs=12, sm=12, md=12, lg=6, xl=6),
            ])
        ]),
    ]),

    dbc.Row([
            dbc.Col([
                card2
            ]),
        ]),
   dbc.Row([
        dbc.Col([
            dbc.Row([
                dbc.Col([card2_1],xs=12, sm=12, md=12, lg=6, xl=6),
                dbc.Col([card2_2],xs=12, sm=12, md=12, lg=6, xl=6),
            ])
        ]),
    ]),

    dbc.Row([
            dbc.Col([
                card3
            ]),
        ]),


    ],id='print'),
    html.H1(id='h'),
    dbc.Button(children=['Download'],className="mr-1",id='js',n_clicks=0),
    html.Script(src="https://code.jquery.com/jquery-3.5.1.slim.min.js",integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj",crossOrigin='anonymous'),
    html.Script(src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js",integrity="sha384-ho+j7jyWK8fNQe+A12Hb8AhRq26LrZ/JpcUGGOn+Y7RsweNrtN/tE3MoK7ZeZDyx",crossOrigin='anonymous'),
    html.Script(src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.8.1/html2pdf.bundle.min.js")

],id='main')

app.clientside_callback(
    """
    function(n_clicks){
        if(n_clicks > 0){
            $('.chkremove').hide();
            var opt = {
                margin: 2,
                filename: 'myfile.pdf',
                image: { type: 'jpeg', quality: 0.98 },
                html2canvas: { scale: 1},
                jsPDF: { unit: 'cm', format: 'a2', orientation: 'p' },
                pagebreak: { mode: ['avoid-all'] }
            };
            html2pdf().from(document.getElementById("print")).set(opt).save();
            setTimeout(function(){
                $('.chkremove').show();
            },2000);
        }
    }
    """,
    Output('js','n_clicks'),
    Input('js','n_clicks')
)
![Capture|362x499](upload://1dHbFMhBT2oIAcEfl2V09hf32pf.jpeg)


Output of Above Code

Hello, I’ve tried applying this code to my own Dash py file and it keeps returning a nontype error. Can anyone tell me why this is happening? Thanks in advance.