TikZ는 LaTeX 안에 살고 있는 그리기 언어이고, 그 이름 자체가 경고입니다. 독일어 TikZ ist kein Zeichenprogramm(“TikZ는 그림 프로그램이 아니다”)의 재귀적 약어로, 마우스로 도형을 끌어다 놓는 도구가 아니라는 점을 처음부터 못 박습니다. 대신 본문과 같은 파일에 \draw (0,0) -- (2,1);이라고 쓰면, 그림은 옆 문단과 같은 글꼴과 같은 품질로 조판됩니다. 편리함을 내주고 정확성과 재현성을 얻는 거래인 셈입니다. 이 페이지는 그 거래를 따라가며 첫 도형 그리기, 좌표와 노드의 사고방식, 그리고 컴파일이 느려지는 이유와 대처법까지 살펴봅니다.
TikZ가 “그림 프로그램이 아니다”라고 못 박는 이유 — TikZ와 PGF
이름은 GNU 전통의 농담이고, 매뉴얼도 스스로 그렇게 밝힙니다. TikZ는 “gnu’s Not Unix”의 계보를 잇는 재귀적 약어이며, 독자에게 무엇을 기대해야 하는지 미리 알리려고 붙인 이름이라는 것입니다. 마우스를 내주고 얻는 것은 TeX 방식을 그림에 적용했을 때의 이점입니다. 정확한 배치, 매크로를 통한 재사용, 본문과 어울리는 글자 모양이 그것입니다. 대신 내놓아야 하는 것이 WYSIWYG입니다. 매뉴얼은 나머지 청구서도 숨기지 않아서, 첫 몇 쪽에서 가파른 학습 곡선과 작은 수정마다 뒤따르는 재컴파일을 그대로 적어 둡니다.
그 아래에서 실제로 선을 긋는 것은 PGF입니다. 이름은 “portable graphics format(이식 가능한 그래픽 형식)”의 약자이고, 유래가 분명합니다. 작성자는 pdfLaTeX에서도, 전통적인 PostScript 경유 LaTeX에서도 똑같이 쓸 수 있는 그래픽 매크로를 만들려 했습니다. 그러려면 출력 경로의 차이를 흡수하는 층이 필요했고, 그래서 portable입니다. 날것의 PGF는 쓰기가 번거로우므로, 그 위에 사람이 읽고 칠 수 있는 문법을 덮은 것이 TikZ입니다. 흔히 “PGF/TikZ”를 한 단어처럼 부르지만 실제로 타이핑하는 것은 거의 언제나 TikZ 쪽이며, \usepackage{tikz}를 쓰면 PGF도 함께 로드됩니다.
둘 다 Till Tantau가 만들었습니다. 그는 학부 시절 TeX을 만지작거리기 시작해 발표용 beamer 클래스를 내놓았고, 이후 박사 논문에 실을 그림을 위해 TikZ를 썼다고 스스로 밝혔습니다. LaTeX으로 슬라이드를 만드는 표준과 LaTeX으로 그림을 그리는 표준, 서로 무관해 보이는 두 가지가 같은 사람에게서 나온 셈입니다. 이것이 더 이상 논문용 소품이 아니라는 사실은 숫자 하나로 드러납니다. 3.1.10 버전 매뉴얼 pgfmanual.pdf는 1,321쪽입니다. 게다가 표지 자체가 TikZ로 그린 그림으로, 해 질 녘 풍경 속 프랙탈 나무와 코흐 눈송이가 놓이고 그것을 만들어 내는 소스 코드가 같은 페이지에 나란히 인쇄되어 있습니다.
설정할 것은 거의 없습니다. 프리앰블에 \usepackage{tikz} 한 줄이면 pdfLaTeX, LuaLaTeX, XeLaTeX에서 그대로 동작합니다. 손이 더 가는 것은 DVI를 거치는 경로뿐이어서, pLaTeX나 upLaTeX에서는 드라이버(보통 dvipdfmx)를 클래스 옵션으로 함께 적어 줍니다. 다음이 컴파일되는 최소 문서입니다.
\documentclass{article} % with pLaTeX: \documentclass[dvipdfmx]{jsarticle}
\usepackage{tikz}
\begin{document}
A line and a circle:
\begin{tikzpicture}
\draw (0,0) -- (2,1);
\draw (2.8,0.5) circle [radius=0.5cm];
\end{tikzpicture}
\end{document}첫 도형 그리기 — tikzpicture 환경과 빠뜨리면 반드시 걸리는 세미콜론
모든 그림은 \begin{tikzpicture} … \end{tikzpicture} 안에 그리며, 그리기 명령은 하나도 빠짐없이 세미콜론 ;으로 끝냅니다. 이 환경은 그림 한 장을 올리는 종이이고, 환경 이름 뒤 대괄호에 쓴 옵션은 종이 전체에 적용됩니다. [scale=2]면 두 배, [thick]이면 모든 선이 굵어집니다. 한 줄로 끝나는 작은 그림이라면 환경을 열 필요조차 없어서, 인라인 형태인 \tikz{...}나 명령이 하나뿐일 때의 \tikz \draw ...;를 쓸 수 있습니다.
% inline: one command, no environment
\tikz \draw (0,0) -- (1.5,0);
% environment: one canvas, several commands, shared options
\begin{tikzpicture}[scale=1.2, thick]
\draw (-1.5,0) -- (1.5,0);
\draw (0,-1.5) -- (0,1.5);
\end{tikzpicture}세미콜론을 하나 빠뜨리면 ! Package tikz Error: Giving up on this path. Did you forget a semicolon?가 나옵니다. 까다로운 점은 보고되는 줄 번호가 잘못 쓴 줄이 아니라 그 다음 줄이라는 것입니다. TikZ는 아직 경로의 일부일 수 있는 토큰을 계속 읽어 들이다가, 도저히 경로가 될 수 없는 것—대개는 다음 \draw—을 만나서야 비로소 손을 듭니다. 그러니 오류가 \draw 줄을 가리킨다면 확인할 곳은 그 바로 윗줄의 끝입니다.
좌표와 경로 — \draw, \fill, --, circle 가려 쓰기
TikZ의 작도는 경로—점에서 점으로 어떻게 이동하는지를 적은 길—를 중심으로 돌아가고, 그것을 어떻게 보여 줄지는 명령 이름이 정합니다. \draw는 경로를 선으로 긋고, \fill은 닫힌 경로의 안쪽을 채우며, \filldraw는 채운 뒤 외곽선까지 긋고, \path는 아무것도 그리지 않은 채 경로만 정의합니다. 마지막 것은 쓸모없어 보이지만, 나중에 참조할 좌표와 노드를 조용히 놓아 두는 수단입니다. 같은 점 목록을 쓰고 명령 이름만 바꾸면 선 그림이 채운 도형이 됩니다. 이 직교성이 TikZ 설계의 뼈대입니다.
점을 적는 방법은 세 가지입니다. 직교 좌표 (x,y)는 기본 단위가 센티미터라서 (1,2)는 오른쪽 1 cm, 위쪽 2 cm이고, (1cm,2pt)처럼 단위를 명시할 수도 있습니다. 극좌표 (30:1cm)은 “30도 방향으로 1 cm”라는 뜻입니다. 그리고 이름 있는 좌표를 \coordinate (P) at (1,1);으로 한 번 놓아 두면 이후에는 (P)라고만 쓰면 됩니다. 상대 좌표에서 +와 ++의 차이는 사고가 나기 전에 익혀 두는 편이 좋습니다. ++(1,0)은 오른쪽으로 1 cm 이동하면서 현재 위치까지 함께 옮기고, +(1,0)은 같은 곳에 닿지만 현재 위치는 그대로 둡니다. ++를 여러 번 이으면 꺾은선이 되고, +를 여러 번 이으면 한 기준점에서 뻗어 나가는 방사선이 됩니다.
경로가 어떻게 나아갈지는 경로 조작이 정합니다. --는 직전 점에서 다음 점으로 가는 직선이고, rectangle은 두 대각 모서리를, circle은 중심을 받으며, 그 밖에 ellipse, arc, grid가 있습니다. 곡선은 .. controls ..(Bézier)나 to[bend left]로 그리고, 도형을 닫으려면 끝에 -- cycle을 붙여 시작점으로 돌아갑니다. 도형의 인수는 요즘 대괄호 안의 key-value 쌍으로 씁니다. circle [radius=10pt], ellipse [x radius=20pt, y radius=10pt], arc [start angle=0, end angle=30, radius=3mm], grid [step=.5cm] 같은 식입니다. 예전의 소괄호 표기 circle (10pt)도 여전히 동작하지만, 새로 쓰는 코드는 대괄호 쪽이 읽기 좋고 키를 하나 더 붙이기도 쉽습니다.
\begin{tikzpicture}
\draw[step=.5cm, gray, very thin] (-1.4,-1.4) grid (1.4,1.4);
\draw (0,0) circle [radius=1cm];
\draw (0,0) -- (30:1cm); % polar: 1 cm at 30 degrees
\fill[blue!20] (0,0) rectangle (0.5,0.5);
\draw[red] (-1.2,-1.2) -- ++(0.6,0) -- ++(0,0.6); % ++ carries the current point
\end{tikzpicture}결과는 이런 한 장입니다. 0.5 cm 간격의 옅은 회색 격자가 깔리고, 원점을 중심으로 반지름 1 cm의 원이 놓이며, 원점에서 30도 방향으로 짧은 선분이 뻗고, 원점 옆에는 0.5 cm 정사각형이 연한 파랑으로 채워지며, 왼쪽 아래에는 오른쪽으로 0.6 cm, 다시 위로 0.6 cm 꺾이는 빨간 갈고리가 있습니다. 색 blue!20은 “파랑 20%”, 나머지 80%는 흰색이라 연하게 나옵니다. red!50!black처럼 두 색을 적으면 섞이고, 이 표기법은 채우기에도 선에도 똑같이 통합니다.
노드 — 문자와 상자를 놓고 이름으로 잇기
노드(node)는 지정한 좌표에 문자나 상자를 놓는 장치이고, 형식은 \node[options] (name) at (coordinate) {contents};입니다. 대괄호에는 옵션이 들어갑니다. draw는 테두리, circle이나 rectangle은 모양, fill=blue!20은 채우기입니다. 이 구성의 핵심은 소괄호에 적은 이름이 그 노드의 손잡이가 된다는 점입니다. 이후 (name)으로 위치를 참조할 수 있고, (name.north), (name.east)처럼 앵커를 붙이면 테두리 위의 한 점을 지목할 수 있습니다.
여기서 실무의 정석이 나옵니다. 이름 있는 노드를 먼저 놓고, 그다음 이름끼리 연결하는 것입니다. 좌표 대신 노드 이름으로 이으면 나중에 노드를 1 cm 옮겨도 연결선이 알아서 따라옵니다. 게다가 선은 노드 테두리에서 멈추므로 원이나 상자 안으로 파고들지 않습니다. 노드는 경로 위에도 놓을 수 있어서, \draw (a) -- (b) node[midway, above] {$f$};라고 쓰면 선의 중점 바로 위에 라벨이 얹힙니다. 화살표에 이름을 붙이는 표준적인 방법입니다.
\begin{tikzpicture}
\node (a) at (0,0) [draw, circle, fill=blue!20] {A};
\node (b) at (2.5,0) [draw, circle, fill=blue!20] {B};
\draw[->] (a) -- (b) node[midway, above] {$f$};
\draw[dashed] (a.south) -- (b.south); % .south is an anchor on the border
\end{tikzpicture}이 그림은 연한 파란색 원형 노드 “A”와 “B”를 2.5 cm 떨어뜨려 놓고, A에서 B로 화살표를 그린 뒤 그 중점 위에 $f$를 얹으며, 두 원의 아래쪽 끝을 파선으로 잇습니다. 여기에 피해 가야 할 지뢰가 하나 있습니다. 노드는 참조하기 전에 정의되어 있어야 합니다. 아직 놓지 않은 이름을 쓰거나 이미 있는 이름의 철자를 틀리면 ! Package pgf Error: No shape named ‘b’ is known.가 나옵니다. 이름은 오타가 나기 쉬우니, 이 오류가 보이면 철자와 함께 그 \node가 정말 앞쪽에 있는지 확인하십시오.
선 옵션과 \tikzset으로 재사용하는 스타일
선의 모양은 대괄호 안의 옵션이 정합니다. 두께 thin / thick / very thick, 색 red나 blue!50, dashed와 dotted, rounded corners, 그리고 화살표 ->(끝), <-(시작), <->(양끝)가 자주 쓰입니다. 화살표 뒤에는 실용적인 지름길이 하나 있습니다. TikZ는 -를 포함한 알 수 없는 옵션을 모두 화살표 지정으로 간주합니다. 그래서 키를 잘못 치면 철자에 따라 두 갈래 결말이 기다립니다. -가 없으면 TikZ가 ! Package pgfkeys Error: I do not know the key ‘/tikz/thikc’ and I am going to ignore it. Perhaps you misspelled it.라고 알려 주지만, -가 섞여 있으면 오타가 화살표 지정으로 읽혀 조용히 이상한 결과로 이어질 수 있습니다.
| 옵션 | 의미 |
|---|---|
thick / very thick | 선을 굵게(ultra thick까지 있습니다) |
red, fill=blue!50 | 선 색이나 채우기 색. !n은 농도, red!50!black은 두 색의 혼합 |
dashed / dotted | 파선 또는 점선 |
rounded corners | 경로의 모서리를 둥글게 |
->, <-, <-> | 끝, 시작, 양끝의 화살촉 |
scale=2 | 그 그림(또는 경로)을 2배로 확대 |
node distance=1cm | positioning으로 상대 배치할 때의 기본 간격 |
같은 장식을 세 번 쓰게 되면 그것을 스타일로 만들라는 신호입니다. 프리앰블에서 \tikzset{name/.style={...}}로 정의해 두면 이후에는 이름 하나만 넘기면 되고, 모양을 바꾸고 싶을 때도 정의 한 줄만 고치면 전부에 반영됩니다. 그림 하나 안에서만 통하면 되는 스타일이라면 tikzpicture 옵션에 그 자리에서 적어도 됩니다. every node/.style={...}처럼 특정 종류의 모든 요소에 적용되는 스타일도 있으며, 플로차트처럼 노드 모양을 맞춰야 하는 그림에서는 없어서는 안 됩니다.
\tikzset{help lines/.style={color=blue!50, very thin}}
\begin{tikzpicture}
\draw[help lines] (0,0) grid (3,2);
\draw[thick, red, ->, rounded corners] (0,0) -- (1,2) -- (3,2);
\end{tikzpicture}이 그림은 (0,0)에서 (3,2)까지 옅은 파란 격자를 깔고, 그 위에 원점에서 (1,2)로, 다시 (3,2)로 꺾이는 굵은 빨간 선을 그리며, 꺾인 모서리를 둥글게 하고 끝에 화살촉을 답니다. 격자가 스타일을 거치므로 very thin을 thin으로 고치면 이 격자뿐 아니라 help lines로 그린 모든 격자가 한꺼번에 바뀝니다.
어떤 \usetikzlibrary를 불러와야 하는가
TikZ 본체는 의도적으로 작게 유지되고, 전문적인 부분은 라이브러리로 떼어져 있습니다. 필요한 것만 프리앰블에서 \usetikzlibrary{...}로 불러오며, 여러 개는 쉼표로 구분합니다. 불러오지 않으면 그 편리한 문법은 아예 존재하지 않습니다. positioning 없이 right=of a라고 쓰면 앞 절의 “모르는 키” 오류로 곧장 이어집니다. 첫 해에 실제로 값을 하는 것은 대체로 다음 얼굴들입니다.
arrows.meta— 풍부하고 조정 가능한 화살촉 모음(-{Stealth},-{Latex}등). 매뉴얼은 예전의arrows와arrows.spaced를 폐기 예정으로 표시하므로, 새로 쓰는 코드는 이쪽을 씁니다.positioning— 노드의 상대 배치.right=of a,below=1cm of b처럼 쓰고, 기본 간격은node distance로 정합니다.calc— 좌표 계산.($(a)+(1,0)$)은 “a의 오른쪽 1 cm”,($(a)!0.5!(b)$)는 “a와 b의 중점”입니다.shapes— 원과 사각형 너머의 노드 모양(마름모, 별, 말풍선 등).shapes.geometric같은 부분으로 나뉘어 있습니다.decorations— 경로를 물결, 지그재그, 중괄호 등으로 장식합니다(decorations.pathmorphing과 그 일가).patterns— 해칭, 선, 점 같은 무늬로 채웁니다.fit— 여러 노드를 딱 감싸는 상자를 자동으로 만듭니다. 그림의 일부를 묶을 때 편리합니다.backgrounds— 그림 뒤쪽 레이어에 테두리나 바탕을 그려, 앞쪽 요소와 부딪히지 않게 합니다.matrix— 노드를 격자(행렬) 형태로 정렬합니다.graphdrawing은 대신 자동 배치를 해 주지만 LuaTeX이 필요합니다.
arrows.meta와 positioning만 조합해도 쓸 만한 플로차트가 나옵니다. 요령은 첫 노드에만 좌표를 주고 나머지는 “저것의 오른쪽”, “저것의 아래”처럼 관계로 놓는 것입니다. 나중에 노드를 하나 끼워 넣어도 좌표를 다시 매길 일이 없습니다.
\usepackage{tikz}
\usetikzlibrary{arrows.meta, positioning}
\begin{tikzpicture}[node distance=1cm, every node/.style={draw, rounded corners}]
\node (start) {Start};
\node (proc) [right=of start] {Process};
\node (end) [right=of proc] {End};
\draw[-{Stealth}] (start) -- (proc);
\draw[-{Stealth}] (proc) -- (end);
\end{tikzpicture}결과는 왼쪽에서 오른쪽으로 흐르는 플로차트입니다. “Start”, “Process”, “End”가 적힌 둥근 상자 셋이 1 cm 간격으로 놓이고 날카로운 Stealth 화살촉으로 차례로 이어집니다. every node/.style이 셋의 모양을 한꺼번에 정하고, node distance=1cm이 right=of가 사용하는 간격을 정합니다. 이 둘을 그림 옵션에 적어 두는 것이 나중에 손보기 쉬운 그림을 만드는 방법입니다.
TikZ 컴파일이 느린 이유와 external·standalone의 사용 시점
느린 이유는 계산을 TeX 자신이 매크로로 하기 때문입니다. 좌표를 더하는 일도, 각도를 점으로 바꾸는 일도, Bézier 곡선을 잘게 나누는 일도 외부 엔진이 아니라 조판 과정 안에서 돌아갑니다. 게다가 결과는 캐시되지 않으므로 \draw 한 줄 한 줄이 매 컴파일마다 처음부터 다시 계산됩니다. 그림이 몇 장일 때는 눈치채지 못하지만, 촘촘한 grid, 점이 많은 플롯, 노드가 수십 개인 그림이 쌓이면 기다림이 확연해집니다. 매뉴얼도 앞부분에서 이를 인정하며, 작은 수정에 뒤따르는 긴 재컴파일을 TeX 방식의 대가 가운데 하나로 적어 두었습니다.
표준 대책은 external 라이브러리입니다. \usetikzlibrary{external}를 불러오고 \tikzexternalize를 선언하면, 각 tikzpicture가 한 번만 별도의 PDF로 쓰이고 캐시됩니다. 이름은 자동으로 붙어서 본문이 main.tex라면 main-figure0, main-figure1 …이 됩니다. 이후 실행에서는 그 그림의 소스가 그대로라면 계산을 통째로 건너뛰고 완성된 PDF만 삽입하며, 그림을 고치면 자동으로 다시 만들어집니다. 기본값인 mode=convert with system call은 LaTeX이 스스로를 호출해 그림을 만들기 때문에 -shell-escape를 붙여 실행해야 합니다(pdflatex -shell-escape main). 이 옵션을 잊으면 그림이 아예 생성되지 않으니, 문제가 생기면 여기부터 확인하십시오.
\usepackage{tikz}
\usetikzlibrary{external}
\tikzexternalize % caches each picture as main-figure0.pdf, main-figure1.pdf, ...
% compile with: pdflatex -shell-escape main다른 길은 standalone 클래스입니다. 그림을 독립된 .tex로 옮기고 \documentclass[tikz]{standalone}로 단독 컴파일하면, tikz 옵션이 tikz 패키지를 불러온 뒤 그림의 실제 크기에 맞춰 잘라 낸 한 쪽짜리 PDF를 만들어 줍니다(tikzpicture가 여러 개면 쪽수도 여럿이 됩니다). 본문에서는 \includegraphics{figure}로 넣기만 하면 됩니다. 판단 기준은 이렇습니다. 그림이 본문과 함께 자라난다면 external — 코드는 본문 곁에 두고 캐시만 얻습니다. 그림을 다른 문서나 슬라이드와 함께 쓰거나, 그림 하나를 수십 번 다듬을 작정이라면 standalone — 몇 초 만에 도는 작은 문서가 결국 실험하기에 더 빠릅니다.
% compiles on its own to a one-page PDF, cropped to the picture
\documentclass[tikz]{standalone}
\usetikzlibrary{arrows.meta}
\begin{document}
\begin{tikzpicture}
\draw[-{Stealth}, thick] (0,0) -- (2,1);
\end{tikzpicture}
\end{document}
% then in the main document: \includegraphics{figure}