TikZでフローチャートを書く

はじめに

Mermaidでフローチャートを書けるのですが、複雑になってくると配置に不満が出ます。

一方、draw.io Diagrams - Windows に無料でダウンロードしてインストールする | Microsoft Storeだと図を書くのに時間がかかります。

TikZでフローチャートを書く | Molina Tech Hubという素晴らしい記事を見つけたので、試してみました。TikZ初心者の私ですが、とりあえず希望通りの図が書けたということでメモです。

UbuntuでLuaLaTeXをセットアップ

LaTeXはいろいろ種類があるそうですが、[改訂第9版]LaTeX美文書作成入門 | Gihyo Digital Publishing … 技術評論社の電子書籍を見てLuaLaTeXを使うことにしました。

Linux/Linux Mint - TeX WikiTeX Live/Debianの「日本語関連のパッケージのみインストールする場合」の手順でセットアップします。

sudo apt install texlive-lang-japanese texlive-latex-extra texlive-luatex
横道: jlreq文書クラスも入ってます

日本語 LaTeX の新常識 2021 #LaTeX - Qiitaによると日本語の文書クラスはabenori/jlreqを使うと良さそうです。

jlreq文書クラスはtexlive-lang-japaneseパッケージに含まれています。

$ dpkg -L texlive-lang-japanese | grep -F jlreq.cls
/usr/share/texlive/texmf-dist/tex/latex/jlreq/jlreq.cls

描いてみたフローチャート

図1 flowchart for OWASP CRS RESPONSE-980.CORRELATION.conf
ソース:RESPONSE-980-CORRELATION.tex
\documentclass[tikz, border=8pt]{standalone}
\usepackage{tikz}
\usetikzlibrary{shapes.geometric}
\usetikzlibrary {shapes.misc}
\usetikzlibrary{positioning}
\begin{document}
\begin{tikzpicture}
  \tikzset{Terminal/.style={rounded rectangle, draw, text centered, text width=5cm, minimum height=1.5cm}};
  \tikzset{Process/.style={rectangle, draw, text centered, text width=5cm, minimum height=1.5cm}};
  \tikzset{Decision/.style={diamond, draw, text centered, aspect=6,text width=10cm, minimum height=1.5cm}};
  \node[Terminal] (start) at (0,0){Start};
  \node[Decision, below=1 of start.south](id_980041){REPORTING\_LEVEL == 0};
  \draw[->, thick] (start) -- (id_980041);

  \node[Decision, below=1 of id_980041.south](id_980042){REPORTING\_LEVEL >= 5};
  \draw[->, thick] (id_980041.south) -- (id_980042) node[pos=0, anchor=north west]{No};

  \node[Decision, below=1 of id_980042.south](id_980043){DETECTION\_ANOMALY\_SCORE == 0};
  \draw[->, thick] (id_980042.south) -- (id_980043) node[pos=0, anchor=north west]{No};

  \node[Decision, below=1 of id_980043.south](id_980044){BLOCKING\_INBOUND\_ANOMALY\_SCORE >= inbound\_anomaly\_score\_threshold};
  \draw[->, thick] (id_980043.south) -- (id_980044) node[pos=0, anchor=north west]{No};

  \node[Decision, below=1 of id_980044.south](id_980045){BLOCKING\_OUTBOUND\_ANOMALY\_SCORE >= outbound\_anomaly\_score\_threshold};
  \draw[->, thick] (id_980044.south) -- (id_980045) node[pos=0, anchor=north west]{No};

  \node[Decision, below=1 of id_980045.south](id_980046){REPORTING\_LEVEL < 2};
  \draw[->, thick] (id_980045.south) -- (id_980046) node[pos=0, anchor=north west]{No};

  \node[Decision, below=1 of id_980046.south](id_980047){DETECTION\_INBOUND\_ANOMALY\_SCORE >= inbound\_anomaly\_score\_threshold};
  \draw[->, thick] (id_980046.south) -- (id_980047) node[pos=0, anchor=north west]{No};

  \node[Decision, below=1 of id_980047.south](id_980048){DETECTION\_OUTBOUND\_ANOMALY\_SCORE >= outbound\_anomaly\_score\_threshold};
  \draw[->, thick] (id_980047.south) -- (id_980048) node[pos=0, anchor=north west]{No};

  \node[Decision, below=1 of id_980048.south](id_980049){REPORTING\_LEVEL < 3};
  \draw[->, thick] (id_980048.south) -- (id_980049) node[pos=0, anchor=north west]{No};

  \node[Decision, below=1 of id_980049.south](id_980050){BLOCKING\_ANOMALY\_SCORE > 0};
  \draw[->, thick] (id_980049.south) -- (id_980050) node[pos=0, anchor=north west]{No};

  \node[Decision, below=1 of id_980050.south](id_980051){REPORTING\_LEVEL < 4};
  \draw[->, thick] (id_980050.south) -- (id_980051) node[pos=0, anchor=north west]{No};

  \node[Terminal, below=1 of id_980051.south](log_reporting){LOG-REPORTING};
  \draw[->, thick] (id_980051.south) -- (log_reporting) node[pos=0, anchor=north west]{No};
  \draw[->, thick] (id_980042) -- ++(-10,0) node[pos=0, anchor=north east]{Yes} |- (log_reporting.west);
  \draw[-, thick] (id_980044) -- ++(-10,0) node[pos=0, anchor=north east]{Yes};
  \draw[-, thick] (id_980045) -- ++(-10,0) node[pos=0, anchor=north east]{Yes};
  \draw[-, thick] (id_980047) -- ++(-10,0) node[pos=0, anchor=north east]{Yes};
  \draw[-, thick] (id_980048) -- ++(-10,0) node[pos=0, anchor=north east]{Yes};
  \draw[-, thick] (id_980050) -- ++(-10,0) node[pos=0, anchor=north east]{Yes};

  \node[Process, below=1 of log_reporting.south](id_980170){msg: Anomaly Scores};
  \draw[->, thick] (log_reporting) --(id_980170);

  \node[Terminal, below=1 of id_980170.south](end_reporting){END-REPORTING};
  \draw[->, thick] (id_980170) --(end_reporting);
  \draw[->, thick] (id_980041) -- ++(10,0) node[pos=0, anchor=north west]{Yes} |- (end_reporting.east);
  \draw[-, thick] (id_980043) -- ++(10,0) node[pos=0, anchor=north west]{Yes};
  \draw[-, thick] (id_980046) -- ++(10,0) node[pos=0, anchor=north west]{Yes};
  \draw[-, thick] (id_980049) -- ++(10,0) node[pos=0, anchor=north west]{Yes};
  \draw[-, thick] (id_980051) -- ++(10,0) node[pos=0, anchor=north west]{Yes};
\end{tikzpicture}
\end{document}

PDFの生成手順

lualatex texファイルのベース名
横道: エラーが起きたときはXで終了

エラーの場合はエラーメッセージの後に?というプロンプトが出ます。 ? Enterで説明が出ます。X Enterで終了です。

出力例:

$ lualatex RESPONSE-980-CORRELATION
…(略)…
See the pgf package documentation for explanation.
Type  H <return>  for immediate help.
 ...

l.17   \draw[->, thick] (start) -- (id_980041x)
                                             ;
? H
This error message was generated by an \errmessage
command, so I can't give any explicit help.
Pretend that you're Hercule Poirot: Examine all clues,
and deduce the truth by order and method.
? ?
Type <return> to proceed, S to scroll future error messages,
R to run without stopping, Q to run quietly,
I to insert something, E to edit your file,
1 or ... or 9 to ignore the next 1 to 9 tokens of input,
H for help, X to quit.
? X
 1402 words of node memory still in use:
   22 hlist, 2 vlist, 1 rule, 2 disc, 2 local_par, 4 dir, 25 glue, 4 kern, 2 pe
nalty, 29 glyph, 67 attribute, 50 glue_spec, 67 attribute_list, 3 temp, 2 if_st
ack, 1 write, 38 pdf_literal, 2 pdf_colorstack nodes
   avail lists: 2:9,3:1,4:2,5:12,10:1

warning  (pdf backend): no pages of output.
Transcript written on RESPONSE-980-CORRELATION.log.

PDFからSVGの生成手順

pdftocairo -svg pdfファイル名

ファイル名の拡張子を.svgにしたファイルが生成されます。

「TikZの使い方」(2025-09-21 21:06 追記)

TikZの公式マニュアルはPGF/TikZ Manual - Complete Online Documentationです。 フッターのOfficial PDF versionでPDFもダウンロードできます。 このPDFは英語で書かれていて1323ページ(2025-09-21時点)と長大です。

TikZ - TeX Wikiからリンクされていた TeXについて | 壱大整域からダウンロードできる TikZの使い方というPDFは日本語で書かれていて111ページ(2025-09-21時点)です。

というわけで、まずはこちらをありがたく読むのが良さそうです。

余談

文書内の図として作るか図単体として作るか

TikZ - TeX Wiki図画のみの出力では以下のような書き方を紹介していました。

\documentclass{jlreq}
\usepackage{tikz}
\pgfrealjobname{RESPONSE-980-CORRELATION}
…(略)…

\begin{document}
\beginpgfgraphicnamed{RESPONSE-980-CORRELATION-fig1}
\begin{tikzpicture}
…(略)…
\end{tikzpicture}
\endpgfgraphicnamed
\end{document}

RESPONSE-980-CORRELATION-fig1だけコンパイルするには以下のようにします。

lualatex --jobname=RESPONSE-980-CORRELATION-fig1 RESPONSE-980-CORRELATION.tex

これ自体は期待通り動きました。一方で文書全体でコンパイルすると2ページになり、1ページ目はページ番号のみで、2ページ目はページからはみ出た状態になってしまいました。

そこで、Google Geminiに聞いて、文書クラスを以下のようにして図単体として作っています。

\documentclass[tikz, border=8pt]{standalone}

線を重ねて核と太くなるので重ねない

複数の菱形の判断(Decision)からlog_reportingへの線は当初は以下のように書いていました。

  \draw[->, thick] (id_980042)node[below, xshift=-200]{Yes} -- ++(-10,0) |- (log_reporting.west);
  \draw[->, thick] (id_980044)node[below, xshift=-250]{Yes} -- ++(-10,0) |- (log_reporting.west);

ですが、重なった箇所が太くなるので、以下のように2つ以降は交わるところまでだけを書くようにしました。

  \draw[->, thick] (id_980042)node[below, xshift=-200]{Yes} -- ++(-10,0) |- (log_reporting.west);
  \draw[-, thick] (id_980044)node[below, xshift=-250]{Yes} -- ++(-10,0);

判断から横に伸びる線は中心から描く

菱形の判断(Decision)は以下のような定義になっています。

  \tikzset{Decision/.style={diamond, draw, text centered, aspect=6,text width=10cm, minimum height=1.5cm}};

text widthに固定の長さを指定していますが、文字数が増えると自動的にサイズが大きくなりました。

そのため線の引き出しを以下のように左端からにしてしまうと、線の先のX軸の値がそろわなくなってしまいます。

  \draw[->, thick] (id_980042.west)node[below, xshift=-200]{Yes} -- ++(-10,0) |- (log_reporting.west);
  \draw[-, thick] (id_980044.west)node[below, xshift=-250]{Yes} -- ++(-10,0);

そのため、.westは省略して中心から引くようにしています。

判断からの線のYesラベルの配置はposとanchorを使うのが良い (2025-09-21 21:06更新)

前項のとおり、菱形の判断から横に伸びる線は判断の中心から引いているため、Yesのラベルを配置する際はxshiftに中心からの距離を指定する必要があります。

  \draw[->, thick] (id_980042.west)node[below, xshift=-200]{Yes} -- ++(-10,0) |- (log_reporting.west);
  \draw[-, thick] (id_980044.west)node[below, xshift=-250]{Yes} -- ++(-10,0);

実は単位がよくわかっていないのですが、試行錯誤して希望の配置になるような数値を指定しています。

判断が文字数に応じてサイズが変わるので、それに合わせてxshiftも調整する必要があります。

ここはもう少し良い方法を調べたいところです。

(↑xshiftの単位を省略したときのデフォルトはptでした。)

上記のTikZの使い方を読んで、線の横に書くYesやNoのラベルの配置はposとanchorを使うのが良いことが分かりました。

  \draw[->, thick] (id_980051.south) -- (log_reporting) node[pos=0, anchor=north west]{No};
  \draw[->, thick] (id_980042) -- ++(-10,0) node[pos=0, anchor=north east]{Yes} |- (log_reporting.west);

上記の「ソース:RESPONSE-980-CORRELATION.tex」の箇所もこの方式で書き換えたもので更新済みです。