📐

LaTeXで作図しSVGにする

2025/02/25に公開

はじめに

LaTeX で教科書に載っているような美しい図を作図し、SVG にする方法をご紹介します。
Markdown や Word でレポートを執筆する方も、LaTeX で作図して周りと圧倒的な差をつけましょう!
以下に図の例を示します。Zenn では SVG がサポートされていないため、PNG になっています。


フーリエ級数のグラフ


ウィーンブリッジ発振回路


線形代数における置換


フーリエ級数の式


WinTpicで描いた加法定理を説明する図

環境

TeX Live がインストールされている必要があります。

親ファイルの作成

次に示す main.tex を作成します。

main.tex
\documentclass[class=jlreq, border=5mm]{standalone}

\usepackage{graphicx}
\usepackage[dvipsnames]{xcolor}
\usepackage{amsmath}
\usepackage{amssymb}
\usepackage{bm}
\usepackage{mathtools}
\usepackage[no-test-for-array]{nicematrix}
\usepackage{siunitx}
\usepackage[siunitx]{circuitikz}
\usepackage{tikz}
\usepackage{tcolorbox}
\usepackage{pgfplots}
\pgfplotsset{compat=newest}
\usetikzlibrary{tikzmark}
\usepackage{luacode} % uplatex 使用時にはコメントアウトする
\setcounter{MaxMatrixCols}{12}
\usepackage{float}

\begin{document}

% \pagecolor{white}
% \pagecolor{green}

\input{}

\end{document}

別の TeX ファイルで作図し、main.tex\input します。

作図する

まずはパスカルの三角形を描写してみましょう。
main.tex と同じディレクトリ内に、次のコードを PascalTriangle.tex として保存します。

PascalTriangle.tex
\begin{tikzpicture}
    \foreach \n in {0,...,5} {
        \foreach \k in {0,...,\n} {
            \node at (\k-\n/2,-\n) {\directlua{
                function binom(n,k)
                    x = 1
                    for y = n-k+1, n do x = x * y end
                    for y = 1, k do x = x / y end
                    return math.floor(x)
                end
                tex.print(binom(\n, \k))}
            };
        }
    }
\end{tikzpicture}

main.tex で、PascalTriangle.tex\input します。

main.tex
-\input{}
+\input{PascalTriangle.tex}

次のコマンドを実行して、PDF main.pdf を得ます。

lualatex main.tex

次のコマンドを実行して、PDF から SVG main.svg を得ます。

pdftocairo -svg main.pdf

ただし、SVG の背景は透明になっています。そのため、ページの背景色によっては全く見えないという事態が起こってしまいます。

背景を透明にしたくない場合には、\pagecolor{white} を指定しましょう。
main.tex でそのコメントアウトを外すだけです。

main.tex
-% \pagecolor{white}
+\pagecolor{white}

最後に、正規表現を使って、図が記述された TeX ファイルの名前を取得し、SVG に同じ名前を付けましょう。
つまり、PascalTriangle.tex ならば PascalTriangle.svg として保存されるようにします。
次のコマンドで実現されます。

mv main.svg $(grep -oP -m 1 '(?<=\\input{).*(?=\.tex})' main.tex).svg

main.tex から \input{} の引数を取得し、main.svg を改名しています。

以上のコマンドをまとめて、コピペして実行しましょう。

lualatex main.tex && pdftocairo -svg main.pdf && mv main.svg $(grep -oP -m 1 '(?<=\\input{).*(?=\.tex})' main.tex).svg

例1. グラフ


フーリエ級数のグラフ

f(x) = \begin{cases} 0 &(-\pi \le x < 0) \\ x &(0 \le x < \pi) \end{cases}, \quad f(x+2\pi) = f(x)

の有限フーリエ級数

f(x) \sim \frac{\pi}{4} + \sum_{n=1}^{N} \left(\frac{1}{n^2\pi}\{(-1)^n - 1\}\cos nx -\frac{(-1)^n}{n}\sin nx\right), \quad N = 20

を描写してみましょう。

FourierSeriesGraph.tex
\begin{luacode*}
    function f(x,N)
        local result = math.pi/4
        for n=1,N do
            result = result + (1/(n^2*math.pi)) * ((-1)^n - 1) * math.cos(n*x) - ((-1)^n)/n * math.sin(n*x)
        end
        tex.sprint(result)
    end
\end{luacode*}
\begin{tikzpicture}[
    domain=-5:5,
    samples=500,
    declare function={f(\x,\N) = \directlua{f(\x,\N)};}
    ]
        \draw (-5,0) -- (5,0) node[right] {$x$};
        \draw (0,-0.5) -- (0,4) node[above] {$y$};
        
        \draw plot (\x,{f(\x,20)});
        \draw[dotted] (-5,pi) -- (5,pi);
        \draw[dotted] (-pi,0) -- (-pi,pi);
        \draw[dotted] (pi,0) -- (pi,pi);

        \draw
            (0,0) node[below left] {O}
            (pi,0) node[below] {$\vphantom{-}\pi$}
            (-pi,0) node[below] {$-\pi$}
            (0,pi) node[left] {$\pi$}
        ;
\end{tikzpicture}

例2. 電気回路図


ウィーンブリッジ発振回路

CircuiTikZ を用いています。
綺麗なコードではないですから、参考程度にお願いします。

WienBridge.tex
\begin{circuitikz}[european]
\ctikzset{
    transistors/scale=1.5,
    capacitors/scale=0.7,
    resistors/scale=0.7,
    sources/scale=0.7
}
\tikzstyle{dot} = [circle, fill, inner sep=1pt]
\draw
(0,0) node[op amp, noinv input up, anchor=+](opamp){}
(opamp.-) -- ++(-1,0) node[dot, label=left:4]{}
++(0,0.5) coordinate (rightCenter)
(rightCenter) to [R, l=$R_4$] ++(0,-3)
-- ++(-1,0) node[dot, label=above:2](bottomCenter){}
(bottomCenter) -- ++(0,-0.25) node[eground]{}
(bottomCenter) -- ++(-1,0) -- ++(0,0.5) node[dot](Z2Bottom){}
(Z2Bottom) -- ++(0.5,0) to [R, l_=$R_2$] ++(0,2) -- ++(-0.5,0) node[dot](Z2Top){}
(Z2Bottom) -- ++(-0.5,0) to [C, l=$C_2$] ++(0,2) -- ++(0.5,0) node[dot]{}
-- (rightCenter -| Z2Top) -- ++(0,0.5) to [R, l=$R_1$] ++(0,2) to [C, l=$C_1$] ++(0,0.2) -- ++(0,0.5)
-- ++(1,0) node[dot, label=below:1](topCenter){}
(rightCenter) to [R, l_=$R_3$] ++(0,3) -- (topCenter -| rightCenter) -- ++(-1,0)
(opamp.+) -- (opamp.+ -| Z2Top) node[dot, label=below right:3]{}
(topCenter) -- ++(0,0.5) coordinate(aboveTopCenter)
(aboveTopCenter) -- (aboveTopCenter -| opamp.out) -- (opamp.out)
;
\end{circuitikz}

例3. インライン数式

Permutation.tex
$
(i,j) \coloneqq \begin{pmatrix}
1 & 2 & \cdots & \tikzmarknode{A}{i \;} & \cdots & \tikzmarknode{B}{\; j} & \cdots & n \\
1 & 2 & \cdots & \tikzmarknode{C}{j \;} & \cdots & \tikzmarknode{D}{\; i} & \cdots & n \\
\end{pmatrix}
$

\begin{tikzpicture}[remember picture, overlay]
    \draw[->] (A) -- (D);
    \draw[->] (B) -- (C);
\end{tikzpicture}

正しい図を得るためには複数回コンパイルする必要があります。


1回だけコンパイルした場合

例4. ディスプレイ数式


フーリエ級数の式

横幅を指定せねばなりません。

FourierSeries.tex
\begin{minipage}{4in}
\begin{tcolorbox}[
    colframe=black!50,
    colback=white,
    colbacktitle=black!50,
    coltitle=white,
    fonttitle=\sffamily,
    title=周期 $2l$ の関数 $f(x)$ のフーリエ級数
]
\vspace{-5mm}
    \[
        \begin{aligned}
            &f(x) \sim c_0 + \sum_{n=1}^\infty \left( a_n \cos \frac{n\pi x}{l} + b_n \sin \frac{n\pi x}{l} \right) \\
            &\qquad\begin{aligned}
            c_0 &= \frac{1}{2l}\int_{-l}^l f(x) \, \mathrm{d}x \\
            a_n &= \frac{1}{l}\int_{-l}^l f(x) \cos \frac{n\pi x}{l} \, \mathrm{d}x, \enspace 
            b_n = \frac{1}{l}\int_{-l}^l f(x) \sin \frac{n\pi x}{l} \, \mathrm{d}x
            \end{aligned}
            \end{aligned}
    \]
\end{tcolorbox}
\end{minipage}

ディスプレイ数式の上に余白が設定されているようなので、それを打ち消すために \vspace{-5mm} を書いています。

例5. WinTpic


WinTpicで描いた加法定理を説明する図

ソースコード
AdditionTheorem.tex
%WinTpicVersion4.32a
{\unitlength 0.1in%
\begin{picture}(22.0000,22.0000)(7.0000,-28.0000)%
% STR 2 0 3 0 Black White  
% 4 1795 1707 1795 1720 4 800 0 0
% O
\put(17.9500,-17.2000){\makebox(0,0)[rt]{O}}%
% CIRCLE 3 0 3 0 Black White  
% 4 1800 1700 2600 1700 2600 1700 2600 1700
% 
\special{pn 4}%
\special{ar 1800 1700 800 800 0.0000000 6.2831853}%
% CIRCLE 3 0 3 0 Black White  
% 4 1800 1700 1733 1566 3800 1700 1300 700
% 
\special{pn 4}%
\special{ar 1800 1700 150 150 4.2487414 6.2831853}%
% VECTOR 2 0 3 0 Black White  
% 2 800 1700 2800 1700
% 
\special{pn 8}%
\special{pa 800 1700}%
\special{pa 2800 1700}%
\special{fp}%
\special{sh 1}%
\special{pa 2800 1700}%
\special{pa 2733 1680}%
\special{pa 2747 1700}%
\special{pa 2733 1720}%
\special{pa 2800 1700}%
\special{fp}%
% CIRCLE 3 0 3 0 Black White  
% 4 1800 1700 2000 1700 3800 1700 2700 1300
% 
\special{pn 4}%
\special{ar 1800 1700 200 200 5.8649610 6.2831853}%
% DOT 0 0 3 0 Black White  
% 2 1443 985 1443 985
% 
\special{pn 4}%
\special{sh 1}%
\special{ar 1443 985 16 16 0 6.2831853}%
\special{sh 1}%
\special{ar 1443 985 16 16 0 6.2831853}%
% VECTOR 2 0 3 0 Black White  
% 2 1800 2700 1800 700
% 
\special{pn 8}%
\special{pa 1800 2700}%
\special{pa 1800 700}%
\special{fp}%
\special{sh 1}%
\special{pa 1800 700}%
\special{pa 1780 767}%
\special{pa 1800 753}%
\special{pa 1820 767}%
\special{pa 1800 700}%
\special{fp}%
% LINE 2 0 3 0 Black White  
% 2 1800 1700 2530 1376
% 
\special{pn 8}%
\special{pa 1800 1700}%
\special{pa 2530 1376}%
\special{fp}%
% LINE 2 0 3 0 Black White  
% 2 1800 1700 1443 985
% 
\special{pn 8}%
\special{pa 1800 1700}%
\special{pa 1443 985}%
\special{fp}%
% STR 2 0 3 0 Black White  
% 4 2065 1645 2065 1695 2 0 0 0
% $\beta$
\put(20.6500,-16.9500){\makebox(0,0)[lb]{$\beta$}}%
% STR 2 0 3 0 Black White  
% 4 1875 1490 1875 1540 2 0 0 0
% $\alpha$
\put(18.7500,-15.4000){\makebox(0,0)[lb]{$\alpha$}}%
% STR 2 0 3 0 Black White  
% 4 2580 1300 2580 1350 2 0 0 0
% B
\put(25.8000,-13.5000){\makebox(0,0)[lb]{B}}%
% STR 2 0 3 0 Black White  
% 4 1393 910 1393 960 3 0 0 0
% A
\put(13.9300,-9.6000){\makebox(0,0)[rb]{A}}%
% STR 2 0 3 0 Black White  
% 4 2625 1675 2625 1725 1 0 0 0
% 1
\put(26.2500,-17.2500){\makebox(0,0)[lt]{1}}%
% STR 2 0 3 0 Black White  
% 4 1775 825 1775 875 3 0 0 0
% 1
\put(17.7500,-8.7500){\makebox(0,0)[rb]{1}}%
% LINE 2 5 3 0 Black White  
% 2 2900 2800 700 600
% 
\special{pn 8}%
\special{pa 2900 2800}%
\special{pa 700 600}%
\special{ip}%
% LINE 2 0 3 0 Black White  
% 2 1443 985 2530 1376
% 
\special{pn 8}%
\special{pa 1443 985}%
\special{pa 2530 1376}%
\special{fp}%
% DOT 0 5 3 0 Black White  
% 2 2530 1376 2530 1376
% 
\special{pn 4}%
\special{sh 1}%
\special{ar 2530 1376 16 16 0 6.2831853}%
\special{sh 1}%
\special{ar 2530 1376 16 16 0 6.2831853}%
\end{picture}}%

WinTpic から生成された TeX コードを LuaLaTeX でコンパイルすると、図形が描写されません。
upLaTeX を用いる必要があります。この際、\usepackage{luacode} をコメントアウトしてください。

main.tex
-\usepackage{luacode} % uplatex 使用時にはコメントアウトする
+%\usepackage{luacode} % uplatex 使用時にはコメントアウトする

次のコマンドを実行して、PDF を得ます。

uplatex main && dvipdfmx main.dvi

SVG の生成と改名のコマンドをまとめると次の通りです。

uplatex main && dvipdfmx main.dvi && pdftocairo -svg main.pdf && mv main.svg $(grep -oP -m 1 '(?<=\\input{).*(?=\.tex})' main.tex).svg

ただし、upLaTeX でコンパイルした場合には、\pagecolor{white} と指定しているにもかかわらず、SVG の背景が透明になってしまいます。
このため、一旦別の色を指定して、SVG 変換後に SVG コード内の色情報を置換する方法をとります。
ここでは、\pagecolor{green} と指定します。

main.tex
-% \pagecolor{green}
+\pagecolor{green}

uplatex main && dvipdfmx main.dvi && pdftocairo -svg main.pdf で SVG を生成すると次のようになります。

次のコマンドを実行して、SVG main.svg 中の緑色 rgb(0%,100%,0%) を白色 rgb(100%,100%,100%) に置換します。

sed -i s/rgb\(0%,100%,0%\)/rgb\(100%,100%,100%\)/g main.svg

こうして白色背景の SVG を得ることができます。

SVG の生成、色置換と改名のコマンドをまとめると次の通りです。

uplatex main && dvipdfmx main.dvi && pdftocairo -svg main.pdf && sed -i s/rgb\(0%,100%,0%\)/rgb\(100%,100%,100%\)/g main.svg && mv main.svg $(grep -oP -m 1 '(?<=\\input{).*(?=\.tex})' main.tex).svg

Discussion