Fill area of curve above a line using pgfplots
Here's a macro that generates a new table \interpolated
that places points on your original data at every intersection with a certain y-value. You call it using \findintersections{<table macro}{<value>}
.
To plot the area above the line, you would then use \addplot[fill,gray!20!white,no markers,line width=2pt] table [y=above line] {\interpolated};
, or ...table [y=below line]
for the area below the line.
In order to close the area properly in case your plot stops or begins above the cutoff line, you should add |- (current plot begin)
at the end of the plot command.
\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}
\pgfplotstableread{data.dat}\data
\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};
\end{axis}
\end{tikzpicture}
\end{document}
With the recently released pgfplots 1.10 and its fillbetween
library
(I learned it from Elke here) it's much easier.
- Load the library:
\usepgfplotslibrary{fillbetween}
- Give the plots a name:
\addplot[name path=curve,orange, ...
Add a fill plot:
\addplot fill between[ of = curve and line, split, % calculate segments every even segment/.style = {yellow!20!white}, every odd segment/.style ={gray!20!white} ];
Complete example:
\documentclass{article}
\usepackage{pgfplots}
\pgfplotsset{compat=newest}
\usepackage{pgfplotstable}
\usepgfplotslibrary{fillbetween}
\usetikzlibrary{calc}
\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}
\pgfplotstableread{data.dat}\data
\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=curve,orange,no markers,line width=2pt,domain=0:17] {0.9};
\addplot[name path=line,blue,line width=2pt,mark=*] table {\data};
\addplot fill between[
of = curve and line,
split, % calculate segments
every even segment/.style = {yellow!20!white},
every odd segment/.style ={gray!20!white}
];
\end{axis}
\end{tikzpicture}
\end{document}