Macros/encapsulation in TIKZ

I'm considering starting to use TIKZ, and trying to figure out if it will do what I want, and how.

I have a large document with many pictures of Bezier curves. For each of these, I'd like to draw the curve itself, four dots at its control points, and three lines joining these four points. I'm currently drawing these using Powerpoint macros. They look like this:

I want to "encapsulate" this thing in a function or macro, to make it easy to instantiate, and ensure consistency between different pictures. I imagine that the inputs to the function would be the coordinates of the four points and three colors (one for the curve, one for the points, and one for the lines).

I read about the "tikzstyle" facility. It sounds good, because I can place the "style" definition externally, and re-use it in all my pictures, which gives me consistency and easy global changes. But I'm not sure how the "input arguments" (the coordinates) would work.

Is tikzstyle the right approach, or is there some better way (either using TIKZ or some other similar tool)?

This is one possible solution. Here a macro called mybezier is defined within a tikzpicture environment as displayed below that takes 4 arguments: P_0, P_a, P_b, P_1. Then call \mybezier{p0}{pa}{pb}{p1} to draw.

\newcommand\mybezier[4]{
\begin{tikzpicture}
\draw [green]          (#1)--(#2)-- (#3)--(#4);
\draw[very thick,blue] (#1).. controls (#2) and (#3) .. (#4);
\draw [fill=red,draw=black]    (#1)node[left] {$P_0$} circle (2pt);
\draw [fill=red,draw=black]    (#2)node[left] {$P_a$} circle (2pt);
\draw [fill=red,draw=black]    (#3)node[right]{$P_b$} circle (2pt);
\draw [fill=red,draw=black]    (#4)node[right]{$P_1$} circle (2pt);
\end{tikzpicture}
}


Code

\documentclass[]{article}
\usepackage{tikz}
\usetikzlibrary{positioning}
\newcommand\mybezier[4]{
\begin{tikzpicture}
\draw [green]          (#1)--(#2)-- (#3)--(#4);
\draw[very thick,blue] (#1).. controls (#2) and (#3) .. (#4);
\draw [fill=red,draw=black]    (#1)node[left] {$P_0$} circle (2pt);
\draw [fill=red,draw=black]    (#2)node[left] {$P_a$} circle (2pt);
\draw [fill=red,draw=black]    (#3)node[right]{$P_b$} circle (2pt);
\draw [fill=red,draw=black]    (#4)node[right]{$P_1$} circle (2pt);
\end{tikzpicture}
}
\begin{document}
\mybezier{1,0}{2,3}{5,4}{6,1}
\end{document}

• Thanks very much. Very close to what I want. Would it be OK to put the tikzpicture commands outside the newcommand\mybezier definition? That way, I could draw several Bezier curves in a single picture. – bubba Nov 8 '14 at 7:37
• Also, can I have three colors as arguments of mybezier? – bubba Nov 8 '14 at 7:42
• Hi, tikzpicture environment needs to be after \begin{document}. What you could do is to use the tikzpicture environment to wrap the \mybezier{}{}{}{} command and leave the \mybezeir macro alone without tikzpicture environment. – Jesse Nov 8 '14 at 7:47
• Sure, just change the color keywords blue, red, black into, say, #5, #6, #7 and change the [4] to [7] accordingly. When input the arguments, notice the sequence of the order. The maximum variables is 9. Lastly, \mybezier{arg1}{arg2}{arg3}{arg4}{arg5}{arg6}{arg7} is the new command – Jesse Nov 8 '14 at 7:51
• That's what I meant -- put 'tikzpicture' outside the definition of mybezier (but still after \begin{document}). Any thoughts on colors? Could they be arguments of mybezier? Thanks again. – bubba Nov 8 '14 at 7:54

An alternative could be to try the show path construction decoration. It takes a bit of time to set up (e.g., automatically placing the control labels "nicely" while also allowing for custom placement) but after that then using it is pretty easy and can be controlled using TikZ styles:

\documentclass[tikz,border=5]{standalone}
\usetikzlibrary{decorations.pathreplacing,shapes.misc,calc}
\tikzset{show bezier controls/.style={decoration={show path construction,
curveto code={
\tikzset{#1}
\path [hull/.try]
(\tikzinputsegmentfirst)    -- (\tikzinputsegmentsupporta) --
(\tikzinputsegmentsupportb) -- (\tikzinputsegmentlast);
\path [curve/.try] (\tikzinputsegmentfirst) .. controls
(\tikzinputsegmentsupporta) and (\tikzinputsegmentsupportb)
.. (\tikzinputsegmentlast);
\path let \p1=(\tikzinputsegmentfirst), \p2=(\tikzinputsegmentsupporta),
\p3=(\tikzinputsegmentsupportb), \p4=(\tikzinputsegmentlast),
% Ugh. All this just to get the mid-angles of the relevant lines
\n1={atan2(\y2-\y1,\x2-\x1)}, \n2={atan2(\y2-\y3,\x2-\x3)},
\n2={abs(\n2-\n1)<180 ? (\n2 + \n1)/2+180 : (\n2 + \n1)/2},
\n3={atan2(\y3-\y2,\x3-\x2)}, \n4={atan2(\y3-\y4,\x3-\x4)},
\n3={abs(\n4-\n3)<180 ? (\n4 + \n3)/2+180 : (\n4 + \n3)/2}
in
\foreach \l [count=\i] in \tikzbezierlabels{ (\p\i)
node [inner sep=0pt, marking/.try, marking=\i/.try] {}
node [anchor=\n\i, marking label/.try, marking label \i/.try] {$\l$}
};
}}, decorate},
bezier labels/.store in=\tikzbezierlabels, bezier labels={,,,}
}
\begin{document}
\begin{tikzpicture}[
hull/.style={draw=green},
curve/.style={thick, draw=blue},
marking/.style={circle, draw=black, fill=red, minimum size=2.5pt},
marking label/.style={shape=circle},
bezier labels={P_0, P_a, P_b, P_1}
]
\path [show bezier controls] (1,0)  .. controls (2,3) and (5,4) .. (6,1);
\path [show bezier controls, bezier labels={P_0, P_1, P_2, P_3}]
(0,4)  .. controls (1,8) and (4,5) .. (6,10);
\path [show bezier controls={hull/.style={fill=gray!20, draw=black, dotted},
curve/.style={draw=black},
marking/.style={solid,shape=cross out, draw=black!50, thick, minimum size=3pt},
marking label/.append style={outer sep=.25cm, fill=blue!20},
marking label 2/.style={text=red}, bezier labels={P,Q,R,S}}, shift=(90:10)]
(0,0) .. controls ++(4,4) and ++(-4,3) .. (2,0);
\end{tikzpicture}
\end{document}


• Thanks for your answer. Unfortunately, I'm not yet smart enough to understand much of what you did. Being able to move the label positions is definitely useful. I guess I can't do that with Jesse's solution above because the mybezier command already has 7 arguments, so 4 more would be 11, and the limit is 9, right? But maybe it's better to add the labels later, anyway, because both the position and the text are likely to vary. – bubba Nov 8 '14 at 8:57

Here is a pic version.

\documentclass[]{article}
\usepackage{tikz}
\usetikzlibrary{positioning}
\tikzset{
pics/.cd,
mybezier/.style args={#1#2#3#4#5}{
code={
\draw[very thick,#5] (#1).. controls (#2) and (#3) .. (#4);
\draw [gray]          (#1)--(#2)-- (#3)--(#4);
\draw [fill=green,draw=black]    (#1)node[above] {$P₀$} circle (2pt);
\draw [fill=green,draw=black]    (#2)node[above] {$P_a$} circle (2pt);
\draw [fill=green,draw=black]    (#3)node[right] {$P_b$} circle (2pt);
\draw [fill=green,draw=black]    (#4)node[right] {$P₁$} circle (2pt);
}},
}

\begin{document}
\begin{tikzpicture}
\pic (first) {mybezier={1,4}{4,4}{7,2}{8,0}{blue}};
\pic (second) {mybezier={0,3}{3,3}{5,2}{6,0}{red}};
\end{tikzpicture}
\end{document}


For easy reference (my me, and perhaps by others), here is the final code, including the improvements mentioned in the comments, and a bit more. Credit to Jesse for doing most of the work.

\documentclass[12pt]{article}
\usepackage{tikz}
\usetikzlibrary{positioning}

\newcommand\poleDiam{1.8pt}

\newcommand\mybezier[5]{
\draw[very thick,#5] (#1).. controls (#2) and (#3) .. (#4);
\draw [gray]          (#1)--(#2)-- (#3)--(#4);
\draw [fill=green,draw=black]    (#1)node[above] {$P_0$} circle (\poleDiam);
\draw [fill=green,draw=black]    (#2)node[above] {$P_a$} circle (\poleDiam);
\draw [fill=green,draw=black]    (#3)node[right] {$P_b$} circle (\poleDiam);
\draw [fill=green,draw=black]    (#4)node[right] {$P_1$} circle (\poleDiam);
}

\begin{document}
Here is some text before the picture.

\begin{center}
\begin{tikzpicture}
\mybezier{1,4}{4,4}{7,2}{8,0}{blue}
\mybezier{0,3}{3,3}{5,2}{6,0}{red}
\end{tikzpicture}
\end{center}

And here is some text after the picture.
\end{document}


The output from this is:

Apart from the graphical consistency and ease of modification, the labels are another nice benefit. Making Computer Modern labels in Powerpoint is tedious.