☰Read-only archive⌕
Looking for help with Bokeh?

This is a read-only archive. Ask questions and join the conversation on GitHub.

Visit Community Support on GitHub →
Archived discussions · Captured October 28, 2026

Generate SVG images from Bokeh Plots and include them in a PDF Report with using "svglib" and "reportlab"

Community Support
jcacabelos
jcacabelos

Hello!!

I have created a script that generates a pdf with SVG images taken from a bokeh plot. It was done with the python libraries reportlab and svglib. I have followed these basic steps:

  1. Use the export_svgs builtin function in order to get the SVG file
  2. Create a Flowable object to add it to the pdf in reportlab
  3. Creating pdf reports with reportlab by the platypus methd (building the pdf with a bunch of elements and template).

As export_svgs can only export one plot at the same time, this may also useful if somebody want to export a grid of plots or a more complex layout.

from reportlab.lib.pagesizes import A4
from reportlab.platypus import Table, TableStyle, SimpleDocTemplate
from reportlab.lib import colors
from reportlab.lib.units import mm

from svglib.svglib import svg2rlg

import numpy as np
from bokeh.plotting import figure
from bokeh.io import curdoc, export_svgs, export_png
from bokeh.models import Button

def get_plot():
    N = 3000
    x = np.random.random(size=N) * 100
    y = np.random.random(size=N) * 100
    radii = np.random.random(size=N) * 1.5

    p = figure(
        x_axis_label='X axis',
        y_axis_label='Y axis',
        title='Random plot',
        tools="crosshair,pan,wheel_zoom,box_zoom,reset,tap,save,lasso_select",
        output_backend = "webgl"
    )

    p.scatter(
        x=x,
        y=y,
        radius=radii,
        fill_color='red',
        fill_alpha=0.6,
        line_color=None
    )

    print('Exporting plot.svg...')
    p.output_backend = "svg"
    export_svgs(p, filename="plot.svg")

    # create a new SVG or a PDF with the new layout
    print('Reading drawing from plot.svg...')
    return svg2rlg("plot.svg")

drawing = get_plot()

print('Scaling SVG image...')
sx = sy = 0.3  # scaling factor
drawing.width = drawing.minWidth() * sx
drawing.height = drawing.height * sy
drawing.scale(sx, sy)

data = [
    ['TABLE HEADER', None],  # this works as a leading row
    [drawing, drawing],
    [drawing, drawing],
    [drawing, drawing],
    [drawing, drawing],
    [drawing, drawing],
    [drawing, None]
]

table = Table(     # Flowable object
    data,
    colWidths=200,
    rowHeights=[1 * mm] + [drawing.height] * (len(data) - 1),
    # hAlign='LEFT',
    repeatRows=1
)

table.setStyle(TableStyle([
    # LEADING ROW
    ('LINEBELOW', (0, 0), (1, 0), 0.5, colors.black),
    ('SPAN', (0, 0), (1, 0)),                           # colspan
    ('FONTSIZE', (0, 0), (1, 0), 12),
    ('LEFTPADDING', (0, 0), (1, 0), 0),
    ('BOTTOMPADDING', (0, 0), (1, 0), 3),

    # REST OF ROWS
    ('LEFTPADDING', (1, 1), (-1, -1), 0),
    ('RIGHTPADDING', (1, 1), (-1, -1), 0),
    ('BOTTOMPADDING', (1, 1), (-1, -1), 0),
    ('TOPPADDING', (1, 1), (-1, -1), 0),
]))

story = []
story.append(table)

margin = 25 * mm
doc = SimpleDocTemplate(
    'minimal_reportlab_table_.pdf', pagesize=A4,
    rightMargin=margin, leftMargin=margin,
    topMargin=margin, bottomMargin=margin
)

doc.build(story)

If you run this code you may read a warning “Unable to find a suitable font for ‘font-family:helvetica’”. But I did not paid attention to it yet.

References:

⌁   Link to post
Mark_Murphy
Mark_Murphy

I did something similar and was thinking that it would be nice if rather than having to save the SVG file as a temporary file it would be great if Bokeh would allow a ByteIO stream with the SVG. Basically writing the svg to a file in memory then SVGlib can read the ByteIO stream.

⌁   Link to post
Bryan
Bryan

@Mark_Murphy I believe you could use get_svgs for that. It is the lower-level function that export_svgs uses internally. It could use a docstring if you want to contribute a small PR. (I don’t think it was originally conceived as public API but it’s in __all__ now, so it’s not going anywhere.)

⌁   Link to post
Mark_Murphy
Mark_Murphy

Thanks for the suggestion. I was developing this for another group and they had multiple graphs on the same page. This caused a lot of problems because the HTML conversion then to PDF would cause the graphs to be cut off. Also SVG is nice because it can handle scaling much better then any images.

Thanks if I find this does not work I’ll try it out tho.

⌁   Link to post
jcacabelos
jcacabelos

@Mark_Murphy To return string buffers instead of writing into file, I think with the following code would be enough (I have added a new boolean parameter). I tested it with a couple of examples and it worked:

def get_svgs(obj, driver=None, timeout=5, buffer=False, **kwargs):
    with _tmp_html() as tmp:
        html = get_layout_html(obj, **kwargs)
        with io.open(tmp.path, mode="w", encoding="utf-8") as file:
            file.write(decode_utf8(html))
        web_driver = driver if driver is not None else webdriver_control.get()
        web_driver.get("file:///" + tmp.path)
        wait_until_render_complete(web_driver, timeout)
        svgs = web_driver.execute_script(_SVG_SCRIPT)

    if buffer:
        buffer_list = [StringIO(x) for x in svgs]
        return buffer_list
    else:
        return svgs

And I had to change the code in my example a little bit to make it work with buffers:

svgs = get_svgs(p, buffer=True)
return svg2rlg(svgs[0])

I can do the PR if it is not already done (also adding the docstring)

⌁   Link to post

End of archived discussion · Replies are disabled