PGF/TikZをオススメする記事


経緯

みなさん、TikZ好きですか?私は好きです。

資料作成の度にPowerPointやillustratorを開いて、慣れないマウス操作をしながら図を作成するなんて考えるだけでもゾッとします。(illustratorは使ったことがないですが...)

きっとQiitaにはTikZに関する知見が溢れて活発な議論が毎夜交わされているに違いな...

image.jpg

(PowerPointは398件、illustratorは427件、いずれも2018/10/3現在)

.............。

ということで、TikZについて書いていきます。


TikZとは


なにができるの?

日本をはじめ世界で大人気のTikZではありますが、なぜかWikipediaに日本語記事が無かったりするので、仕方なく英語のWikipedia先生に頼ります。


PGF/TikZ is a pair of languages for producing vector graphics from a geometric/algebraic description. PGF is a lower-level language, while TikZ is a set of higher-level macros that use PGF. (From: PGF/TikZ-Wikipedia)

ざっくり訳:PGF/TikZは幾何/代数的な情報からベクタ形式の画像を生成する言語である。PGFは低水準言語であり、TikZはPGFを利用するためのマクロである。


もっとざっくり言いますと、TikZではプログラミングのように図形と図形との位置関係などを指定してやることで、画像を生成することができます。具体的な例はTeXsample.netなどで公開されています。例えば、

polarizing-microscope.jpg

(Published 2010-03-17 | Author: Cyril Langlois from TeXsample.net)

夢が広がりますね。TeXsample.netには、これ以外にも素晴らしい作品が100点以上公開されているので、興味があれば覗いてみると良いでしょう。


なんでこんなの使わなくちゃいけないの?

個人的に考える、TikZを使うことによるメリットとデメリットを(主にPowerPointと比較して)挙げてみます。


メリット


  • 図の変更に素早く対応できる。

TikZでは図形と図形の間の間隔などをパラメータで指定するため、例えばこれらの値を変更する際には数字を変えるだけで良い、ということが多いです。

PowerPointを使うときのように「範囲選択して全体を右に...ああ、選択範囲間違えてた、<C-z>押してもう一回やり直して...よし、あとはこれらの間の矢印の長さも調節して......」といった煩雑な作業は必要ありません。


  • テキストエディタのみで編集できる。

TikZで図を描くのに必要なことは、重い画像編集ソフトウェアを開くことではなく、ただ使い慣れたエディタを開くだけです。

久しぶりに開いたPowerPointで、Microsoft AutoUpdateによる大量の更新ファイルのインストールに時間を取られることもありません。

さらに素晴らしいことに、VimmerもEmacserも秀丸ユーザーも、誰もが自身の使い慣れたエディタで作業することができます。

もしあなたがVisual Studio Codeなどのエディタで日常的にTeXファイルをコンパイルするならば、その拡張機能を使ってリアルタイムに図を見ながら編集できるでしょう。(Vimなどでもできるようですが、私は使ったことがないです。)

tikz_preview.gif



  • \foreach文等を利用して、大量の画像を一気に(重くならずに)描くことができる

Powerポイントなどで、単純な図形が単純に並ぶだけの図を描くことは意外と骨が折れるものです。

コピー&ペーストを繰り返すだけ...ではあるのですが、その手間もバカになりませんし、ペーストする度にいちいち場所を合わせて...なんてやってるうちに驚くほど時間が経ってしまうことがあります。

その上、大量の図形が並んだPowerPointファイルはだんだんと重くなり、あなたが苦労して作ったファイルは重すぎて二度と開かないなんてこともありえます。

TikZでは\foreachと言う、いわゆるループ記法を用いることができる場合には単純な図形を大量に、簡潔に記述できます。

スクリプトも生成される画像も、常識的な利用では重くなることはまずありません。


デメリット


  • 一枚の画像にかかるコストが大きくなりがち

TikZでは図形内のオブジェクトの位置などを逐一指定する必要があるので、私の感覚では1枚目の図を作るコストは、PowerPoint等を利用する場合に比べて大きくなりがちです。

メリットにも書いたように変更のコストは小さいので、何度も修正が入るような場合はコストが逆転することもあるのですが、最初に作った図のまま最後まで変更されないことも往々にしてあります。


  • 相対的な図形の配置に強い反面、絶対的な配置は難しい

TikZにおける図形の配置は、他の図形からの相対的な位置による指定と、絶対的な座標による指定の2通りがあります。

このうち絶対的な座標によるものは、個人的にはやや使いづらいと考えています。というのも、自分の持っているイメージから「この図形は(◯,◯)に配置するべきだ」ということがわかる人は一部の特殊な人間を除いてそう多くはないと考えますし、また、他の図形との位置関係を考える上で、場合によっては複雑な計算をする必要さえあるためです。

そのため、例えば上の例に挙げたようないわゆる「お絵かき」のような図の作成には実はあまり向いておらず、ノード、エッジからなるグラフのような図形の作成に力を発揮すると考えています。(この辺りには賛否あるかと思われます。)

このような「苦手な図の作成」においてはTikZはあまり力を発揮することはできないでしょう。


どうやって使うの?

LaTeXユーザーであれば、デフォルトですでにPGF/TikZが利用できるはずです。試しにTeXsample.netからコードを引っ張ってきて、お手持ちの環境でタイプセットしてみましょう。

手持ちのPCにLaTeX環境がない場合は、LeTeXをインストールする必要があります。TeX入手法-TeX Wikiのページなどを参考にインストールしてみましょう。


その他参考にすると良いページ

TikZ-TeX Wiki

イントロから基本的、中級的な使い方までかなりいろんなことが書いてあります。(というかこれ見ればこのページは見なくてもいいんじゃ...)

beamerのためのTikZ-Tasuku Soma's webpage

beamer(TeXでプレゼンテーションを作成するためのパッケージ)のためのと書かれてはいますが、TikZに関してもまとまった内容がすっきりと説明されています。とりあえず手っ取り早く図が描きたいんじゃという人向け

LaTeX Stack Exchange

LaTeX版Stack Overflowです。大抵なにかに詰まったときはここを検索すれば答えがあります。

その他、コマンドラインからtexdoc tikzを実行して表示されるpdf文書も、例が豊富です。こちらのページで紹介されていて初めて知ったのですがtexdoc visualtikzで見られるpdfも有用です。それらのpdfを見ればこのページは見なくてもいいのでは...?


TikZ入門

気を取り直して実際のTikZ記法について紹介します。入門的、かつTikZを使うメリットが分かる例として、このエントリでは次のような完全二部グラフ(complete bipartite graph)の図を作ることを目標とします。TikZについての知識は要求しませんが、LaTeXは触ったことあるよ、という人を想定しています。

bipartite.jpg


node

nodeとは節点の意味です。ネットワークやグラフ理論などを扱う方には馴染みの深い言葉でしょうか。

TikZでは、「図形やテキストボックス」といったオブジェクトを指します。試しにnodeを配置して見ましょう。


sample.tex

\documentclass[dvipdfmx]{standalone}

\usepackage{tikz}

\begin{document}
\begin{tikzpicture}
\node (name) {label};
\end{tikzpicture}
\end{document}


sample.jpg

\documentcalssにはstandaloneを設定し、画像のみが出力されるようにします。

pLaTeXを使ってdvipdfmxでpdfを出力する場合、オプションにdvipdfmxを指定しておきましょう。こうしないと画像が正しくトリミングされません。

\usepackage{tikz}でTikZパッケージを読み込みます。これがないと始まりません。

TikZを用いて図を作成する場合にはtikzpicture環境を利用します。この中にTikZのコマンドを打っていきますが、その際、各文の終わりには;をつける必要があります。(通常のLaTeX記法には無い仕様なので、注意しましょう。)

各nodeは、普通名前ラベルという二つの属性を持っています。ラベルは図として出力される際に表示される内容、名前はそのノードを参照する場合に用いられる名前です。

labelには通常のテキストのほか$で囲まれた数式なども使えます。また、名前には英数半角以外にも(推奨はしませんが)半角スペースや2バイト文字も用いることができます。(半角スペースの方は割と普通に使われていたりもしますが...)

また、この他にオプションとして様々な属性を指定できます。

試しにこれらを盛り込むと、例えば次のようにできます。


sample.tex

\documentclass[dvipdfmx]{standalone}

\usepackage{tikz}

\begin{document}
\begin{tikzpicture}
\node[draw, circle, fill=blue!20] (これは ノードです) {$\int_{0}^{\infty} f(x) dx$};
\end{tikzpicture}
\end{document}


sample.jpg

オプションの細かい説明は省きますが一点だけ、drawオプションを指定しないと枠線が表示されないことに注意しましょう。

この他にも多種多様なオプションがあるので、ドキュメント等で確認してください。


nodeの配置

nodeの配置には、絶対座標による配置と相対座標による配置があります。まず、絶対座標による配置を見て見ましょう。


sample.tex

\documentclass[dvipdfmx]{standalone}

\usepackage{tikz}

\begin{document}
\begin{tikzpicture}
\node[draw, circle] (A) at (0cm, 0cm) {A};
\node[draw, circle] (B) at (0cm, -1cm) {B};
\end{tikzpicture}
\end{document}


sample.jpg

絶対座標による配置では、名前の後にat ([x], [y])の形で座標を指定します。分かりやすい反面、どの座標にどのnodeがいるかなどを割と見失いやすいので、注意が必要です。

相対座標による配置は次のようになります。


sample.tex

\documentclass[dvipdfmx]{standalone}

\usepackage{tikz}
\usetikzlibrary{positioning}

\begin{document}
\begin{tikzpicture}
\node[draw, circle] (A) {A};
\node[draw, circle, below=1cm of A] (B) {B};
\end{tikzpicture}
\end{document}


sample.jpg

相対座標を用いるには、positioningというtikzlibraryを利用し、[below/above/left/right]=[length] of [name]の形で指定します。

また、[below/above][right/left]は組み合わせることができ、例えば

above right=1cm and 2cm of B

のような記法も可能です。

さて、二つの座標の指定方法でどちらもBのnodeをAのnodeの1cm下に配置したはずですが、相対座標の例の方がAとBの間隔が広く見えています。これは、絶対座標モードでは各nodeの中心を指定の座標に配置するのに対し、相対座標モードでは2つのnodeの上端と下端の距離が1cmになるように調整されているからです。

sample.jpg

これを回避するには、相対座標モードでどこからどこまでの距離が1cmなのかを正確に指定する必要があります。今回は「Aの中心からBの中心まで」が1cmにしたいとすると、次にようになります。


sample.tex

\documentclass[dvipdfmx]{standalone}

\usepackage{tikz}
\usetikzlibrary{positioning}

\begin{document}
\begin{tikzpicture}
\node[draw, circle] (A) {A};
\node[draw, circle, below=1cm of A.center, anchor=center] (B) {B};
\end{tikzpicture}
\end{document}


sample.jpg

belowオプションの中でどこからであるところのA.centerを明確にし、また、受け手側のBでは中心を配置する、というのをanchor=[position](錨の意味)で指定します。

注意点として、これらのオプションで指定されるのはcenterまたは[north/south/east/west]等の方角を表す単語とその組み合わせです。(node上端をnorthとみなし、そこからの方位で指定する)

例えばAの右下からBの左上の距離を1cmにしたい場合には

\node[below=1cm of A.south east, anchor=north west] ...

のようにします。


(閑話休題)冗長性の排除

どの分野であっても冗長なコードというものは美しくありませんし、また変更に際して多大なコストを要します。これではTikZを使う意義もほぼなくなってしまうでしょう。

これまでの記法では、nodeの配置はできますが、それは非常に冗長な表現となってしまいます。

以下ではこのような冗長さを排除する記法を紹介します。

まず、次のような3つのnodeが配置された図を考えます。


sample.tex

\documentclass[dvipdfmx]{standalone}

\usepackage{tikz}
\usetikzlibrary{positioning}

\begin{document}
\begin{tikzpicture}
\node[draw, circle] (A) {A};
\node[draw, circle, right=1cm of A] (B) {B};
\node[draw, circle, right=1cm of B] (C) {C};
\end{tikzpicture}
\end{document}


sample.jpg

このコードは一見シンプルに見えますが、次のような問題をはらんでいます。


  • 例えば全てのnodeを青色で塗りつぶしたい、といったときに各nodeのオプションを全て変更しなければならない

  • nodeの間隔を全て2cmにしたい、というときに2箇所を変更する必要がある。

これらの問題は、今は小さな問題かもしれませんが、nodeの数が増えるにしたがって深刻化します。


  • 全てのnodeは同じスタイルに統一する

  • 各nodeは等間隔に配置する

という前提条件の下では、これらの冗長性は排除されている方が望ましいでしょう。

そのため、例えば次のようにコードを改変します。


sample.tex

\documentclass[dvipdfmx]{standalone}

\usepackage{tikz}
\usetikzlibrary{positioning}

\begin{document}
\begin{tikzpicture}[nodestyle/.style={draw, circle}]
\def\horizontalnodesep{1cm}

\node[nodestyle] (A) {A};
\node[nodestyle, right=\horizontalnodesep of A] (B) {B};
\node[nodestyle, right=\horizontalnodesep of B] (C) {C};
\end{tikzpicture}
\end{document}


sample.jpg

まず、tikzpicture環境のオプションで、いくつかのオプションをまとめたstyleを定義します。全てのnodeでこのstyleを参照することにより、nodeのスタイルの変更が1箇所の変更のみで容易に行えるようになりました。

また、\def\horizontalnodesep{1cm}として横方向のnodeの距離を指定し、これを参照することで距離に関する冗長性も排除できます。

なお、この\defコマンドはTikZのコマンドではないため、行末に;がつかないことに注意してください。

(補足に見せかけた蛇足)

このあたりで薄々感づいている人もおられるかもしれませんが、LaTeXにおける標準的なコマンドの命名規則はCamelでもsnakeでもなく全て英小文字で繋げて書くという頭のおかしいものが一般的なようです。(そのあたりの歴史やら事情やらは知っている方がいたら教えて欲しいのですが...)

しかしながらこれらが別に禁止されているという訳ではないですし、アンダーバーも2バイト文字も普通に名前に使えますので、自分の使いやすい命名規則で書くのが幸せになれると思います。

チームでLaTeX文書開発、なんて機会も滅多にないでしょうし。


draw

指定された点をつなぐような線を描きます。オプションで->を指定すると矢印、thickを指定するとちょっと太い線が描けます。

nodeとnodeをつなぐときには、デフォルトでは最短距離っぽいところをいい感じに結ぶように線を繋ぎます。


sample.tex

\documentclass[dvipdfmx]{standalone}

\usepackage{tikz}
\usetikzlibrary{positioning}

\begin{document}
\begin{tikzpicture}[
nodestyle/.style={
draw,
rectangle,
minimum height=1cm,
minimum width=1cm,
}
]
\node[nodestyle, anchor=north] (A) at (0cm, 0cm) {A};
\node[nodestyle, anchor=south] (B) at (2cm, 0cm) {B};

\draw (A) to (B);
\draw[red] (A.north east) -- (B.south west);
\draw[thick, <->] (A.south east) to (B.north west);
\end{tikzpicture}
\end{document}


sample.jpg

細かい説明は省きますが、この他にも座標と座標を結んだり、曲がる線を描くなど、様々なことができます。


coordinate

座標に名前をつけて定義することができます。間隔的には「大きさとラベルの存在しないnode」のようなものだと思って大きくは違わないと思います。また、calcというtikzlibraryを読み込むことで簡単な計算もできます。


sample.tex

\documentclass[dvipdfmx]{standalone}

\usepackage{tikz}
\usetikzlibrary{calc}

\begin{document}
\begin{tikzpicture}[
nodestyle/.style={
fill=black,
circle,
minimum height=4pt,
inner sep=0pt,
}
]
\coordinate (c1) at (0cm, 0cm);
\coordinate (c2) at ($(c1)+(1cm, 0cm)$); % calculation with calc library

\node[nodestyle] (A) at (c1) [label=c1] {};
\node[nodestyle] (B) at (c2) [label=c2] {};
\end{tikzpicture}
\end{document}


sample.jpg

こちらも詳細は省きます。基本的な記法は\coordinate ([name]) at ([position]);です。


完全二部グラフの図の作成

ここまでくれば、完全に初心者は卒業です。最後に完全二部グラフの図を作ってみましょう。まずは、documentclassと使用するパッケージ、ライブラリを宣言しましょう。

\documentclass[dvipdfmx]{standalone}

\usepackage{tikz}
\usetikzlibrary{
positioning,
calc,
}

次に、本文中で、まず使うパラメータを宣言します。

\begin{document}

\begin{tikzpicture}
\def\leftsidenodes{a, b, c, d, e}
\def\rightsidenodes{A, B, C, D}
\def\horizontalspacing{4cm} % center to center distance
\def\verticalspacing{1.5cm} % center to center distance
\end{tikzpicture}
\end{document}

左側にはa, b, c, d, eの5つのnode、右側にはA, B, C, Dの4つのnodeを配置し、それらの間の間隔は横方向が4cm、縦方向が1.5cmとします。

次に、これらのnodeを設置するのですが...最初のノードはどこに配置するべきでしょうか?

sample.jpg

図から分かるように、一番左上のnodeはセンターラインから

$\frac{\mathsf{[nodecount] - 1}}{2} \times \mathsf{[verticalspacing]}$

だけ上に配置すれば良さそうです。\verticalspacingは指定していましたが、nodecountに関しては\[left/right]sidenodesから求める必要がありそうです。

そのためのコードをプリアンブルに書いておきましょう


preamble

\newcounter{nodecount}

\providecommand{\nodecounter}[1]{
\setcounter{nodecount}{-1}
\foreach \iter in #1{
\stepcounter{nodecount}
}
}

\nodecounter\[left/right]sidenodesを引数にしてnodecountというカウンターに(ノードの数-1)を格納します。このカウンタの値は\thenodecountというコマンドで参照することができます。

それではいよいよノードを順に配置していきます。\foreach文を使って一つずつだんだんと座標(pos)を下にしながら描画しています。

\nodecounter{\leftsidenodes}

\coordinate (pos) at (0, \verticalspacing * \thenodecount * .5);
\foreach \currentnode in \leftsidenodes {
\node[below=0 of pos, anchor=center] (\currentnode) {};
\coordinate (pos) at ($(pos)+(0, -\verticalspacing)$);
}
\nodecounter{\rightsidenodes}
\coordinate (pos) at (\horizontalspacing, \verticalspacing * \thenodecount * .5);
\foreach \currentnode in \rightsidenodes {
\node[below=0 of pos, anchor=center] (\currentnode) {};
\coordinate (pos) at ($(pos)+(0, -\verticalspacing)$);
}

またこれらの間にエッジを描画していきます。

\foreach \leftsidenode in \leftsidenodes{

\foreach \rightsidenode in \rightsidenodes{
\draw (\leftsidenode) to (\rightsidenode);
}
}

最後に、ノードとエッジのstyleを決めてやれば完成です。

最終的なコードは次のようになります。


bipartite.tex

\documentclass[dvipdfmx]{standalone}

\usepackage{tikz}
\usetikzlibrary{
positioning,
calc,
}

\newcounter{nodecount}
\providecommand{\nodecounter}[1]{
\setcounter{nodecount}{-1}
\foreach \iter in #1{
\stepcounter{nodecount}
}
}

\begin{document}
\begin{tikzpicture}[
graphnode/.style={
draw,
thick,
circle,
minimum height=1cm,
},
graphedge/.style={
thick,
}
]
\def\leftsidenodes{a, b, c, d, e}
\def\rightsidenodes{A, B, C, D}
\def\horizontalspacing{4cm} % center to center distance
\def\verticalspacing{1.5cm} % center to center distance

\nodecounter{\leftsidenodes}
\coordinate (pos) at (0, \verticalspacing * \thenodecount * .5);
\foreach \currentnode in \leftsidenodes {
\node[graphnode, below=0 of pos, anchor=center] (\currentnode) {\currentnode};
\coordinate (pos) at ($(pos)+(0, -\verticalspacing)$);
}
\nodecounter{\rightsidenodes}
\coordinate (pos) at (\horizontalspacing, \verticalspacing * \thenodecount * .5);
\foreach \currentnode in \rightsidenodes {
\node[graphnode, below=0 of pos, anchor=center] (\currentnode) {\currentnode};
\coordinate (pos) at ($(pos)+(0, -\verticalspacing)$);
}

\foreach \leftsidenode in \leftsidenodes{
\foreach \rightsidenode in \rightsidenodes{
\draw[graphedge] (\leftsidenode) to (\rightsidenode);
}
}
\end{tikzpicture}
\end{document}


bipartite.jpg


おわりに

以上で、PGF/TikZの誰得な導入を終わります。

最後の方力尽きて説明が雑になっていますが、まあだいたい書きたいことは書けたかな、という感じです。最終章とかもはや初心者に理解させる気ないよね

最初にも書いた通り、TikZは個人的にはbeamerと並んで大好きな技術なのですが、これまたbearmer同様日本では(海外でも?)そこまで普及しているようには見えません。

もちろんLaTeXを使う人自体限られた領域のオタクエキスパートというのもあるのでしょうが、それにしても少ないのは「和文の情報の少なさ」にあるのではと思っています。

何か困ったときに情報を見つけようとするとどうしても英語の文献を当たらなければならないことが多い、というのは、初心者にとっては大きなハードルとなるのではないでしょうか。

そのため、本エントリでは、なるべく私自身が詰まったところとして、nodeの相対配置、絶対配置のあたりを重点的に書きました。

これを機に、少しでもTikZを使う人が増えれば...と思います。そしてPowerPointのエントリ数を上回ればと思います