
简介plotIt-master是一个面向C研究人员与开发者的ROOT直方图绘制工具源码包通过简洁API自动完成直方图创建、填充与绘图帮助用户避开繁琐的底层代码更专注于数据分析本身。资源共17个文件主要包括6个C头文件与3个源文件分别承担接口定义与核心实现另有Shell环境配置脚本、Python辅助脚本、Makefile构建文件及YAML示例配置压缩包整体仅22KB。已有256人学习下载。解压后即可查看README与示例通过makefile编译集成快速生成支持自定义分箱、误差棒、轴标签与图例的单维/多维直方图配套的example.yml与构建脚本能显著降低环境配置门槛适合高能物理、数据科学等领域需要频繁绘制ROOT直方图的初学者与资深用户。1. plotIt 是什么在 ROOT 绘图这条路上把重复劳动交给配置文件如果你用 ROOT 画过上百张直方图应该记得那种改一个变量就要改一遍宏的滋味。真正花时间的不是画图而是让每张图都符合统一规范字体、坐标轴、图例、logY、比值面板。plotIt 就是把这一层重复劳动抽出来的实用程序它读取 ROOT 文件里已经填好的 TH1按一份 YAML 配置决定画什么、怎么叠、开不开 logY、带不带比值面板一条命令批量输出 PDF/PNG。适合正在做粒子物理数据分析的人也适合被批量出图拖后腿的团队。注意这里说的直方图是 ROOT 框架的统计分布 TH1不是图像处理里做直方图均衡化的那个直方图root 是 CERN 的软件框架不是 Linux root 账号。2. 从源码装好并用最小配置跑出第一张图依赖、demo 数据与第一份 YAML2.1 依赖检查和编译ROOT 6 环境先就位再谈其他先把话说清楚plotIt 不是独立画图软件它要骑在 ROOT 肩膀上画布、直方图、字体、PDF 输出全走 ROOT 的 C 核心库。所以第一步不是急着下源码而是确认 ROOT 环境能用。source /opt/root/bin/thisroot.sh root-config --version which cmake第二行会打印 ROOT 版本比如 6.24.06。后面接 CMake。thisroot.sh的路径取决于你装 ROOT 的方式可能不在/opt/root下面。只要root-config能被 which 找到编译时就能正确拿到 ROOT 的头文件和库文件。如果前两行就卡住先回去折腾 ROOT 安装别急着编译 plotIt——我见过最长的一次编译翻车现场最后发现只是 ROOT 没 source。源码拿到本地后我习惯用 CMake 编译因为可以顺手指定安装前缀把工具装进用户目录不污染系统。cd plotIt mkdir -p build cd build cmake -DCMAKE_INSTALL_PREFIX$HOME/local .. make -j$(nproc) make install hash -r which plotIt第一行进入源码目录第二行新建独立构建目录这是 CMake 的常规用法避免源码目录里堆满中间文件。-DCMAKE_INSTALL_PREFIX让 make install 把可执行文件放到$HOME/local/bin以后卸载直接删目录就行。make -j$(nproc)用满 CPU 核心对这种小工具基本半分钟就编完。如果 cmake 报 yaml-cpp 找不到说明系统缺 C 的 YAML 解析库Debian/Ubuntu 上装libyaml-cpp-dev即可。有些版本还依赖 boost 的 filesystem 库报错信息会写清楚按包名补上就行。装完用which plotIt确认可执行文件已经出现如果 which 没找到检查$HOME/local/bin是否在 PATH 里或者直接用全路径调用。提示不同发行版本的 plotIt 编译流程有细微差别有的源码里干脆只带 Makefile。动手前先看仓库 README确认该用 CMake 还是直接 make能省掉无谓的折腾。2.2 准备一棵最小输入直方图用 PyROOT 生成 demo ROOT 文件plotIt 只画“已经存在”的直方图它不负责填数。真实工作流里这些直方图是分析作业跑完写进 ROOT 文件的为了练习可以用 PyROOT 造两个假的——一个代表数据一个代表 MC 本底。import ROOT f ROOT.TFile(data.root, RECREATE) h ROOT.TH1F(MET, MET, 20, 0, 200) h.FillRandom(gaus, 10000) h.Write() f.Close()TH1F五个参数依次是直方图名字、标题、分箱数、下界、上界。真正分析里这一句会被事件选择代码替代但名字这个参数要特别在意plotIt 靠它去 ROOT 文件里定位直方图起名时最好带完整语义比如jet1_pt、muon_eta而不是h1。FillRandom(gaus, 10000)是偷懒式填数让直方图有个高斯形状Write()把它写进 TFileClose()落盘。MC 那边稍微多两步假装做过一次归一化import ROOT f ROOT.TFile(mc.root, RECREATE) h ROOT.TH1F(MET, MET, 20, 0, 200) h.FillRandom(gaus, 12000) h.Scale(0.8) h.Write() f.Close()Scale(0.8)相当于给 MC 乘以权重让它的积分和 Data 大致匹配。真实分析里这个权重来自截面、亮度、筛选效率的乘积不会在绘图工具里手动乱乘我这里只是让两张图叠在一起时不至于比例离谱。注意直方图名字是“MET”后面配置文件里的 histo 字段就要写“MET”对不上会直接报找不到对象。2.3 第一个真配置文件与第一条命令数据和一个 MC 文件都备齐后写第一份配置。文件名叫first.ymldatasets: - name: Data file: data.root histo: MET color: 1 marker: 20 - name: MC file: mc.root histo: MET color: 2 stack: 1 layout: title: MET spectrum lumi: 35.9 energy: 13 logY: false ratio: false format: PDF结构上配置分成datasets和layout两段。datasets 里每一行描述一个要画的直方图name 是显示在图例上的名字file 是 ROOT 文件路径histo 是文件里的直方图名字color 是 ROOT 调色板索引1 黑、2 红、3 绿这样排stack: 1 表示参与堆叠。layout 段控制整个画布title 出现在图顶lumi 和 energy 会渲染成35.9 fb^{-1}, 13 TeV的标头logY 控制纵轴是否对数ratio 控制要不要数据/MC 比值面板format 指定输出格式。运行命令只有一行plotIt -u first.yml ls -la-u在我用的版本里是更新模式作用是允许重复运行覆盖已有输出而不是每跑一次就在文件名后面加序号。跑完ls看当前目录应该多出一个 PDF。如果报error while loading shared libraries: libCore.so不是 plotIt 的问题而是当前 shell 没 source ROOT 环境回到 2.1 第一行即可。这张图通常不会好看但没关系。plotIt 的价值不是让单张图更美而是让你把这份 YAML 当作模板换变量名、换文件路径批量产出统一规范的图。它对标的是手写TCanvas、Draw、SaveAs那一堆样板代码赢在批量不赢在单张。不同版本对 YAML 字段的支持有差异有的版本叫logY有的叫log写配置前打开仓库自带 example 看一眼字段以它为准。3. 把配置文件写厚datasets、layout 与输出组织3.1 datasets 块数据、本底、信号的身份要分清楚只有两个 dataset 的配置太单薄。真实分析里至少有 Data、数个本底、可能还有信号颜色、堆叠行为、分组方式都不一样。把 datasets 段展开datasets: - name: Data file: data.root histo: MET color: 1 marker: 20 group: data - name: QCD file: qcd.root histo: MET color: 2 stack: 1 group: bkg - name: Wjets file: wjets.root histo: MET color: 3 stack: 1 group: bkg比 2.3 多出来的关键字段是group。它把多个 dataset 划进同一个集合后续想整组归一化、整组隐藏时不用一个个列名字。我的习惯是数据归data本底归bkg有信号样本就单独开sig组。看配置时哪一堆是数据、哪一堆是陪跑本底一眼读出来。stack: 1决定这个 dataset 是否参与堆叠。数据不堆叠用 marker 画点MC 本底堆叠成色块彼此累加。堆叠顺序一般按配置文件里的出现顺序第一个写在最底层。如果某个本底横跨整个能谱且量级很大放在最底层是好习惯否则后面的信号容易被压住。一个细节dataset 的 name 直接进图例。Wjets这种带加号的字符串YAML 里最好加引号包住部分解析器会把它当成特殊 tag 而出错。组里的规矩是所有 name 一律加引号。dataset 字段名也有版本差异我用的版本file是单个路径有的工具用files接列表并自动相加。如果支持列表同本底拆成多个跑批文件时就不用先合并直方图直接列出来由程序加总。3.2 layout 块坐标轴、标题、logY 一个都不能少datasets 决定“画什么”layout 决定“画在什么样的画布上”。这一块我通常按下面的表逐项核对字段作用常用值title图顶大标题Inclusive jet p_{T}xLabel横轴标签p_{T} [GeV]yLabel纵轴标签常含分箱宽度Events / GeVlumi亮度值显示在图例上方35.9energy对撞能量13logY纵轴是否对数坐标false / truexRange横轴显示范围[0, 200]yRange纵轴显示范围留给自动ratio是否需要比值面板false / truegrid画布网格线falseformat输出格式PDFwatermark图角水印Internallayout 段示例layout: title: MET spectrum xLabel: E_{T}^{miss} [GeV] yLabel: Events / 10 GeV lumi: 35.9 energy: 13 logY: true xRange: [0, 200] ratio: false grid: false format: PDFlogY 的经验法则纵轴数据跨几个量级就开最多和最少差不到一个数量级就别开。开 logY 后 yRange 仍是线性坐标里的数值范围但视觉被对数压缩新手常在这里翻车把 yRange 下界设成 0对数轴直接画不出来。yLabel 里的分箱宽度是粒子物理出图的礼貌问题写成Events / 10 GeV代表每分箱宽度归一评审看着更顺眼。plotIt 不会帮你自动算要在 yLabel 里手写清楚。xLabel 里的E_{T}^{miss}是 ROOT 的数学文本语法能渲染上标注意字符串里的反斜杠转义。watermark 字段平时不显眼但内部评审时非常有用。同一张图带 Internal 水印的发组会材料不带水印的进论文同一个配置只改一个字段。这个习惯帮我挡了不少次“这张图能不能外发”的追问。3.3 一份配置产出多种格式输出目录与命名管理layout 里的format多数版本支持一次输出多个格式写法有的用逗号有的用列表。我常用layout: format: [PDF, PNG] outputDir: plots_v1一次跑完PDF 写论文插 LaTeXPNG 贴聊天记录和会议纪要。outputDir如果版本支持所有产物集中在目录里清理备份都方便不支持的版本我用 shell 重定向一样能达到目的。真正值得提前想清楚的是输出文件命名。常见逻辑是把 name 和 histo 拼一下比如Data_MET.pdf、QCD_MET.pdf。如果两个配置文件画同一个直方图、同一个数据集名字后跑的会覆盖先跑的——这是最安静的坑不报错文件悄悄变旧。我的做法是每个物理分析建独立输出目录目录名带日期和版本比如plots_v1、plots_v2_fixBinning。目录一多谁哪天跑过哪版图翻名字就知道。输出命名还有一个实践直方图名字尽量全局唯一。同一个 ROOT 文件里同时存在MET和h_MET配置里写错名不会报重名而是画错对象。我在分析框架里填直方图时就统一用varname_region_suffix格式把命名习惯前置到填数阶段比在绘图阶段补救便宜得多。4. 画出版级效果比值面板、误差带与多变量循环出图4.1 打开 ratio 面板数据/MC 比值的两级台阶粒子物理内部评审有个潜规则光看堆叠图不算数底下必须挂一条数据/MC 比值线。堆叠图里的差距会被颜色和对数轴掩盖比值面板能直接暴露哪个 bin 偏离模型。很多工具不帮你做这一步plotIt 这类程序通常是一个开关的事。datasets: - name: Data file: data.root histo: MET color: 1 marker: 20 - name: QCD file: qcd.root histo: MET color: 2 stack: 1 - name: Wjets file: wjets.root histo: MET color: 3 stack: 1 layout: title: MET spectrum lumi: 35.9 energy: 13 ratio: true ratioRange: [0.9, 1.1] yLabel: Events format: PDFratio: true会把画布切成上下两个 TPad上 pad 画堆叠与数据下 pad 画比值。第二级台阶是下 pad 的纵轴范围。我用的版本支持ratioRange直接写范围如果字段不识别去仓库 README 里搜 ratio看它实际叫什么。下 pad 用自动范围时比值图的零点会被拉得很低看起来到处都是偏差其实只是显示问题。范围锁在 0.9 到 1.1 是常见做法统计量小就放宽到 0.8 到 1.2不丢人。ratio 面板有个前提MC 必须先归一化到数据。如果 MC 积分只有数据六成比值面板整体漂在 0.6 附近看不出任何 bin 的欠拟合。开 ratio 之前第一件事是把 MC 权重流程检查一遍。4.2 系统误差带误差要提前算好画图只是最后一步系统误差带是出图写论文绕不开的一环。先说原理绘图程序里的误差带本质是一条带上下界的曲线它不负责计算误差从哪来。真实流程是在分析框架里对每个系统源做一次重填直方图累加得到每个 bin 的总上下偏差存成 TH1 或 TGraphAsymmErrors绘图时贴到对应的 MC 直方图上。我用的版本在 dataset 上支持这样的写法- name: QCD file: mc.root histo: MET stack: 1 errorBandUp: MET_sys_up errorBandDown: MET_sys_downerrorBandUp和errorBandDown指到 ROOT 文件里的另外两条直方图绘图时在堆叠图上画一个淡色带子。不同版本的字段名可能不同有的叫errorBand有的要求先把上下界合成 TGraphAsymmErrors 再指过去。动手前翻仓库 example把字段认准这是我被各种版本差异坑过之后的固定动作。用 PyROOT 造误差带示意文件import ROOT f ROOT.TFile(mc.root, UPDATE) h f.Get(MET) h.Sumw2() sig_up h.Clone(MET_sys_up) sig_down h.Clone(MET_sys_down) for b in range(1, sig_up.GetNbinsX() 1): sig_up.SetBinContent(b, h.GetBinContent(b) * 1.05) sig_down.SetBinContent(b, h.GetBinContent(b) * 0.95) sig_up.Write() sig_down.Write() f.Close()关键在Sumw2()。它让 ROOT 给直方图维护误差平方和数组之后的统计误差才有意义。很多人画误差带发现带子是零绝大多数就是忘了 Sumw2。Clone复制直方图再逐 bin 改 content 作为上下界。真实分析里误差源有十几二十个逐源扫完再累加这里用 5% 只是演示“直方图可以作为误差带输入”。误差带的质量取决于输入不取决于画法别在绘图阶段临时补误差。4.3 多变量批量绘制一套 datasets 配多个 histo前面所有例子都只画一个变量实际分析要出的是成批的图。多数版本的 plotItdataset 里的 histo 字段是单数形式不支持数组。我应对的办法是datasets 和 layout 写成模板变量名用占位符外面套 shell 循环。模板template.ymldatasets: - name: Data file: data.root histo: HISTONAME color: 1 marker: 20 - name: QCD file: qcd.root histo: HISTONAME color: 2 stack: 1 layout: title: HISTONAME distribution lumi: 35.9 energy: 13 ratio: true format: PDF循环脚本for var in MET pt_jet n_jet; do sed s/HISTONAME/$var/g template.yml /tmp/cfg_${var}.yml plotIt -u /tmp/cfg_${var}.yml donesed 把模板里所有HISTONAME替换成当前变量名生成临时配置再调 plotIt。这里有个细节直方图名字不带扩展名如果同名直方图在 Data 和 MC 文件里都存在替换一次全部命中输出 PDF 文件名带上变量名不会互相覆盖。这套循环看起来简单但从“手动改一个变量画一张”升级到“一个循环画完关键变量”是工作方式的分水岭。我后来封装成脚本入参是变量列表出参是一批 PDF/PNG。新增变量时只要保证分析代码真的把直方图 Fill 进去其余全自动。循环能跑通的前提是直方图命名规整。如果变量叫bkg_pt但配置里写pt循环会安静地跳过直到查图时才发现少一张。所以占位符最好带路径前缀比如jets/HISTONAME与 ROOT 文件内部目录严格一致。5. 避坑五类最常见的崩溃与错图陷阱5.1 ROOT 版本不一致本地能编服务器上崩现象同一份配置在实验室服务器画图正常在自己笔记本上一跑就 segmentation fault或者图能出图例里的希腊字母全变方框。原因ROOT 版本跨度大时绘图后端 API 有破坏性变更编译期和运行期版本不一致就翻车系统缺字体是方框的元凶。另一个高频现场是 Python 侧包装的 plotIt 与 PyROOT 绑定版本不匹配加载时就崩。解决把服务器root-config --version记下来本地装同一个大版本6.24 对 6.24 绝大多数情况下没大碍。字体问题优先用 ROOT 内置编号字体配置里把 font 设成 42即 Helvetica 内置编号不依赖系统字体环境。我之后固定了一套环境清单ROOT 版本、编译器版本、yaml-cpp 版本写成一行的记录换机器先对齐再跑图。5.2 YAML 缩进错误四个空格引发的数据失踪现象plotIt 能启动但图里少了一个 dataset或者所有 MC 颜色一样堆叠没生效。原因YAML 列表项的属性没有对齐。最常见的是stack或group写到 dataset 的同级而不是缩进的子级解析器把它当成另一条记录。Tab 和空格混用是同族问题YAML 里 Tab 是非法字符很多编辑器默认缩进用 Tab复制进配置就出鬼。解决统一用两个空格缩进编辑器开启显示空白字符。不确定层级时对着 example 逐行数空格。坏例子- name: QCD file: qcd.root histo: MET color: 2 stack: 1stack: 1少两格QCD 的 stack 没生效。和 file、histo 对齐就好了- name: QCD file: qcd.root histo: MET color: 2 stack: 1画完图发现某条 MC 没参与堆叠第一眼看缩进而不是怀疑解析器。5.3 直方图路径没写对文件里明明有程序说找不到现象plotIt 报找不到直方图打开 ROOT 文件浏览器里一眼看到名字和配置里一模一样。原因直方图在文件里的真实路径是jet1/MET配置里只写MET或者名字旁边带空格。ROOT 文件的内部目录结构很少显示在文件名里浏览器里看到的是完整路径但容易看漏前缀。还有一类是文件路径带中文目录名部分版本 ROOT 的文件 IO 对非 ASCII 路径支持不稳定。解决写配置前先打印文件里的对象路径import ROOT f ROOT.TFile.Open(mc.root) for k in f.GetListOfKeys(): print(k.GetName(), k.GetClassName())如果返回jets/MET配置里的 histo 就写jets/MET。文件路径和输出目录尽量纯 ASCII。这条花不到两分钟能避掉大量“配置没问题但就是画不出来”的玄学。还有人把.root写进 histo 字段这是把文件路径和文件内部路径混了。5.4 重跑之后输出不更新旧 PDF 比新代码活得久现象改完 YAML 重跑 plotIt打开 PDF 和上次一模一样再改一次还是老样子。原因最常见三种。一是忘了更新模式参数工具为了省时跳过已有输出二是输出路径变了新 PDF 写在plots_v1打开的是plots_v2里的旧文件三是 PDF 查看器缓存页面没刷新。解决先ls -lt看输出目录的时间戳确认刚生成的到底是哪个文件。命令行显式重跑加更新模式。我的固定动作是每次重跑终端打印输出文件路径然后直接复制路径去打开不靠猜。ls -lt能救掉一半“怎么没生效”。5.5 堆叠数量被重复归一化叠出来的总形状莫名其妙现象开 ratio 面板后比值整体漂在 0.5 或 1.5 附近或者 QCD 和 Wjets 各自正常叠起来总形状对不上 Data。原因上游 ROOT 文件里的 MC 已经乘过权重配置的归一化又整组归一了一次或者每个 MC 都开了单独归一化叠完后每一份都变成单位面积堆叠总形状自然是错的。解决归一化逻辑只留一层。是“MC 总积分对齐 Data 总积分”还是“每类 MC 单独归一化到 1”二选一不要混。我的习惯是上游分析代码已经做绝对归一化绘图配置里不再开归一化需要做形状对比时才临时开。这个坑的诡异之处在于它不报错只在数值关系上露馅所以每次画完堆叠图先看总积分再看比值面板基准线是否贴 1。6. 进阶把二十张图的迭代速度压到一分钟以内配置和模板循环稳定后整个出图流程就变成一套可重复的流水线。最后补三个让效率再高一截的小习惯。第一个是把循环脚本改成“失败继续”模式for f in configs/*.yml; do echo $f plotIt -u $f || echo FAILED: $f done常规脚本遇到一次失败会中断剩下十几张全停。加|| echo FAILED: $f后单张失败只打一行字整个循环跑完回头统一看哪些失败。做变量扫描时这个习惯把出图从“等着中断”变成“等着结果报告”。第二个是验证输出不只是文件存在而是内容可读。批量出图后顺手pdftotext plots_v1/Data_MET.pdf - | grep MET能 grep 到标题文本说明 PDF 不是空白页字体也没崩成乱码。配合ls -lt plots_v1/几分钟内完成“所有图都画了、内容都对了”的双重确认。这个习惯帮我抓住过一次字体雪崩——文件生成了二十个每个都是空白 PDF就是被这条命令拦下来的。第三个是把配置纳入版本管理。datasets 和 layout 都是纯文本 YAML跟着代码进仓库直方图 ROOT 文件体积太大不进。这样每次出图都能追溯“当时的配置是哪一次提交”。评审问某张图的归一化是谁加的git log 一查清清楚楚。我现在的新变量出图流程是在分析代码里加一行 Fill跑完作业把 template.yml 里的 HISTONAME 改掉执行循环脚本打开 PDF 扫一眼全套一分钟以内。宏文件里挪图例、调坐标轴的苦基本告别了。希望帮到你。本文还有配套的精品资源点击获取