7

What I'm trying to do is plot a function (defined using \newcommand{\f}[1]{expression}) using pgfplots Then draw N+1 lines. Each going from (axis cs: x,0) to (axis cs: x,f(x)), where x is in (0,1/N,2/N,...,1).
I tried using a \foreach loop but it didn't work in pgfplots. So I tried replacing it with \pgfplotsinvokeforeach, but I still had to find someway to calculate f(x) before putting it inside the coordinate.
I tried using \pgfmathsetmacro\foo{\f{#1}}, but it seemed like only the last value stuck, as if it waited until the loop was over before drawing the lines. So instead of getting the points
(0,f(0)), (1/N,f(1/N)),..., (1,f(1)),
I got (0,f(1)), (1/N,f(1)),... ,(1,f(1)).

Is there a simple way of doing this?

Code:

\begin{tikzpicture}
\begin{axis}[
    xlabel = ,
    ylabel = ,
    axis lines = middle,
    xtick ={1,4},
    ytick ={0},
    xticklabels = {$a$,$b$},
    ymin = -0.2,
    ymax = 3.7,
    xmin = -0.2,
    xmax = 5.2,
    x=2cm,y=2cm,
    axis line style = thick,
]
\newcommand{\f}[1]{2+sin(deg(#1-2))+sin(deg(3*#1))/2+sin(deg(5*#1))/8 + sin(deg(7*#1))/28}

\addplot[domain=0:5, samples = 300, line width = 1pt, colorOne, name path = f]{\f{x}};

\path [name path = axis] (axis cs:1,0) -- (axis cs:4,0);

\addplot [thick, color = colorOne, fill = colorOne, fill opacity=0.2]
          fill between[of=f and axis, soft clip = {domain=1:4}];

\pgfplotsinvokeforeach{1,1.2,...,4}{
    \pgfmathsetmacro\fAtPoint{\f{#1}}
    \draw[line width = 1pt, colorOne] (axis cs:#1,0) -- (axis cs: #1,\fAtPoint);
}

\end{axis}
\end{tikzpicture}

Note: Instead of going from 0 to 1, I'm trying to go from 1 to 4 with 1/N = 0.2

I ended up using this solution at the end. But I'd like to know how I could do it using for loops.

  • 4
    Would you mind adding the (non-working) code you already have? Makes it easier for us, and it requires less guesswork/assumptions about your setup. – Torbjørn T. Dec 30 '15 at 17:15
  • 1
    @TorbjørnT. Alright, here it is. – Kitegi Dec 30 '15 at 17:51
  • 2
    I've never quite figured out loops inside axis. The suggested method in section 8.1 of the manual (the Remark under the description of \pgfplotsforeachungrouped) doesn't seem to work here. I'll note though that the simplest way of making those lines is a ycomb plot, i.e. \addplot [ycomb,blue!20,samples at={1,1.2,...,4}] {\f{x}}; – Torbjørn T. Dec 30 '15 at 18:13
  • 2
    (By the way, it's always nice if you make complete examples, starting with \documentclass, ending with \end{document}, and containing all the necessary packages, libraries, colour definitions and similar. In this case it's not a big deal to make it workable, but sometimes it can be.) – Torbjørn T. Dec 30 '15 at 18:20
10

With PGFPlots, while it's possible to use "normal" TikZ commands in a loop within an axis environment, this takes some special care because the commands aren't executed at the time when they are first encountered, but rather they are stored and executed when other actions (like determining the axis limits) have been completed. In general, it's preferable to use an \addplot command where possible.

In your case, instead of drawing the vertical lines "manually", you can simply use the ycomb plot style with the markers turned off. To specify at which x-positions to draw the lines, you can use samples at={1,1.2,...,4} (the key uses the same syntax as \foreach).

By the way, instead of defining a macro for the function, you can use the TikZ key declare function = {f(\x) = ...;}. That way, you can call the function in the math parser using simply f(x) instead of \f{x}.

And instead of using the fillbetween library, for filling between a plot and the x-axis you can simply add \closedcycle at the end of the \addplot command.

\documentclass{article}
\usepackage{pgfplots}
\usetikzlibrary{intersections}
\usepgfplotslibrary{fillbetween}

\begin{document}
\begin{tikzpicture}[
    declare function={
        f(\x)=2+sin(deg(\x-2))+sin(deg(3*\x))/2+sin(deg(5*\x))/8 + sin(deg(7*\x))/28;
    }
]
\begin{axis}[
    axis lines = middle,
    xtick ={1,4},
    ytick ={0},
    xticklabels = {$a$,$b$},
    ymin = -0.2,
    ymax = 3.7,
    xmin = -0.2,
    xmax = 5.2,
    x=2cm,y=2cm,
    axis line style = thick,
]

\addplot [
    domain=1:4,
    samples=300,
    line width=1pt,
    fill=red, draw=none,
    fill opacity=0.2
] {f(x)} \closedcycle;

\addplot [
    domain=0:5,
    samples=300,
    line width = 1pt, red
] {f(x)};

\addplot [
    ycomb, thick, red,
    no markers,
    samples at={1,1.2,...,4}
] {f(x)};

\end{axis}
\end{tikzpicture}
\end{document}

If you absolutely want to use a loop, you can define your function using the declare function command described above, and then use

\pgfplotsinvokeforeach{1,1.2,...,4}{
    \draw  [thick, red] (axis cs:#1,0) -- (axis cs:#1, {f(#1)});
}

or

\pgfplotsextra{
    \foreach \x in {1,1.2,...,4}{
        \draw  [thick, red] (axis cs:\x,0) -- (axis cs:\x, {f(\x)});
    }
}

The \pgfplotsextra approach also works with your macro definition and \pgfmathsetmacro.

If you set \pgfplotsset{compat=1.12} or higher you can omit the axis cs, since it's activated by default then.

  • While this works for my particular case, it feels more like a workaround rather than a solution. What if I want to draw diagonal lines instead of vertical lines? Or circles centered at each of those points? – Kitegi Dec 30 '15 at 23:11
  • 1
    I don't see any workaround. – Dr. Manuel Kuehner Dec 30 '15 at 23:23
  • 1
    @Farnight: I also don't think this is a workaround. To me this is just using PGFPlots the way it was intended, employing plots of functions instead of explicit loops. Circles would be plotted using markers, for diagonal lines it would depend on how you want to specify the angle. But if you don't want to use plots, maybe pure TikZ would be a better fit (or, indeed, Python and matplotlib, as you suggested in your other question). – Jake Dec 30 '15 at 23:32
  • 1
    @Farnight: I've edited my answer to show how to achieve the desired result using a loop. – Jake Dec 30 '15 at 23:39
  • 1
    @Farnight: Sorry, typo. The parentheses need to be hidden from the coordinate parser by wrapping the expression in {...}. I've edited my answer. – Jake Dec 30 '15 at 23:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.