PGFPlots supports boxplots natively as of version 1.8
See Boxplot in LaTeX for an example.
The remainder of this answer should be considered obsolete.
You're right to ask about this, the current code is not very convenient to use (although it's proved surprisingly useful to me in the past nonetheless).
Your approach is very attractive in how much simpler the code is. However, when I first wrote the box plot stuff, I decided to go with several \addplot
commands because that's the easiest way to get PGFPlots to take the box and whiskers into account when calculating the axis ranges.
I've modified my code to now provide a new command \boxplot[<optional keys>]{<data table>}
. You can now also tell the command in which columns the different components of the box plots are, by setting box plot median index=<column index>
, box plot whisker top index=<column index>
, and so on. The box width is adjustable based on the question PGFplots and boxplots: How to tune width and separation of boxes?.
By default, only legend entry is created per box plot. If you want to avoid creating legend entries for the box plots entirely, you can add forget plot
to the \boxplot
options.
Using the following code (testdata1.dat
is in my format, testdata2.dat
in yours)
\begin{axis} [box plot width=2mm]
\boxplot [forget plot, red] {testdata.dat}
\boxplot [
forget plot,
box plot whisker bottom index=1,
box plot whisker top index=5,
box plot box bottom index=2,
box plot box top index=4,
box plot median index=3
] {testdata2.dat}
\addplot [domain=-2:6, thick, cyan] {-x+25+rnd}; \addlegendentry{Some line}
\end{axis}
you can now get
Complete code:
\documentclass{article}
\usepackage{pgfplots}
\usepackage{filecontents}
\begin{filecontents}{testdata.dat}
0 10 12 4 15 2
1 20 23 15 27 10
2 7 14 5 19 1
\end{filecontents}
\begin{filecontents}{testdata2.dat}
x whiskerbottom boxbottom median boxtop whiskertop
1 42 45 47 47.5 48
2 36 39 40 41 43
3 41 44 45 46 47
4 20 29 31 36 38
5 31 32 34 36 39
\end{filecontents}
\pgfplotsset{
box plot/.style={
/pgfplots/.cd,
black,
only marks,
mark=-,
mark size=\pgfkeysvalueof{/pgfplots/box plot width},
/pgfplots/error bars/y dir=plus,
/pgfplots/error bars/y explicit,
/pgfplots/table/x index=\pgfkeysvalueof{/pgfplots/box plot x index},
},
box plot box/.style={
/pgfplots/error bars/draw error bar/.code 2 args={%
\draw ##1 -- ++(\pgfkeysvalueof{/pgfplots/box plot width},0pt) |- ##2 -- ++(-\pgfkeysvalueof{/pgfplots/box plot width},0pt) |- ##1 -- cycle;
},
/pgfplots/table/.cd,
y index=\pgfkeysvalueof{/pgfplots/box plot box top index},
y error expr={
\thisrowno{\pgfkeysvalueof{/pgfplots/box plot box bottom index}}
- \thisrowno{\pgfkeysvalueof{/pgfplots/box plot box top index}}
},
/pgfplots/box plot
},
box plot top whisker/.style={
/pgfplots/error bars/draw error bar/.code 2 args={%
\pgfkeysgetvalue{/pgfplots/error bars/error mark}%
{\pgfplotserrorbarsmark}%
\pgfkeysgetvalue{/pgfplots/error bars/error mark options}%
{\pgfplotserrorbarsmarkopts}%
\path ##1 -- ##2;
},
/pgfplots/table/.cd,
y index=\pgfkeysvalueof{/pgfplots/box plot whisker top index},
y error expr={
\thisrowno{\pgfkeysvalueof{/pgfplots/box plot box top index}}
- \thisrowno{\pgfkeysvalueof{/pgfplots/box plot whisker top index}}
},
/pgfplots/box plot
},
box plot bottom whisker/.style={
/pgfplots/error bars/draw error bar/.code 2 args={%
\pgfkeysgetvalue{/pgfplots/error bars/error mark}%
{\pgfplotserrorbarsmark}%
\pgfkeysgetvalue{/pgfplots/error bars/error mark options}%
{\pgfplotserrorbarsmarkopts}%
\path ##1 -- ##2;
},
/pgfplots/table/.cd,
y index=\pgfkeysvalueof{/pgfplots/box plot whisker bottom index},
y error expr={
\thisrowno{\pgfkeysvalueof{/pgfplots/box plot box bottom index}}
- \thisrowno{\pgfkeysvalueof{/pgfplots/box plot whisker bottom index}}
},
/pgfplots/box plot
},
box plot median/.style={
/pgfplots/box plot,
/pgfplots/table/y index=\pgfkeysvalueof{/pgfplots/box plot median index}
},
box plot width/.initial=1em,
box plot x index/.initial=0,
box plot median index/.initial=1,
box plot box top index/.initial=2,
box plot box bottom index/.initial=3,
box plot whisker top index/.initial=4,
box plot whisker bottom index/.initial=5,
}
\newcommand{\boxplot}[2][]{
\addplot [box plot median,#1] table {#2};
\addplot [forget plot, box plot box,#1] table {#2};
\addplot [forget plot, box plot top whisker,#1] table {#2};
\addplot [forget plot, box plot bottom whisker,#1] table {#2};
}
\begin{document}
\begin{tikzpicture}
\begin{axis} [box plot width=2mm]
\boxplot [forget plot, red] {testdata.dat}
\boxplot [
forget plot,
box plot whisker bottom index=1,
box plot whisker top index=5,
box plot box bottom index=2,
box plot box top index=4,
box plot median index=3
] {testdata2.dat}
\addplot [domain=-2:6, thick, cyan] {-x+25+rnd}; \addlegendentry{Some line}
\end{axis}
\end{tikzpicture}
\end{document}
A sample:
\documentclass{article}
\usepackage{pgfplots}
\begin{document}
\pgfplotsset{
compat=newest,
/pgfplots/legend image code/.code={%
\draw[mark repeat=2,mark phase=2,#1]
plot coordinates {
(0cm,0cm)
(0.3cm,0cm)
(0.6cm,0cm)
(0.9cm,0cm)
(1.2cm,0cm)%
};
},
}
\begin{tikzpicture}
\begin{axis}
\addplot+[dashdotted,mark=triangle] plot {x^2};
\addlegendentry{a}
\addplot+[dotted,mark=*] plot {1};
\addlegendentry{b}
\addplot+[mark=star] plot {x};
\addlegendentry{c}
\end{axis}
\end{tikzpicture}
\end{document}
Another sample
plot coordinates {
(0cm,0cm)
(0.3cm,.1cm)
(0.6cm,0cm)
(0.9cm,-.1cm)
(1.2cm,0cm)%
};
Explanation
Go to pgfplots.code.tex
and find this:
/pgfplots/line legend/.style={%
/pgfplots/legend image code/.code={%
\draw[mark repeat=2,mark phase=2,##1]
plot coordinates {
(0cm,0cm)
(0.3cm,0cm)
(0.6cm,0cm)%
};%
}%
},
/pgfplots/line legend/.style/.code={\pgfplots@error{This style is supposed to be constant.}},%
/pgfplots/line legend/.append style/.code={\pgfplots@error{This style is supposed to be constant.}},%
This shows us that
- A legend entry is indeed a plot;
- that plot consists of three fixed point, the second marked; and last, but the worst
- you cannot modify this style anymore because
/.style/.code
make it meaningless to say /.style={new style}
. (It throw the error and ignore your suggestion)
So... in general, one solution is to copy pgfplots.code.tex
to your current folder and modify those lines to, say,
/pgfplots/line legend/.style={%
/pgfplots/legend image code/.code={%
\draw[mark repeat=2,mark phase=2,##1]
plot coordinates {
(0cm,0cm)
(0.3cm,0cm)
(0.6cm,0cm)
(0.9cm,0cm)
(1.2cm,0cm)%
};%
}%
},
But at the beginning of my answer I need no new pgfplots.code.tex
because we do not really care about /pgfplots/line legend/.style
and we can simply manipulate /pgfplots/legend image code/.code
. However, one obvious drawback is that it nullifies any previous /pgfplots/legend image code/.add code
, or .append code
or .prefix code
.
About distance of marks
In your case, the default samples=25
and domain=-5:5
are used. So there is one mark every .4
unit in x direction. Therefore we expect the following assignment gives the correct result.
plot coordinates {
(axis cs:.0,-5)
(axis cs:.2,-5)
(axis cs:.4,-5)
(axis cs:.6,-5)
(axis cs:.8,-5)
};
In general it is quite hard to tell the actual (horizontal) distance between marks since PgfPlots does scaling quite often. By general I meant that you may have data points with periodic x-values but periods vary from line to line. Manual calculation is doable only if you are plotting a function. But then (a) you do not need PgfPlots but TikZ and (b) it is meaningless to add marks which represent data.
Best Answer
In principle, you can just provide one or more box plots here. In your case, it appears to be
boxplot prepared
with suitable arguments.I rarely had the opportunity to study common box plot styles, so I took the freedom to adopt pgfplots to your style. Maybe I can add such styles to pgfplots eventually, so feedback of sorts "I miss a style like XYZ" are welcome.
Anyway, I arrived at the following which appears to be quite close to what you want to archieve:
(my
convert
outputs used to be better; no idea why the quality of the screenshot looks so degenerate!?)I do not know what you mean by "breaking my hack", but I would be interested in why and how pgfplots "broke" something. Breaking compatibility is surely something that it should not do, so I consider any problem of this sorts a bug report. I would appreciate it if you would send an email to me which illustrates the problem (some minimal working example).