12

Recently I found a great example provided by the community of Stack Exchange where fillbetween was used to fill the area between a curve and a constant (see 1st image). I liked it especially, since the color of the area changes depending on whether the curve is larger or smaller than the constant. This was made possible by a findintersections function provided by Jake (link to solution). However, I think it would be especially useful if it would fill an area between two curves (see 2nd image). I have provided a MWE below, which has two curves and a constand. Could somebody provide a solution where the two curves are compared against one another. Then based on which is larger than the other, change the colour between the curves? I think doing this would be very useful for graphs which are otherwise hard to interpret, where differences between two curves are small and there is much overlap.

MWE:

\documentclass{article}

\usepackage{pgfplots}
\usepackage{pgfplotstable}
\usepackage{filecontents}
\usetikzlibrary{calc}
\begin{filecontents}{data.dat}
0 0.2
1 0.217
2 0.255
3 0.288
6 0.58
7 0.91
8 1.02
10 1.05
12 0.92
13 0.78
15 0.56
17 1.1
\end{filecontents}
\begin{filecontents}{data2.dat}
    0 1.2
    1 0.8
    2 0.6
    3 0.20
    6 0.4
    7 0.5
    8 1.5
    10 1.5
    12 0.5
    13 0.9
    15 0.4
    17 1.1
\end{filecontents}

\pgfplotstableread{data.dat}\data
\pgfplotstableread{data2.dat}\datas

\newcommand\findintersections[2]{
    \def\prevcell{#1}
    \pgfplotstableforeachcolumnelement{1}\of#2\as\cell{%
        \pgfmathparse{!or(
            and(
                \prevcell>#1,\cell>#1
            ),
            and(
                \prevcell<#1,\cell<#1
            )
        )}

        \ifnum\pgfmathresult=1
            \pgfplotstablegetelem{\pgfplotstablerow}{0}\of{\data} \let\xb=\pgfplotsretval
            \pgfplotstablegetelem{\pgfplotstablerow}{1}\of{\data} \let\yb=\pgfplotsretval
            \pgfmathtruncatemacro\previousrow{ifthenelse(\pgfplotstablerow>0,\pgfplotstablerow-1,0)}
            \pgfplotstablegetelem{\previousrow}{0}\of{\data} \let\xa=\pgfplotsretval
            \pgfplotstablegetelem{\previousrow}{1}\of{\data} \let\ya=\pgfplotsretval
            \pgfmathsetmacro\newx{
                \xa+(\ya-#1)/(ifthenelse(\yb==\ya,1,\ya-\yb) )*(\xb-\xa)    }

            \edef\test{\noexpand\pgfplotstableread[col sep=comma,row sep=crcr,header=has colnames]{
                0,1\noexpand\\
                \newx,#1\noexpand\\
            }\noexpand\newrow}
            \test
            \pgfplotstablevertcat\interpolated{\newrow}
        \fi
        \let\prevcell=\cell
    }
    \pgfplotstablevertcat\interpolated{#2}
    \pgfplotstablesort[sort cmp={float <}]\interpolated{\interpolated}
    \pgfplotstableset{
        create on use/above line/.style={
            create col/expr={max(\thisrow{1},#1)}
        },
        create on use/below line/.style={
            create col/expr={min(\thisrow{1},#1)}
        },
    }
}


\begin{document}
\pgfplotsset{compat=newest} % For nicer label placement

\findintersections{0.9}{\data}

\begin{tikzpicture}
\begin{axis}[
    xlabel=Time of day,
    ylabel=Volume,
    ytick=\empty,
    axis x line=bottom,
    axis y line=left,
    enlargelimits=true
    ]
\addplot[fill,gray!20!white,no markers,line width=2pt] table [y=above line] {\interpolated} |- (current plot begin);
\addplot[fill,yellow!20!white,no markers,line width=2pt] table [y=below line] {\interpolated} |- (current plot begin);
\addplot[orange,no markers,line width=2pt,domain=-1:20] {0.9};
\addplot[blue,line width=2pt,mark=*] table  {\data};
\addplot[red,line width=2pt,mark=*] table  {\datas};
\end{axis}
\end{tikzpicture}

\end{document}

Image 1

Image 2

15

Since pgfplots version 1.10 you can load library fillbetween:

\documentclass{article}
\usepackage{pgfplotstable}
\pgfplotsset{compat=1.15}% current version is 1.15
\usepgfplotslibrary{fillbetween}
\usepackage{filecontents}
\begin{filecontents}{data.dat}
0 0.2
1 0.217
2 0.255
3 0.288
6 0.58
7 0.91
8 1.02
10 1.05
12 0.92
13 0.78
15 0.56
17 1.1
\end{filecontents}
\begin{filecontents}{data2.dat}
0 1.2
1 0.8
2 0.6
3 0.20
6 0.4
7 0.5
8 1.5
10 1.5
12 0.5
13 0.9
15 0.4
17 1.1
\end{filecontents}

\pgfplotstableread{data.dat}\data
\pgfplotstableread{data2.dat}\datas

\begin{document}
\begin{tikzpicture}
  \begin{axis}[
      xlabel=Time of day,
      ylabel=Volume,
      ytick=\empty,
      axis x line=bottom,
      axis y line=left,
      enlargelimits=true
    ]
  \addplot[name path=plot1,blue,line width=2pt,mark=*] table {\data};
  \addplot[name path=plot2,red,line width=2pt,mark=*]  table {\datas};
  \addplot fill between[ 
    of = plot1 and plot2, 
    split, % calculate segments
    every even segment/.style = {yellow!20!white},
    every odd segment/.style ={gray!20!white}
  ]; 
  \end{axis}
\end{tikzpicture}
\end{document}

results in

enter image description here


If axis background can be filled white and you only want to color the areas yellow where the red curve is higher than the blue one, you could use

\documentclass{article}
\usepackage{pgfplotstable}
\pgfplotsset{compat=1.15}% current version is 1.15
\usepgfplotslibrary{fillbetween}
\usepackage{filecontents}
\begin{filecontents}{data.dat}
0 0.2
1 0.217
2 0.255
3 0.288
6 0.58
7 0.91
8 1.02
10 1.05
12 0.92
13 0.78
15 0.56
17 1.1
\end{filecontents}
\begin{filecontents}{data2.dat}
0 1.2
1 0.8
2 0.6
3 0.288
4 1
6 0.4
7 0.5
8 1.5
10 1.5
12 0.5
13 0.9
15 0.4
17 1.1
\end{filecontents}

\pgfplotstableread{data.dat}\data
\pgfplotstableread{data2.dat}\datas

\begin{document}
\begin{tikzpicture}
  \begin{axis}[
      xlabel=Time of day,
      ylabel=Volume,
      ytick=\empty,
      axis x line=bottom,
      axis y line=left,
      enlargelimits=true,
      axis background/.style={fill=white}
    ]
  \addplot[name path=plot1,blue,line width=2pt,mark=*] table {\data};
  \addplot[name path=plot2,red,line width=2pt,mark=*]  table {\datas};
  \path[name path=xaxis](current axis.south west)--(current axis.south east);
  \addplot[yellow!20!white] fill between[ 
    of = plot1 and plot2,
    split
  ];
  \addplot[axis background] fill between[of = plot1 and xaxis];
  \end{axis}
\end{tikzpicture}
\end{document}

results in (I have changed some values in the data files)

enter image description here

Unfortunaly I did not find a solution where the areas with a higher red curve can be colored yellow and that one with a lower red curve can be colored gray in the same picture.

  • so simple? :-) +1 for nice answer. – Zarko Jul 11 '17 at 10:24
  • Hi esdd, Yeah, I have seen a similar solution. However, this solution is not consistent. What if the blue curve is actually larger than the red curve in the beginning. Then the area between the curves would be yellow as well. I only want the yellow area to be yellow if the red curve is higher than the blue curve, not based on whether it is an even or odd segment. – Raphael Spaans Jul 11 '17 at 11:14
  • I have added a suggestion (or workaround) where the area between the two curves is colored yellow when the red curve is higher. But I only find a solution where the background and the area between the curves with a lower red curve are both colored white. – esdd Jul 11 '17 at 14:34
  • Hi esdd, thanks for your efforts so far! I really appreciate your help and I have gotten closer to a solution. However, I'm still looking for a solution which handles all circumstances. Therefore I decided to create a much more detailed question on this topic. I would like to invite you to take a look at it and perhaps you can help me out with the additional details I provided: tex.stackexchange.com/questions/380323/… – Raphael Spaans Jul 12 '17 at 10:33
4

You can use the every segment no <index> syntax here to specify the formatting of each individual fill segment. See section 4.5.10 and 5.7.2 - 5.7.4 of the PgfPlots manual.

The other option is to create two data series for the paths that define the upper and lower bounds of the difference between the two sets of data. They
could be used as helpers to define the fill area, just as the x-axis is being used as a helper in esdd's answer, but the x-axis helper is not required here.

This is the output: enter image description here

This is the MWE:

\documentclass{article}
\usepackage{pgfplots}
\usepgfplotslibrary{fillbetween}
\pgfplotsset{compat=newest} % For nicer label
\usepackage{pgfplotstable}
\usepackage{filecontents}
\usepackage{xcolor}
\colorlet{higher}{yellow!30}
\colorlet{lower}{lightgray}

\begin{filecontents}{data.dat}
0 0.2
1 0.217
2 0.255
3 0.288
6 0.58
7 0.91
8 1.02
10 1.05
12 0.92
13 0.78
15 0.56
17 1.1
\end{filecontents}
\begin{filecontents}{data2.dat}
    0 1.2
    1 0.8
    2 0.6
    3 0.20
    6 0.4
    7 0.5
    8 1.5
    10 1.5
    12 0.5
    13 0.9
    15 0.4
    17 1.1
\end{filecontents}

\pgfplotstableread{data.dat}\data
\pgfplotstableread{data2.dat}\datas

\begin{document}

\begin{tikzpicture}
\begin{axis}[
    xlabel=Time of day,
    ylabel=Volume,
    ytick=\empty,
    axis x line=bottom,
    axis y line=left,
    enlargelimits=true
    ]
\addplot[name path=plot1,blue,line width=2pt,mark=*] table  {\data};
\addplot[name path=plot2,red,line width=2pt,mark=*] table  {\datas};
  \addplot
  fill between[of = plot1 and plot2,
  split,
  every segment no 0/.style={fill=higher},
  every segment no 1/.style={fill=lower},  
  every segment no 2/.style={fill=higher},
  every segment no 3/.style={fill=lower},
  every segment no 4/.style={fill=higher},
  every segment no 5/.style={fill=lower},
  ]
  ;
\end{axis}
\end{tikzpicture}

\end{document}
  • Hi Ross, thanks for your efforts so far! I really appreciate your help and I have gotten closer to a solution. However, I'm still looking for a solution which handles all circumstances. Therefore I decided to create a much more detailed question on this topic. I would like to invite you to take a look at it and perhaps you can help me out with the additional details I provided: tex.stackexchange.com/questions/380323/… – Raphael Spaans Jul 12 '17 at 10:34

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.