Well, here's a start, using a quiver
plot. This approach requires that you have a "difference" column for the x and y values. This could also be created on the fly using pgfplotstable
, but for the moment, I did it by hand. The joins aren't very pretty, but I can't really think of a way to fix this. Using line cap=round
helps somewhat, but of course introduces artifacts at the start and end of the plot.
Here's Napoleon's main group of soldiers marching from Kowno to Moscow and back (data from http://www.datavis.ca/gallery/re-minard.php)
And here's a plot of your sample data, with line cap=rect
.
Code for plot with Napoleon's data
\documentclass{article}
\usepackage{pgfplots,pgfplotstable}
\begin{document}
\begin{tikzpicture}
\begin{axis}[
width=10cm,
height=4cm,
xlabel=Longitude,
ylabel=Latitude
]
\addplot[
quiver={
u=\thisrow{U},
v=\thisrow{V},
every arrow/.append style={
line width=1pt+\pgfplotspointmetatransformed/1000 * 9pt,
line cap=round,
color=brown!\dir!black
}
},
point meta=explicit,
visualization depends on=\thisrow{Dir}*100\as\dir
] table [meta index=2]{
X Y N U V Dir Group
24 54.9 340000 0.5 0.1 1 1
24.5 55 340000 1 -0.5 1 1
25.5 54.5 340000 0.5 0.2 1 1
26 54.7 320000 1 0.1 1 1
27 54.8 300000 1 0.1 1 1
28 54.9 280000 0.5 0.1 1 1
28.5 55 240000 0.5 0.1 1 1
29 55.1 210000 1 0.1 1 1
30 55.2 180000 0.3 0.1 1 1
30.3 55.3 175000 1.7 -0.5 1 1
32 54.8 145000 1.2 0.1 1 1
33.2 54.9 140000 1.2 0.6 1 1
34.4 55.5 127100 1.1 -0.1 1 1
35.5 55.4 100000 0.5 0.1 1 1
36 55.5 100000 1.6 0.3 1 1
37.6 55.8 100000 0.1 -0.1 1 1
37.7 55.7 100000 -0.2 0 0 1
37.5 55.7 98000 -0.5 -0.7 0 1
37 55 97000 -0.2 0 0 1
36.8 55 96000 -1.4 0.3 0 1
35.4 55.3 87000 -1.1 -0.1 0 1
34.3 55.2 55000 -1 -0.4 0 1
33.3 54.8 37000 -1.3 -0.2 0 1
32 54.6 24000 -1.6 -0.2 0 1
30.4 54.4 20000 -1.2 -0.1 0 1
29.2 54.3 20000 -0.7 -0.1 0 1
28.5 54.2 20000 -0.2 0.1 0 1
28.3 54.3 20000 -0.8 0.2 0 1
27.5 54.5 20000 -0.7 -0.2 0 1
26.8 54.3 12000 -0.4 0.1 0 1
26.4 54.4 14000 -1.4 0 0 1
25 54.4 8000 -0.6 0 0 1
24.4 54.4 4000 -0.2 0 0 1
24.2 54.4 4000 -0.1 0 0 1
};
\end{axis}
\end{tikzpicture}
\end{document}
Code for plot with gerrit's data
\documentclass{article}
\usepackage{pgfplots,pgfplotstable}
\begin{document}
\begin{tikzpicture}
\begin{axis}
\addplot[
quiver={
u=\thisrow{U},
v=\thisrow{V},
every arrow/.append style={
line width=1pt+\pgfplotspointmetatransformed/1000 * 5pt,
line cap=rect
}
},
point meta=explicit
] table [meta index=2]{
X Y N U V
2006 5213 48 1 -101
2007 5112 47 1 148
2008 5260 49 1 -99
2009 5161 53 1 -514
2010 4647 57 1 -234
2011 4413 62 1 -104
2012 4309 62 0.01 -1
};
\end{axis}
\end{tikzpicture}
\end{document}
With the help of Christian Feuersänger's comments, I wrote a solution I am happy with, so I am answering the question myself.
The question Using pgfplots, how do I arrange my data matrix for a surface plot so that each cell in the matrix is plotted as a square? here on TeX.SE that Christian Feuersänger suggested in his first comment was very useful. Viewing the data as a matrix instead of a scatter plot made things easier.
First, I needed to get the input data in matrix form. Skip this section and go directly to "Working examples" below unless ROOT is of interest to you.
ROOT data export
Before, I generated the scatter plot values via ROOT's Python bindings as:
import csv
def th2f_to_csv(hist, csv_file):
"""Print TH2F bin data to CSV file."""
xbins, ybins = hist.GetNbinsX(), hist.GetNbinsY()
xaxis, yaxis = hist.GetXaxis(), hist.GetYaxis()
with open(csv_file, 'w') as f:
c = csv.writer(f, delimiter=' ', lineterminator='\n')
for xbin in xrange(xbins+2):
xcenter = xaxis.GetBinCenter(xbin)
for ybin in xrange(ybins+2):
ycenter = yaxis.GetBinCenter(ybin)
weight = hist.GetBinContent(xbin, ybin)
if weight > 0:
c.writerow((xcenter, ycenter, weight))
The input argument hist
is a TH2F
object. See the ROOT and PyROOT documentation for more information.
The "+2
" in xrange
handles that ROOT saves an underflow and an overflow bin on top of the bins that are actually drawn. I at this stage explicitly threw out the points with no content to keep the "scatter plot" in PGFPlots clean.
But now I instead want to output a complete matrix, which I did as follows:
import csv
def th2f_to_csv(hist, csv_file):
"""Print TH2F bin data to CSV file."""
xbins, ybins = hist.GetNbinsX(), hist.GetNbinsY()
xaxis, yaxis = hist.GetXaxis(), hist.GetYaxis()
with open(csv_file, 'w') as f:
c = csv.writer(f, delimiter=' ', lineterminator='\n')
for ybin in xrange(1, ybins+2):
y_lowedge = yaxis.GetBinLowEdge(ybin)
for xbin in xrange(1, xbins+2):
x_lowedge = xaxis.GetBinLowEdge(xbin)
weight = hist.GetBinContent(xbin, ybin)
c.writerow((x_lowedge, y_lowedge, weight))
I now discard the underflow bin by starting the range from 1
, and I also discard the overflow bin, since the last bin will not be shown when choosing shader=flat corner
in PFDPlots later. I could have just given a dummy value instead of the actual overflow value, but it does not matter (EDIT: actually, it might matter if the overflow value is larger/smaller than the maximum/minimum "real" value — it would then affect the color map scale, so beware).
Instead of extracting the center of the bins, I now am interested in the lower bin edges.
I also change the order of the x
and y
loops to get the matrix data in a form that PGFPlots processes more efficiently, as described in section 7.2.1 in the manual: "Importing Mesh Data From Matlab To PGFPlots". This made a noticeable difference in compilation time.
Working examples
Now that I have the matrix, a minimal working example of plotting this data (matrix.csv
) as a 2D histogram was:
\documentclass{article}
\usepackage{tikz}
\usepackage{pgfplots}
\pgfplotsset{compat=newest}
\begin{document}
\begin{tikzpicture}
\begin{axis}[
view={0}{90},
colorbar,
]
\addplot3[
surf,
shader=flat corner,
mesh/cols=51,
mesh/ordering=rowwise,
] file {matrix.csv};
\end{axis}
\end{tikzpicture}
\end{document}
Section 7.2.1 in the manual and the earlier linked question explains the parameters. mesh/cols=51
comes from the known fact that the histogram contains 50 horizontal bins, and the extra one accounts for the "dummy bin" mentioned in the above linked TeX.SE question. The bin count could be output to a CSV specific configuration file along with the data if more automation is needed.
One issue was that the compiler (xelatex
in this case) threw exactly 5000 rows to the terminal with content:
pgfplotsplothandlermesh@get@flat@color
There should be 50⨯100 = 5000 "cells" to be rendered in total in the image. This excess of messages might be a bug in itself, or something that I could have suppressed in some way.
Another issue is that the "background", i.e. the parts of the graph that represents value zero, is colored in, which is not optimal. The most obvious solution to this I found was to create a color map that "began" at white, which makes sense for these types of graphs anyway.
Along with some minor other formatting fixes, this gave the following result:
\documentclass{article}
\usepackage{tikz}
\usepackage{pgfplots}
\pgfplotsset{compat=newest}
\usepackage{siunitx}
\begin{document}
\pgfplotsset{
/pgfplots/colormap={coldredux}{
[1cm]
rgb255(0cm)=(255,255,255)
rgb255(2cm)=(0,192,255)
rgb255(4cm)=(0,0,255)
rgb255(6cm)=(0,0,0)
}
}
\begin{tikzpicture}
\begin{axis}[
view={0}{90},
xlabel={$\theta$ /degrees},
ylabel={Energy /\si{\MeV}},
minor tick num=4,
colorbar,
colorbar style={ylabel={Counts}},
]
\addplot3[
surf,
shader=flat corner,
mesh/cols=51,
mesh/ordering=rowwise,
x filter/.code={\pgfmathparse{#1*180}\pgfmathresult},
y filter/.code={\pgfmathparse{#1/1000}\pgfmathresult},
] file {matrix.csv};
\end{axis}
\end{tikzpicture}
\end{document}
I will implement the x filter
and y filter
transformations in the analysis stage instead of the plotting stage.
Probably not a finished "product" yet, but now I can apply styling freely, and it is produced with a definitely manageable amount of code.
Best Answer
Here's an option using
asymptote
instead ofpgfplots
. Compile withpdflatex -shell-escape
.Some notes:
L
andM
, andPLM
, which is the associated legendre polynomial. (Unfortunately,asymptote
does not have a built-in library for these to my knowledge.)If this is appealing to you and you want to learn more about
asymptote
, I recommend Charles Staats' asymptote tutorial