読者です 読者をやめる 読者になる 読者になる

PythonのReportLabで、表(TableやTableStyle)について調べてみた

Python ReportLab

ReportLabでpdfに表を描いてみたところ、悩んだところがあったため、メモを残しておきます。

なお、詳細はReportLabの公式ドキュメント中の「ReportLab PDF LibraryUser Guide」のp77〜にも記載があります。

 
目次

 

環境

 
今回使うReportLabの実装は、基本的な形は以下となります。必要に応じてこのクラスを継承、_draw()メソッドをオーバーライドして各表を描きます。

注意点として、デフォルトと異なり、pdfの原点を左上(bottomup=False)にしてあります。

base.py

from django.http import HttpResponse
from django.views import View

from reportlab.lib.pagesizes import A4
from reportlab.lib.pagesizes import portrait
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.cidfonts import UnicodeCIDFont
from reportlab.pdfgen import canvas
from reportlab.lib.units import mm


class BaseView(View):
    filename = 'example.pdf'
    title = 'title: example'
    font_name = 'HeiseiKakuGo-W5'
    is_bottomup = False

    def get(self, request, *args, **kwargs):
        # pdf用のContent-TypeやContent-Dispositionをセット
        response = HttpResponse(status=200, content_type='application/pdf')
        response['Content-Disposition'] = 'filename="{}"'.format(self.filename)
        # 即ダウンロードしたい時は、attachmentをつける
        # response['Content-Disposition'] = 'attachment; filename="{}"'.format(self.filename)

        # 日本語が使えるゴシック体のフォントを設定する
        pdfmetrics.registerFont(UnicodeCIDFont(self.font_name))

        # A4縦書きのpdfを作る
        size = portrait(A4)

        # pdfを描く場所を作成:位置を決める原点は左上にする(bottomup)
        # デフォルトの原点は左下
        doc = canvas.Canvas(response, pagesize=size, bottomup=self.is_bottomup)

        # pdfのタイトルを設定
        doc.setTitle(self.title)

        # pdf上にも、タイトルとして使用したクラス名を表示する
        doc.drawString(10*mm, 10*mm, self.__class__.__name__)

        self._draw(doc)

        return response


    def _draw(self, doc):
        pass

 

複数列・複数行の表を作成

複数列・複数行の表を描くには、

  • 複数列:配列の要素が複数あるデータを用意
  • 複数行:2次元配列としてデータを用意

とします。

以下の場合、3列3行の表のデータとなります。

data = [
    ['行1-列1', '行1-列2-*********', '行1-列3-*********-*********'],
    ['行2-列1', '行2-列2-*********', '行2-列3-*********-*********'],
    ['行3-列1', '行3-列2-*********', '行3-列3-*********-*********'],
]

 
上記のデータを使って表を描いてみます。

multi_rows.py

class BasicMultiRows(BaseView):
    def _draw(self, doc):
        # 複数行の表を用意したい場合、二次元配列でデータを用意する
        data = [
            ['行1-列1', '行1-列2-*********', '行1-列3-*********-*********'],
            ['行2-列1', '行2-列2-*********', '行2-列3-*********-*********'],
            ['行3-列1', '行3-列2-*********', '行3-列3-*********-*********'],
        ]

        table = Table(data)
        # TableStyleを使って、Tableの装飾をします
        table.setStyle(TableStyle([
            # 表で使うフォントとそのサイズを設定
            ('FONT', (0, 0), (-1, -1), self.font_name, 9),
            # 四角に罫線を引いて、0.5の太さで、色は黒
            ('BOX', (0, 0), (-1, -1), 1, colors.black),
            # 四角の内側に格子状の罫線を引いて、0.25の太さで、色は赤
            ('INNERGRID', (0, 0), (-1, -1), 0.25, colors.red),
            # セルの縦文字位置を、TOPにする
            # 他にMIDDLEやBOTTOMを指定できるのでお好みで
            ('VALIGN', (0, 0), (-1, -1), 'TOP'),
        ]))

        # tableを描き出す位置を指定
        table.wrapOn(doc, 50*mm, 10*mm)
        table.drawOn(doc, 50*mm, 10*mm)

        # pdfを保存
        doc.save()

 
結果は以下の通りです。bottomup=Falseなので、原点は左上になります。

indexの降順で、表の上から下に並びます。

f:id:thinkAmi:20170117060903p:plain

 
ちなみに、bottomup=Trueにした場合、原点は左下となり、こんな感じになります。

indexの昇順で、表の上から下に並びます。

f:id:thinkAmi:20170117060906p:plain

 

複数列・複数行の表で、セルの高さや幅を指定

上の例では、表のセルの高さや幅は自動計算で設定されました。

任意の位置にあるセルの高さや幅を指定したい場合は、Tableオブジェクトを生成する際にrowHeights(行の高さ)やcolWidths(列の幅)を使います。

列・行ごとに設定したい場合はタプルで長さを渡し、全て一律で設定したい場合は単一値を指定します。

 
例えば、列幅は左から20mm・40mm・60mm、行の高さは一律10mmとしたい場合、

table = Table(data, colWidths=(20*mm, 40*mm, 60*mm,), rowHeights=10*mm)

と書くと、結果は以下となります。

f:id:thinkAmi:20170117060926p:plain

 

セルごとの設定

一番左の列のみ色を塗る

ここまでは表のセル全体に関する設定でしたので、次はセルごとの設定を行います。

ReportLabでは、セルごとの設定はTableStyleを使います。

TableStyleで指定するセル位置の表記は、ExcelでいうところのR1C1形式にて、(列, 行)のタプルで表現します。

また、列と行のindexは0から始まります。

 
例えば、5x5の表に対して左側の列だけ色塗りをする場合、

table.setStyle(TableStyle([
    ('VALIGN', (0, 0), (-1, -1), 'TOP'),
    # 指定された範囲の背景色を変える
    ('BACKGROUND', (0, 0), (0, 4), colors.lightpink),
]))

のようにTableStyleのコンストラクタにて、

  • タプルの2番目の要素に、開始セル位置: (0, 0)
  • タプルの3番目の要素に、終了セル位置: (0, 4)

をそれぞれ指定します。

結果は、

f:id:thinkAmi:20170117060928p:plain

となります。

 

一番左の列のみ色を塗る (indexはマイナスバージョン)

(列, 行)のindexにはマイナス値も設定することができます。

Pythonのindexのマイナス値と同じ考え方ですので、-1は最後、-2は最後から2番目となります。

例えば左側の列だけ色塗りするには、

table.setStyle(TableStyle([
    ('BACKGROUND', (0, 0), (0, -1), colors.lightpink),
]))

のように指定します。

f:id:thinkAmi:20170117060931p:plain

応用として、中央の範囲を色塗りする場合は、

table.setStyle(TableStyle([
    ('BACKGROUND', (1, 0), (3, 4), colors.lightpink),
]))

とすると、

f:id:thinkAmi:20170117060934p:plain

となります。

 
また、マイナスのindexを使う場合は、

table.setStyle(TableStyle([
    ('BACKGROUND', (1, 0), (-2, -1), colors.lightpink),
]))

とすると、

f:id:thinkAmi:20170118060743p:plain

となります。

 

罫線を引く

ReportLabではTableStyleのLine Commandsを使って罫線を引きます。

Line Commandsにはいくつかの種類がありますので、それぞれ試してみます。

なお、Line Commandsの形式は以下の通りです。

table.setStyle(TableStyle([
    # 決められた範囲で、太さや色を指定して、罫線を引く
    (<Line Comamnd名のリテラル>, 開始セルを示したタプル, 終了セルを示したタプル, 線の太さを示す数値, 線の色),
]))

 

セルの左側に罫線を引く

LINEBEFOREを使います。

table.setStyle(TableStyle([
    ('LINEBEFORE', (0, 0), (0, 4), 0.25, colors.black),
]))

f:id:thinkAmi:20170117060945p:plain

 

セルの右側に罫線を引く

LINEAFTERを使います。

table.setStyle(TableStyle([
    ('LINEAFTER', (0, 0), (0, 4), 0.25, colors.black),
]))

f:id:thinkAmi:20170117060948p:plain

 

セルの上に罫線を引く (bottomupの影響あり)

LINEABOVEを使います。

table.setStyle(TableStyle([
    ('LINEABOVE', (0, 0), (0, 4), 0.25, colors.black),
]))

なお、今回の場合、bottomup=Falseと原点を左上にしていますので、セルの下側に罫線があります。

f:id:thinkAmi:20170117060953p:plain

 

セルの下に罫線を引く (bottomupの影響あり)

LINEBELOWを使います。

table.setStyle(TableStyle([
    ('LINEBELOW', (0, 0), (0, 4), 0.25, colors.black),
]))

なお、こちらもbottomup=Falseの影響を受け、セルの上側に罫線があります。

f:id:thinkAmi:20170117060958p:plain

 

セルの外枠に罫線を引く

BOXを使います。

table.setStyle(TableStyle([
    ('BOX', (0, 0), (0, 4), 0.25, colors.black),
]))

f:id:thinkAmi:20170117061002p:plain

 
もしくは、OUTLINEでも同じ結果になります。

table.setStyle(TableStyle([
    ('OUTLINE', (0, 0), (0, 4), 0.25, colors.black),
]))

f:id:thinkAmi:20170117061007p:plain

 

セルの内側に格子状の罫線を引く

INNERGRIDを使います。

なお、今までの例と異なり、分かりやすくするためにセルの範囲を中央にしてあります。

table.setStyle(TableStyle([
    ('INNERGRID', (1, 1), (3, 3), 0.25, colors.black),
]))

f:id:thinkAmi:20170117061011p:plain

 

セルのすべてに罫線を引く

GRIDを使います。

なお、こちらも、分かりやすくするためにセルの範囲を中央にしてあります。

table.setStyle(TableStyle([
    ('GRID', (1, 1), (3, 3), 0.25, colors.black),
]))

f:id:thinkAmi:20170117061015p:plain

 

セルを結合する

SPANを使います。

下記の例は、

  • 2列2行目から4列4行目までをSAPNで結合
  • 2列2行目から3列3行目までの背景色をlightpink

としています。

なお、SAPNで結合した部分にあるデータは、最初のセルを除いて削除されることに注意します。

table.setStyle(TableStyle([
    # わかりやすくするため、全範囲をグリッドにしておく
    ('GRID', (0, 0), (-1, -1), 0.25, colors.black),
    # 指定した範囲のセルを結合して、背景色を入れる
    # ただし、結合した部分のデータは最初のセルを除いて削除されることに注意
    ('SPAN', (1, 1), (3, 3)),
    ('BACKGROUND', (1, 1), (2, 2), colors.lightpink),
]))

f:id:thinkAmi:20170117061019p:plain

 

ソースコード

GitHubに上げてあります。table_styleアプリが今回のアプリです。
thinkAmi-sandbox/Django_ReportLab_on_Heroku-sample