How download a pdf in my Dash & Plotly application

I want to download a PDF that includes the content of tab 4 in my Dash application. It’s important that the visual content (front end) is preserved. With my current script, I managed to export a PDF, but it only contains text and doesn’t display the charts and cards as I intended.

app.layout = html.Div(
    [
        html.Div(
            [
                html.Img(src='/assets/ESCUDO_EDLP.png', style={'height': '95px', 'width': '60px', 'margin-bottom': '20px'}),
                html.H1("DASHBOARD NUTRICIÓN ESTUDIANTES DE LA PLATA",
                        style={'text-align': 'center', 'font-size': '30px', 'margin-bottom': '20px', 'color': 'white'}),
            ],
            style={'display': 'flex', 'flex-direction': 'column', 'align-items': 'center', 'background-color': 'black',
                   'padding': '10px'}
        ),
        dbc.Tabs(
            [
                dbc.Tab(tab1_content, label="INFORME EVOLUTIVO INDIVIDUAL",
                        label_style={'color': 'black', 'background-color': 'white'},
                        active_label_style={'color': 'red'}),
                dbc.Tab(tab2_content, label="DASH DE GESTIÓN",
                        label_style={'color': 'black', 'background-color': 'white'},
                        active_label_style={'color': 'red'}),
                dbc.Tab(tab3_content, label="INFORME COLECTIVO",
                        label_style={'color': 'black', 'background-color': 'white'},
                        active_label_style={'color': 'red'}),
                dbc.Tab(tab4_content, label="INFORME INDIVIDUAL",
                        label_style={'color': 'black', 'background-color': 'white'},
                        active_label_style={'color': 'red'}),
                dbc.Tab(label="CARGA DE DATOS",
                        label_style={'color': 'black', 'background-color': 'white'},
                        active_label_style={'color': 'red'},
                        )
            ],
            id="tabs",
        ),
        html.Button('Descargar PDF', id='btn-download-pdf', n_clicks=0),
        dcc.Download(id='download-pdf')
    ], 
    style={'position': 'relative'}
)

@app.callback(
    Output('download-pdf', 'data'),
    Input('btn-download-pdf', 'n_clicks'),
    State('tab-4', 'children')
)
def download_pdf(n_clicks, tab4_children):
    if n_clicks > 0:
        # Convertir el contenido HTML de la pestaña 4 a una cadena
        html_content = str(tab4_children)

        # Crear un archivo temporal para almacenar el HTML
        with tempfile.NamedTemporaryFile(suffix=".html", delete=False) as temp_file:
            temp_file.write(html_content.encode("utf-8"))

        # Generar el PDF a partir del archivo temporal HTML  
        output_file = "informe_individual.pdf"             
        options = {'disable-local-file-access': True}
        pdfkit.from_file(temp_file.name, output_file, configuration=pdfkit.configuration(wkhtmltopdf=r'C:\Program Files\wkhtmltopdf\bin\wkhtmltopdf.exe'), options=options)

        # Leer el contenido del archivo PDF generado
        with open(output_file, "rb") as pdf:
            pdf_data = pdf.read()

        # Crear un diccionario con los datos del PDF para su descarga
        return dcc.send_bytes(pdf_data, filename=output_file)

    return None

Hi @Matemunioz,

I am guessing this thread should help you: Exporting multi page Dash app to pdf with entire layout - #10 by NeeradjPelargos. In summary, there are a couple of options to go about this. There is a module called pdfkit. However, if you want to be able to control the styling and all, you can add a .js script. Both of these solutions are outlined in the thread.

Hello @Matemunioz and welcome to the forum!

Print to PDF is one of the most delicate taks in Dash. Personally I implemented this solution from mentioned thread:

If you are no expert in javascript like me it is relatively simple to understand (it contains one clientside callback and one javascript script you have to place in assets folder and downloading htm2pdf). When it is set up it works relatively nice (i.e. you can use classes to give htm2pdf commands how it should see the layout (i.e. placing html.Div(className=“html2pdf__page-break”) into layout makes a page divider in a layout and you can control the behaviour of new page breaker).

Problem is that I encountered numerous bugs I was not able to solve (i.e. the plotly charts were always a little bit wider than in the browser), only working solution was to construct the layout for pdf in some special way so it looked fine in pdf (I solved mentioned issue by setting the width of the charts about 3/4 of the width of the parent container and in PDF it occupies whole container). There are different problems with different components so it is possible using another components in another libraries would not cause this issues.

2 Likes

Include the content you want to print in a specific div, with a specific className,

then use the CSS @page and so that when you press on CTRL+P, everything but the div containing the content you want to print, is hidden. It will print what you need, with the format you want, for instance A4 297mmx209.8mm.

One point though: you have to design the content of your tab so that it fits on each page. So, if you have a lot of charts, include them all in a distinct page, which are then all put in a container.

You’will have a full and perfect controle on everything.

I just designed a 20p+ multiPage app, built as if it was a powerpoint presentation. Generated on the fly. It works like a charm :slight_smile:

2 Likes

Hi, just one remark regarding this. In my experience responsive graphs within tabs and using the technique of hiding for print don’t work out of the box. It need some client side JS to resize correctly. Also you need to resize dynamically since a graph is actually built of many layersz where css resizing impact only a few… Some nested canvas type of components (based on the functionality used, don’t get resized)

I’ve used pagedjs for print preview and layout, which are based on the principles outlined above.

https://pagedjs.org/

Reg,I.

@jcuypers
It depends on what you mean by out of the box. On my side it didnt require a lot of finetunning to have it working.
If the layout is properly set and defined on a grid, with some element in “auto” sizing or “min-content”, you can control the layout without js. I have circa 15 figures per A4 pages, on 2 to 4 col, the rest being dash.DataTables. Mix of sankeys, bar, plot, etc.

Set the responsive attribute in dcc.Graph on True. But it is important do really design the structure of the app first. Keep your div empty when designing it, apply some background color to see what is where and often double check that when pressing on CTRL + P you actually print everything on 1 page, not 2.
Then, when the grid structure is in place, fill it with your charts.

One note though; my pages are not included in tabs. But I doubt this change anything, in the end. I’m keen to experiment/check this point asap. I will update my post later if I’m wrong.

1 Like