🎨

ReportLabでSeabornのグラフをSVGで埋め込む

2024/06/29に公開

ReportLabを使ってPDFを作成時の、画像の埋め込みに関する記事です。特にsvgなどのベクタ画像をファイルを介さずに、PDFへ埋め込むスクリプトを紹介します。

ReportLabだけでグラフ出力・埋め込みを完結させる

ReportLabでPDFを出力する際に、Pythonのpandasなどで集計してグラフを出力してそのままPDFに埋め込みたいですよね?画像ファイルとして出力して、それを読み込んでみたいな手順を踏むこともできますが、可能ならばファイルを介さない方が楽です。

上記のことは、ReportLabに備わっている機能で実現できます。

# see: https://chocottopro.com/?p=227
# にあるデータ/グラフを出力するコードを参考にしています。

from reportlab.graphics.shapes import Drawing
from reportlab.graphics.charts.barcharts import VerticalBarChart
from reportlab.graphics import renderPDF
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4, portrait


def main():
    file = "example.pdf"
    # A4のPDF
    page = canvas.Canvas(file, pagesize=portrait(A4))

    # 以下引用元コード
    data = [
        (2010, 1000, 1500),
        (2011, 1500, 1800),
        (2012, 1800, 2000),
        (2013, 2000, 2500),
    ]
    drawing = Drawing(400, 200)
    chart = VerticalBarChart()
    chart.x = 50
    chart.y = 50
    chart.width = 300
    chart.height = 125
    chart.data = data
    chart.valueAxis.valueMin = 0
    chart.valueAxis.valueMax = 3000
    chart.valueAxis.valueStep = 500
    chart.categoryAxis.labels.boxAnchor = 'ne'
    chart.categoryAxis.labels.dx = 8
    chart.categoryAxis.labels.dy = -2
    chart.categoryAxis.labels.angle = 30
    drawing.add(chart)
    # ここだけ変更
    renderPDF.draw(drawing, canvas=page, x=50, y=50)
    # pdfを出力
    page.save()


if __name__ == "__main__":
    main()


上のスクリプトによって作成されるPDFファイル

グラフはseabornなどで出力してPDFに埋め込む

上記のように、ReportLabにある機能だけでグラフを出力・埋め込みはできます。ただ、ReportLabではなく有名なグラフライブラリの出力を利用したい場合があると思います。加えて、jpgやpng画像などのラスタ画像ではなく、拡大しても綺麗なままのsvgやepsのラスタ画像の方が好ましいです。

その時に便利なのがsvglibです。以下のコマンドでインストールします。

$ pip install svglib

上記のライブラリと今回はseabornを使ってグラフを出力し、そのままPDFに埋め込みます。ポイントとしては画像ファイルに出力していないところです。

# https://stats.biopapyrus.jp/python/pie-chart.html
# にあるデータ/グラフを出力するコードを参考にしています。

import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4, portrait
from svglib.svglib import svg2rlg
from reportlab.graphics import renderPDF
from io import BytesIO


def main():
    file = "example2.pdf"
    # A4のPDF
    page = canvas.Canvas(file, pagesize=portrait(A4))

    # 以下引用元コード
    plt.style.use('default')
    sns.set()
    sns.set_style('whitegrid')
    sns.set_palette('Set2')

    x = ['A', 'B', 'C', 'D', 'E']
    y = np.array([300, 220, 180, 90, 10])

    fig = plt.figure()
    ax = fig.add_subplot(1, 1, 1)
    ax.pie(y, labels=x, autopct="%1.1f%%")

    imgdata = BytesIO()
    plt.savefig(imgdata, format="svg")
    imgdata.seek(0)
    drawing = svg2rlg(imgdata)
    renderPDF.draw(drawing, canvas=page, x=50, y=50)
    # pdfを出力
    page.save()


if __name__ == '__main__':
    main()


上のスクリプトによって作成されるPDFファイル

画像の埋め込みに関しては、xyがグラフの左下の位置になります。

renderPDF.draw(drawing, canvas=page, x=50, y=50)

グラフのサイズを変えたい場合は、以下の点に留意しているとすぐに変換可能です。

seabornなどのpltで指定するfigsizeの単位はインチです。そのため以下の例では縦横5インチのグラフになります。

fig = plt.figure(figsize=(5, 5))

一方、PDFなどの単位はピクセルになります。いろいろ試したところ、seabornでの出力結果の90倍の値が換算したピクセルの値になります。そのためfigsize=(5,5)で指定した場合には450x450の大きさになります。

page.rect(50, 50, 450, 450, fill=True)
renderPDF.draw(drawing, canvas=page, x=50, y=50)


上のサイズ調整によって作成されるPDFファイル

以上、ReportLabを使ってグラフの埋め込みについて紹介しました。seabornなどのグラフをそのままPDF化できることはいいと思います。一方で、ReportLabは各APIが低レベルすぎて扱いにくく、利用できるシーンはとても限られているなとも思います。

それでも誰かの参考になれば幸いです。

そのほかの参考

https://zenn.dev/takahashi_m/articles/e00473bdeb9dd8

GitHubで編集を提案

Discussion