Why must the independent variable of a function to be plotted be a macro in TikZ?

When pgfmath parser was written, the main aim was to provide consistent and slightly more versatile mathematical operations than the calc package (which used to do all the calculations) without the overhead of the fp package. Also the integration with \foreach variables was important as well (as been suggested above).

So, every expression given to the pgfmath parser is \edef'ed immediately prior to parsing. This means that in most cases (unless you do something clever with \noexpand inside double quotes) that

  • any unexpanded tokens are TeX registers
  • the parser does not have to worry about checking whether tokens are expandable

As things stand it is possible to define a function which can stand for a plotting variable (as has been shown above). This is, to my knowledge, the only way of achieving the OPs requirements. But (as has also already been pointed out) parsing a number is quicker than parsing/evaluating a function.

As a "side note", I genuinely intended pgfmath to be a temporary fix for mathematical operations inPGF and thought that when luatex came along "proper" programmers would jump in and sort things out. Sadly that hasn't happened.


Why? I don’t know.

Though, TikZ doesn’t do anything else but to iterate over the domain. When TikZ plots a function it uses the PGF macro \pgfplotfunction for this. In our case it is called with something like (the third parameter is not correct but this is essentially how you would do this in PGF)

\pgfplotfunction{\t}{0, 0.10019, ..., 50}{\pgfpointpolar{\t r}{1+2*exp(-\t/10)}

In the definition of \pgfplotfunction we find something like

% Initialize plotting
\foreach #1 in {#2}{
   % parse #3, extract x and y and sent it to the plotter
}
% Do the actual plotting

Here’s a way around it by defining a PGFmath variable that simply expands to the variable used by the plotter. To not overwrite any already defined macros like \t or \theta (which could be used by a node on the path), I “hid” the actual variable name in a m@cro.

\tikzset{
  variable*/.style={
    declare function={#1=\tikz@plot@var;},
    variable=\tikz@plot@var}}

You can now say variable*=theta and then use theta instead of \t in the function.

I have added a “fast” version in the code below because our variable is already calculated by the \foreach loop and doesn’t need additional parsing or evaluating. Thus, I copy the value of the iterator directly to \pgfmathresult.


Note that if you plot with gnuplot (an external tool to TeX and TikZ) by using the function plot operator as in

\draw plot[parametric, domain=0:50, samples=500, smooth]
        function {cos(t)*(1+2*exp(-t/10)), sin(t)*(1+2*exp(-t/10))};

you need to use the variables x and, for parametric plots, t.

Code

\documentclass[border=5pt,tikz]{standalone}
\makeatletter
\tikzset{variable*/.code=\pgfmathdeclarefunction{#1}{0}{\let\pgfmathresult\tikz@plot@var}%
                         \def\tikz@plot@var{\tikz@plot@var}}
\makeatother
\begin{document}
\begin{tikzpicture}
    \draw [domain=0:50,variable*=theta,smooth,samples=500]
        plot (theta r: {1 + 2 * exp( -theta / 10)});
\end{tikzpicture}
\end{document}

(edit) 2017: since xint 1.1 (2014/10/28), xinttools is not loaded by xintfrac. Code updated.


Not that I recommend it at all, as the nice key=value syntax is lost, but it is possible to do a bit of the job done by TikZ and avoid having a macro for the variable in plots. But one still needs a macro, now for the expression giving the plotted points. I tried without but it appears that after coordinates one needs something completely expandable, so I could not use \xintFor there.

The macros \Sample, \SamplE and \SampleFit are a bit undecipherable, what they do is to compute the #1 which will be used by the macro \PointMacro (name arbitrary) corresponding to the actual plot. The #1 will be a fixed point number with 4 digits after decimal mark.

\documentclass[border=5pt,tikz]{standalone}
\usepackage{xintfrac, xinttools}

% \Sample {N}\pointmacro {start:end}
% it returns expandably the N points from  N equispaced samples starting with
% start and ending with end.
% \pointmacro (name arbitrary) 
% should be a one-parameter macro which returns a point
% as recognized by tikz (such as (x,y), or (angle:radius))
% example \def\macro #1 {(#1, {(#1)^2})}
% see examples below

\def\Sample #1#2#3{\SamplE {#1}#2#3;}

\def\SamplE #1#2#3:#4;{\expandafter\xintApplyUnbraced\expandafter
     {\expandafter\SampleFit\expandafter #2\expandafter
       {\romannumeral0\xintdiv{\xintSub{#4}{#3}}{\xintDec{#1}}}{#3}}
     {\xintSeq{0}{#1-1}}}

\def\SampleFit #1#2#3#4{\expandafter #1\expandafter{\romannumeral0%
                       \xinttrunc {4}{\xintAdd{#3}{\xintMul{#2}{#4}}}}}

\begin{document}    
\tikzset {x=.5cm, y=.5cm}

\begin{tikzpicture}
    \def\ParabolaPoint #1{(#1, {(#1)^2})}% negative #1 within parentheses!
    \draw [smooth] plot coordinates {\Sample {25}\ParabolaPoint {-2:2}};
\end{tikzpicture}

\begin{tikzpicture}
    \def\CubicPoint #1{(#1, {(#1)^3})}
    \draw [smooth] plot coordinates {\Sample {25}\CubicPoint {-1.25:1.25}};
\end{tikzpicture}

\begin{tikzpicture}
    \def\SpiralPoint #1{({#1 r}: {1+2*exp(-#1/10)})}
    \draw [smooth] plot coordinates {\Sample {200}\SpiralPoint {0:50}};
\end{tikzpicture}

\end{document}

parabolacubicspiral