Using axis equal
causes to maintain the width/height and to modify axis limits and the image scaling. In order to keep the axis limits and modify just the units, we can say scale uniformly strategy=units only
. I added width=10cm
to enlarge the sphere compared to the axis descriptions (different fonts for the axis description might also have done the job). Adding height=10cm
as well avoids confusion as to which of the parameters width
or height
is to be used in the final version.
Adding view/h=45
appears to be quite good as well.
Combined with opacity
as suggested by Henri, we end up at
\documentclass[tikz]{standalone}
\usepackage{pgfplots}
\pgfplotsset{compat=1.8}
\begin{document}
\begin{tikzpicture}
\begin{axis}[%
axis equal,
width=10cm,
height=10cm,
axis lines = center,
xlabel = {$x$},
ylabel = {$y$},
zlabel = {$z$},
ticks=none,
enlargelimits=0.3,
view/h=45,
scale uniformly strategy=units only,
]
\addplot3[%
opacity = 0.5,
surf,
z buffer = sort,
samples = 21,
variable = \u,
variable y = \v,
domain = 0:180,
y domain = 0:360,
]
({cos(u)*sin(v)}, {sin(u)*sin(v)}, {cos(v)});
\end{axis}
\end{tikzpicture}
\end{document}
We can also change the parameterization of the sphere to screen coordinate rather than angles and get the LEFT image (the right is the same as above)
\documentclass[tikz]{standalone}
\usepackage{pgfplots}
\pgfplotsset{compat=1.8}
\begin{document}
\begin{tikzpicture}
\begin{axis}[%
axis equal,
width=10cm,
height=10cm,
axis lines = center,
xlabel = {$x$},
ylabel = {$y$},
zlabel = {$z$},
ticks=none,
enlargelimits=0.3,
view/h=45,
scale uniformly strategy=units only,
]
\addplot3[
surf,
opacity = 0.5,
samples=21,
domain=-1:1,y domain=0:2*pi,
z buffer=sort]
({sqrt(1-x^2) * cos(deg(y))},
{sqrt( 1-x^2 ) * sin(deg(y))},
x);
\end{axis}
\end{tikzpicture}
\end{document}
I causes an even distribution along the z axis (but not along the angles).
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
Do you mean something like this ...