LoginSignup
5
4

More than 5 years have passed since last update.

TikZのdatavisualizationライブラリで凝った折れ線グラフを描く

Last updated at Posted at 2016-08-04

この記事この記事この記事の続き.

今まで書いてきたこと+αで, (見やすいかどうかは別として) 以下のような折れ線グラフを描けるようになりたい.

目標:
csv2pdf-2-000016.png

使ったデータセットは長くなるので載せない (ちなみにこの図は簡単な数値積分の誤差をプロットしたものである).
このグラフを得るための全体的なソースは最後に書いておくことにして, step by step でやり方を書いていく.

なお, 他の記事もそうだが, 特にこの記事の内容は, gnuplot でやった方がはるかに楽であると思う.
TikZ でやるメリットは, フォントを本文と合わせられることとグラフ以外の部分での自由度の高さくらいだと思う.
つまり, この記事は, すべてを LaTeX でやってしまいたい人向けである.

他の記事へのリンクも書いておく.

  • 1つ目: 基本的なプロットについて.
  • 2つ目: 軸の編集について.
  • 3つ目: より細かい装飾について.
  • 4つ目: この記事. 応用編. 凝った折れ線グラフを描く.

プリアンブル

プリアンブルは以下のようにする.

\documentclass{ltjsarticle}

% TikZ と必要なライブラリの読み込み
\usepackage{tikz}
\usetikzlibrary{datavisualization,plotmarks,fpu,calc}

% 色の定義
\definecolor{color1}{cmyk}{1,0,1,0}
\colorlet{color2}{blue}
\colorlet{color3}{red}

% 必要なパッケージ
\usepackage{amsmath,amssymb}

% y軸表示用のマクロ
\newcommand{\typesetten}[1]{%
\pgfmathfloatparsenumber{#1}%
\pgfmathfloatlogten{\pgfmathresult}%
\pgfmathfloattofixed{\pgfmathresult}%
\pgfmathroundto{\pgfmathresult} $10^{\pgfmathresult}$
}

% 図だけのpdfファイルを出力
\usepackage[active,tightpage]{preview}
\PreviewEnvironment{tikzpicture}

\begin{document}
...
\end{document}

datavisualization 以外のライブラリを読み込む理由と, \newcommand で何をしているのかは, 後で説明する.
それ以外は,

  • ltjsarticle クラスなのは, 日本語を入力する場合に備えて
  • blueなどの簡単な色に新しく名前をつけているのは, 後で変更することになった時のため
  • \usepackage{amsmath,amssymb} は数学記号をマーカーに使うため
  • \usepackage[active,tightpage]{preview} は図だけのpdfファイルを出力するため

である.

以下は, tikzpicture 環境だけ書く.

とりあえずプロット

\begin{tikzpicture}
    % 可視化に関する設定
    \datavisualization[
        % 最低限の情報
        scientific axes, 
        visualize as line/.list={data1, data2, data3, data4, data5, data6}, 
        % 両軸共通のスタイルの指定
        all axes={
            logarithmic,
        },
    ]

        % データのプロット
        data[set=data1, headline={x, y}, read from file=data1.csv]
        data[set=data2, headline={x, y}, read from file=data2.csv]
        data[set=data3, headline={x, y}, read from file=data3.csv]
        data[set=data4, headline={x, y}, read from file=data4.csv]
        data[set=data5, headline={x, y}, read from file=data5.csv]
        data[set=data6, headline={x, y}, read from file=data6.csv];
\end{tikzpicture}

出力:
csv2pdf-4-000001.png

これにいろいろとオプションを付け加えていって, 最初の画像のようにする.

さまざまなマーカーを付ける

以前の記事にも書いたが, TikZ のデフォルトで使えるマーカーは, *: ●, +: +, x: エックス, ×印, の3種類であり, 記号を増やしたければ plotmarks ライブラリを読み込めばよいのであった (プリアンブルにはすでに書いてあるが).

\usetikzlibrary{plotmarks}

使える記号はマニュアルに書いてあるが, $\boxtimes$ のような記号は用意されていない.
TikZ で頑張って描いてもいいのだが, 幸い plotmarks ライブラリには文字をマーカーに使う機能が備わっているので, ここでは amssymb パッケージの \boxtimes を使うことにする.

目標の図は, 以下のようにマーカーが指定されている.
なお, style={mark=square*,} のようにカンマで終わっているのは, 後でいろいろ追記することを見越しているため. このようなカンマはコンパイル時には無視されるので, 出力に影響はない.

\begin{tikzpicture}
    % 可視化に関する設定
    \datavisualization[
        ...
        data1={
            style={mark=square*,}, 
        },
        data2={
            style={mark=square,},
        },
        data3={
            style={
                mark=text, 
                text mark={$\boxtimes$}, 
                text mark as node=true, 
                text mark style={scale=0.6},
            }, 
        },
        data4={
            style={mark=*,}, 
        },
        data5={
            style={mark=o,}, 
        },
        data6={
            style={mark=otimes,}, 
        },
        ...
    ]

        % データのプロット
        ...
\end{tikzpicture}

出力:
csv2pdf-4-000002.png

文字列をマーカーにする際の設定

data3

style={
    mark=text, 
    text mark={$\boxtimes$}, 
    text mark as node=true, 
    text mark style={scale=0.6},
}

の部分を簡単に説明しておく.

  • mark=text でマーカーに文字列を使用することを宣言.
  • text mark={$\boxtimes$} でマーカーにする文字列を定義 (これを指定しないと p という文字がマーカーになる).
  • text mark as node=true を指定すると, マーカーのスタイリングに TikZ の \node コマンドのオプションが使えるようになる.
  • text mark style={scale=0.6}\node コマンドのようにスタイリングする.

scale=0.6 を指定したのは, そのままだと大きかったから.

出力結果を見ると, 図がかなり潰れてしまっている.
色を付けたり, ($\boxtimes$ 以外の) マーカーを小さくしたりして, もう少しマシな見た目にしたい.

複数の線に同じスタイリング

目標の図の data2 と data5 は, どちらも

  • 細線
  • 青線 (color2)
  • 破線
  • マーカーの色も青 (color2)
  • マーカーのサイズはデフォルトの0.9倍の大きさ

という設定にしたい.
そのためには, tikzpicture 環境の頭で自前のスタイルを定義しておいて, それを読み込ませれば楽である.
例えば上述のスタイルに style2 という名前をつけるならば, 以下のようになる.

\begin{tikzpicture}[
    % スタイルの定義
    style2/.style={
        thin, color2, dashed,
        mark options={color2, scale=0.9}, 
    },
]

    % 可視化に関する設定
    \datavisualization[
        ...
        data2={
            style={style2, mark=square,}, 
        },
        ...
        data5={
            style={style2, mark=o,}, 
        },
        ...
    ]

        % データのプロット
        ...
\end{tikzpicture}

他の線も同様で, 目標の画像では以下のようなスタイルを用いている.

\begin{tikzpicture}[
    % スタイルの定義
    style1/.style={
        thin, color1, 
        mark options={color1, scale=0.9},
    },
    style2/.style={
        thin, color2, dashed,
        mark options={color2, scale=0.9}, 
    },
    style3/.style={
        thin, color3, dash dot,
        mark options={color3, scale=0.9, very thin},
    },
]

    % 可視化に関する設定
    \datavisualization[
        ...
        % 各線に対するスタイルなどの指定
        data1={
            style={style1, mark=square*,}, 
        },
        data2={
            style={style2, mark=square,}, 
        },
        data3={
            style={
                style3, mark=text, 
                text mark={$\boxtimes$}, 
                text mark as node=true, 
                text mark style={scale=0.6},
                }, 
        },
        data4={
            style={style1, mark=*,}, 
        },
        data5={
            style={style2, mark=o,}, 
        },
        data6={
            style={style3, mark=otimes,}, 
        },
        ...
    ]

        % データのプロット
        ...
\end{tikzpicture}

出力:
csv2pdf-4-000004.png

style3mark optionsvery thin が入っているのは, マーカーの $\otimes$ を小さくするとそのままでは潰れてしまうため.

使える線種

使える線種をメモしておく (これは TikZ の機能).

オプション 線種
dotted 点線
dashed 破線
dash dot 一点鎖線
dash dot dot 二点鎖線

間隔を狭くしたり広くしたりするには, densely dottedloosely dashed などとすれば良い.
より細かい指定はマニュアル参照.

軸の見た目を変える

これは以前の記事にも書いたが, 以下のように min valuelength などを指定すれば良い.

\begin{tikzpicture}[
    % スタイルの定義
    ...
]

    % 可視化に関する設定
    \datavisualization[
        ...
        % 両軸共通のスタイルの指定
        all axes={
            logarithmic, 
            ticks={minor steps between steps=8},
            length=7cm,
        },
        % x軸のスタイルの指定
        x axis={
            min value=0.03, max value=1, 
            label={mesh size},
        },
        % y軸のスタイルの指定
        y axis={
            min value=0.0000000000001, max value=0.1, 
            label={error},
        },
    ]

        % データのプロット
        ...
\end{tikzpicture}

出力:
csv2pdf-4-000005.png

凡例をつける

凡例を表示するには, 各データセットの設定のところに label in legend={text=凡例ラベル} を追記すれば良い.
また, 凡例の表示位置などの設定は, \datavisualization コマンド内で legend={...}, とすれば良い.

\begin{tikzpicture}[
    % スタイルの定義
    ...
]

    % 可視化に関する設定
    \datavisualization[
        ...
        data1={
            style={style1, mark=square*,}, 
            label in legend={text=data1},
        },
        data2={
            style={style2, mark=square,}, 
            label in legend={text=data2},
        },
        ...(以下同様)

        % 凡例の設定
        legend={north east outside},
    ]

        % データのプロット
        ...
\end{tikzpicture}

出力:
csv2pdf-4-000006.png

legend={north east outside}, は, プロットの外側の右上 (北東) に表示せよ, という意味.
他には south east inside north outside などがある. 意味はそのまま.

凡例の見た目もいろいろとカスタマイズできるようだが, 個人的にはこれで満足しているので, 必要な場合はマニュアル参照.

傾きのサンプルを入れる

これは info{...} で好きなようにやれば良い.
calc ライブラリを読み込んでおくと便利である.

\usetikzlibrary{calc}

目標の図のようにするには, 例えば以下のようにすれば良い.
なお, プロット内の座標へのアクセスの仕方はこの記事を参照.

\begin{tikzpicture}[
    % すべてのノードに対するスタイルの指定
    every node/.style={font=\footnotesize},
    % スタイルの定義
    ...
]

    % 可視化に関する設定
    \datavisualization[
        ...
    ]

        % データのプロット
        ...
        data[set=data6, headline={x, y}, read from file=data6.csv]

        % その他の情報
        info{
            \coordinate (O) at (visualization cs:x=0.125, y=1e-12);
            \coordinate (S) at (visualization cs:x=0.5, y=1e-12);
            \coordinate (N) at (visualization cs:x=0.5, y=4e-12);
            \draw[very thin] (O) -- (S) -- ($(S)!6!(N)$) -- cycle;
            \draw[very thin] (O) -- (N);
            \draw[very thin] (O) -- ($(S)!2!(N)$);
            \draw[very thin] (O) -- ($(S)!4!(N)$);
            \node[below] at ($(O)!.5!(S)$) {$1$};
            \node[right] at (N) {$1$};
            \node[right] at ($(S)!2!(N)$) {$2$};
            \node[right] at ($(S)!4!(N)$) {$4$};
            \node[right] at ($(S)!6!(N)$) {$6$};
        };
\end{tikzpicture}

出力:
csv2pdf-4-000007.png

every node/.style={font=\footnotesize} を冒頭に追加したのは, 表示する文字をすべて小さくしたかったから.

目盛りの数字の見た目を変える

y軸を, $10^{-1}, 10^{-2},...$ という表記にしたい.
gnuplot なら set format y "10^{%L}" とたったの1行で済むのだが, これを datavisualization ライブラリで実現するのにめちゃめちゃ苦労した.

結論から言うと, 以下のようにすれば良い.

まず, TikZ の fpu ライブラリ (fpu = floating point unit) を読み込む (最初に書いたプリアンブルでは読み込まれている).

\usetikzlibrary{fpu}

その上で, 以下のコマンドを定義する (これが最初のプリアンブルに書いた謎のコマンド).

\newcommand{\typesetten}[1]{%
\pgfmathfloatparsenumber{#1}%
\pgfmathfloatlogten{\pgfmathresult}%
\pgfmathfloattofixed{\pgfmathresult}%
\pgfmathroundto{\pgfmathresult} $10^{\pgfmathresult}$
}

そして, y axis={...} の中に次を追記する.

ticks={tick typesetter/.code={\typesetten{##1}}}

以上で最初にあげた目標の図が完成するので, 出力結果は載せない.

コマンドの説明

以下では先の追記によって何をしているかを簡単に説明する.

  • fpu ライブラリは, 浮動小数点演算をするためのライブラリである. 多分.
  • 自前の \typesetten コマンドで, 入力値の常用対数をとって整数値に丸めて, $10$ の肩に乗せている.
  • tick typesetter/.code={\typesetten{##1}} によって, \typesetten コマンドの引数にy軸目盛りの数字を$a \cdot 10^m$ の形で代入する. 多分.

例えば, $10^{-9}$ に対応する目盛りを表示するために, \typesetten{1e-9} というコマンドが実行されているのだと思う. 多分.

\typesetten コマンドの中身を説明する.

  • \pgfmathresult: 直前の計算結果を参照する.
  • \pgfmathfloatparsenumber{}: 入力値を浮動小数点表示に変換する.
  • \pgfmathfloatlogten{}: 浮動小数点数で表示された入力値の常用対数を取る.
  • \pgfmathfloattofixed{}: 浮動小数点数を入力すると, 固定小数点表示に変換する.
  • \pgfmathroundto{}: 入力値を整数に丸める. 四捨五入?
  • $10^{\pgfmathresult}$: 以上の計算結果を $10$ の肩に乗せて表示する.

なぜこんなにも回りくどいことをしているかというと, TikZ の (というか PGF の) 数値計算機能がショボいからである.

例えば, 1e-9 を $10^{-9}$ と表示するためには, 指数 -9 を取り出せば良くて, そのためには常用対数を取ればよい.
そして, TikZ (PGF) には数値計算をする機能 (pgfmath パッケージが自動で読み込まれる) が備わっていて, 常用対数を計算するには \pgfmathlogten というコマンドを使えばいいのだが, fpu ライブラリ無しで次のようにするとゼロの対数など計算出来ないぞというエラーが出る.

\pgfmathlogten{1e-9} \pgfmathresult

それどころか, 入力値が 1e-5 であっても同じエラーになる.
TikZ (PGF) では扱える数字の範囲が狭すぎて, $10^{-5}$ は $0$ であると解釈されてしまうのである. ショボすぎ.

これを解決するには fixedpointarithmetic ライブラリか fpu ライブラリを使えばよいのだが, 前者と datavisualization パッケージとの相性が悪いっぽい (一緒に使うとエラーになる) ので, 後者を使うことにした.
fpu ライブラリを用いることで, 上のような計算ができるようになる. 詳細はマニュアル参照.

一応, 各ステップでの計算結果を表示させると, 次のようになっている.

\pgfmathfloatparsenumber{1e-9} \pgfmathresult,
\pgfmathfloatlogten{\pgfmathresult} \pgfmathresult,
\pgfmathfloattofixed{\pgfmathresult} \pgfmathresult,
\pgfmathroundto{\pgfmathresult} \pgfmathresult.

出力:
csv2pdf-2-000018.png

こうしてみると, 常用対数の計算精度がかなり悪いことに気づく. 丸めを入れているのはそのせいである.

最終的なソースコード

\documentclass{ltjsarticle}

% TikZ と必要なライブラリの読み込み
\usepackage{tikz}
\usetikzlibrary{datavisualization,plotmarks,fpu,calc}

% 色の定義
\definecolor{color1}{cmyk}{1,0,1,0}
\colorlet{color2}{blue}
\colorlet{color3}{red}

% 必要なパッケージ
\usepackage{amsmath,amssymb}

% y軸表示用のマクロ
\newcommand{\typesetten}[1]{%
\pgfmathfloatparsenumber{#1}%
\pgfmathfloatlogten{\pgfmathresult}%
\pgfmathfloattofixed{\pgfmathresult}%
\pgfmathroundto{\pgfmathresult} $10^{\pgfmathresult}$
}

% 図だけのpdfファイルを出力
\usepackage[active,tightpage]{preview}
\PreviewEnvironment{tikzpicture}

\begin{document}

\begin{tikzpicture}[
    % すべてのノードに対するスタイルの指定
    every node/.style={font=\footnotesize},
    % スタイルの定義
    style1/.style={
        thin, color1, 
        mark options={color1, scale=0.9},
    },
    style2/.style={
        thin, color2, dashed,
        mark options={color2, scale=0.9}, 
    },
    style3/.style={
        thin, color3, dash dot,
        mark options={color3, scale=0.9, very thin},
    },
]

    % 可視化に関する設定
    \datavisualization[
        % 最低限の情報
        scientific axes, 
        visualize as line/.list={data1, data2, data3, data4, data5, data6}, 
        % 各線に対するスタイルなどの指定
        data1={
            style={style1, mark=square*,}, 
            label in legend={text=data1},
        },
        data2={
            style={style2, mark=square,}, 
            label in legend={text=data2},
        },
        data3={
            style={
                style3, mark=text, 
                text mark={$\boxtimes$}, 
                text mark as node=true, 
                text mark style={scale=0.6},
                }, 
            label in legend={text=data3},
        },
        data4={
            style={style1, mark=*,}, 
            label in legend={text=data4},
        },
        data5={
            style={style2, mark=o,}, 
            label in legend={text=data5},
        },
        data6={
            style={style3, mark=otimes,}, 
            label in legend={text=data6},
        },
        % 両軸共通のスタイルの指定
        all axes={
            logarithmic, 
            ticks={minor steps between steps=8},
            length=7cm,
        },
        % x軸のスタイルの指定
        x axis={
            min value=0.03, max value=1, 
            label={mesh size},
        },
        % y軸のスタイルの指定
        y axis={
            min value=0.0000000000001, max value=0.1, 
            label={error},
            ticks={tick typesetter/.code={\typesetten{##1}}}
        },
        % 凡例の設定
        legend={north east outside},
    ]

        % データのプロット
        data[set=data1, headline={x, y}, read from file=data1.csv]
        data[set=data2, headline={x, y}, read from file=data2.csv]
        data[set=data3, headline={x, y}, read from file=data3.csv]
        data[set=data4, headline={x, y}, read from file=data4.csv]
        data[set=data5, headline={x, y}, read from file=data5.csv]
        data[set=data6, headline={x, y}, read from file=data6.csv]

        % その他の情報
        info{
            \coordinate (O) at (visualization cs:x=0.125, y=1e-12);
            \coordinate (S) at (visualization cs:x=0.5, y=1e-12);
            \coordinate (N) at (visualization cs:x=0.5, y=4e-12);
            \draw[very thin] (O) -- (S) -- ($(S)!6!(N)$) -- cycle;
            \draw[very thin] (O) -- (N);
            \draw[very thin] (O) -- ($(S)!2!(N)$);
            \draw[very thin] (O) -- ($(S)!4!(N)$);
            \node[below] at ($(O)!.5!(S)$) {$1$};
            \node[right] at (N) {$1$};
            \node[right] at ($(S)!2!(N)$) {$2$};
            \node[right] at ($(S)!4!(N)$) {$4$};
            \node[right] at ($(S)!6!(N)$) {$6$};
        };
\end{tikzpicture}

\end{document}

他の記事へのリンク

  • 1つ目: 基本的なプロットについて.
  • 2つ目: 軸の編集について.
  • 3つ目: より細かい装飾について.
  • 4つ目: この記事. 応用編. 凝った折れ線グラフを描く.
5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4