我想要实现的目标
框架根可以创建带有彩色框的二维直方图,表示计数率,如下所示:
我的问题实际上只是:我可以通过 PGFPlots 生成这种二维直方图吗?这篇文章的其余部分描述了我目前的发现和尝试。
截至最近撰写本文时,ROOT 有一个名为 TikZ 输出引擎TTeXDump
,进而生成:
这接近良好的图形质量。轴标签文本很容易手动修改为 TeX 语法(或者可以在导出之前在 ROOT 中完成),但还存在其他问题:
- 标签、刻度等的放置均由原始坐标完成(
TTeXDump
描述上述图片的TikZ 代码输出示例)。例如,将x
标签置于轴下方的中心并不是一件容易的事,这使得符合与直接使用 PGFPlots 制作的其他图相一致的图形布局变得很棘手。 - 由于所有图形实体都是静态定义的,因此缩放不会产生透明的结果。
或许还有其他东西。
自己的尝试
我尝试使用 PGFPlots 从导出的 ROOT 数据生成图表。但是,有几个细节我搞错了,也许有一个更明显的解决方案。
通过以以下形式转储直方图箱数据
xcenter ycenter weight
在文件scatter.csv
(Pastebin 数据链接),以下代码给出以下结果:
\documentclass{article}
\usepackage{tikz}
\usepackage{pgfplots}
\pgfplotsset{compat=newest}
\usepackage{siunitx}
\begin{document}
\begin{tikzpicture}
\begin{axis}[
xlabel={$\theta$ /($\pi$ rad)},
ylabel={Energy /\si{\MeV}},
enlarge x limits=.02,
enlarge y limits=.02,
minor tick num=4,
xticklabel style={/pgf/number format/fixed},% exponential axis notation looks bad in this case
colorbar,
scatter/use mapped color={%
draw=mapped color,
fill=mapped color
}]
\addplot[
scatter,
scatter src=explicit,
only marks,
mark=square*,
] file{scatter.csv};
\end{axis}
\end{tikzpicture}
\end{document}
此后,可以轻松对文本、刻度线、颜色图等进行微调,但数据呈现存在一些问题:
- “箱”尺寸由标记尺寸模拟,但最好直接输入箱号来设置标记尺寸。这也需要不对称地完成,因为和方向上的箱数量会有所不同
x
。y
我查看了mark=cube*
和cube/size x
,cube/size y
但未能成功更改标记尺寸。目前,标记是对称的,乍一看可能还不错,但实际上标记以非平凡的方式重叠,这本身就是一个交易破坏者。 enlarge x axis
检查后插入和值enlarge y axis
,以避免标记突出轴外。相反,轴距应该根据标记尺寸自动计算。- 数据点标记位于轴和刻度标记的顶部,这在这里不是最佳的。强制 pgfplots 中的绘图标记“轴在顶部”有一些关于此问题的信息,但解决方案有些复杂。这里有更好的方法吗?
上述散点图方法是否是错误的?其他一些想法如下:
- 也许我可以直接使用散点图数据,并使用 PGFPlots 执行分箱,而不是导出计算出的分箱。但我找不到这样做的方法,而且存在遇到内存限制的潜在风险。
初始 ROOT 输出 PDF 可以去掉轴、刻度标记和标题,只保留图形表面。然后我可以使用
\addplot figure
包含这些内容并使用 PGFPlots 将轴重新绘制上去。数值轴限值可以从 ROOT 中提取,因此比例应该能够正确再现。我还没有研究过从最大/最小值校准 PGFPlots 中的颜色图比例,但这也应该是可能的。也许会有一些对齐问题需要解决。TTeXDump
如果我可以使用输出,删除静态定义的轴、刻度等,并仅使用生成的 TikZ 命令来绘制图体,这将有助于实现自动化\addplot
。但我看不出有什么简单的方法可以将其与此结合起来。数据输出可以用箱号来定义,而不是用明确的坐标来定义,即
1 10 11.0
对于
x
bin 1,y
bin 10 的值为 11,而不是当前的:0.04580479262184749 0.0755985979686503 11.0
由于我们还定义了轴限制和箱量,这实际上应该是我们构建直方图所需的全部信息,但我发现执行起来并不容易。
结论
这看起来似乎有很多问题,但正如最初提到的,内核实际上只有一个:我可以通过 PGFPlots 生成这种二维直方图吗?
答案1
在 Christian Feuersänger 的评论的帮助下,我写出了一个我满意的解决方案,所以我自己回答了这个问题。
问题使用 pgfplots,如何排列数据矩阵以绘制曲面图,以便矩阵中的每个单元格都绘制为正方形?Christian Feuersänger 在他的第一条评论中建议的 TeX.SE 非常有用。将数据视为矩阵而不是散点图使事情变得更容易。
首先,我需要以矩阵形式获取输入数据。除非您对 ROOT 感兴趣,否则请跳过此部分并直接转到下面的“工作示例”。
ROOT数据导出
之前,我通过以下方式生成散点图值ROOT 的 Python 绑定作为:
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))
输入参数hist
是一个TH2F
对象。有关更多信息,请参阅 ROOT 和 PyROOT 文档。
中的“ +2
”xrange
表示 ROOT 在实际绘制的箱体顶部保存了一个下溢箱体和一个溢出箱体。我在此阶段明确丢弃了没有内容的点,以保持 PGFPlots 中的“散点图”整洁。
但现在我想输出一个完整的矩阵,我这样做如下:
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))
我现在通过从 开始范围来丢弃下溢箱1
,并且我还丢弃了溢出箱,因为稍后在 PFDPlots 中选择时不会显示最后一个箱shader=flat corner
。我本可以给出一个虚拟值而不是实际的溢出值,但这并不重要(编辑:实际上,如果溢出值大于/小于最大/最小“真实”值,则可能会有影响 - 它会影响彩色图比例,所以要小心)。
我现在感兴趣的不是提取箱子的中心,而是提取箱子的下边缘。
x
我还更改了和循环的顺序,y
以便以 PGFPlots 更高效处理的形式获取矩阵数据,如手册第 7.2.1 节所述:“将网格数据从 Matlab 导入 PGFPlots”。这在编译时间上产生了明显的差异。
工作示例
现在我有了矩阵,这是绘制此数据的最小工作示例(matrix.csv
)作为二维直方图为:
\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}
手册中的第 7.2.1 节和之前链接的问题解释了这些参数。mesh/cols=51
来自一个已知事实,即直方图包含 50 个水平箱,额外的一个代表了上面链接的 TeX.SE 问题中提到的“虚拟箱”。如果需要更多自动化,可以将箱数与数据一起输出到 CSV 特定的配置文件中。
一个问题是,编译器(xelatex
在本例中)向终端抛出了 5000 行内容:
pgfplotsplothandlermesh@get@flat@color
图像中总共应该有 50⨯100 = 5000 个“单元格”需要渲染。过多的消息本身可能是一个错误,或者我可以通过某种方式抑制它。
另一个问题是“背景”,即表示零值的图形部分,是彩色的,这不是最佳选择。我发现最明显的解决方案是创建一个“开始”为白色的彩色图,这对这些类型的图形来说是有意义的。
除一些其他小的格式修复外,还产生了以下结果:
\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}
我将在分析阶段而不是绘图阶段实现x filter
和转换。y filter
可能还不是一个完成的“产品”,但现在我可以自由地应用样式,并且它是用绝对可管理的代码量来制作的。