#include "QFramework/TQROOTPlotter.h"
#include "TCanvas.h"
#include "TStyle.h"
#include "TROOT.h"
#include "TFile.h"
#include "TLegend.h"
#include "TLegendEntry.h"
#include "TLatex.h"
#include "THStack.h"
#include "TMath.h"
#include "TArrow.h"
#include "TLine.h"
#include "TGaxis.h"
#include "TH1.h"
#include "TH2.h"
#include "TProfile.h"
#include "QFramework/TQIterator.h"
#include "QFramework/TQLibrary.h"
#include "QFramework/TQNamedTaggable.h"
#include "QFramework/TQHistogramUtils.h"
#include "QFramework/TQStringUtils.h"
#include "QFramework/TQUtils.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <cstdlib>
#include <cmath>
ClassImp(TQROOTPlotter)
TQROOTPlotter::TQROOTPlotter() :
TQPlotter(),
pads(new TObjArray())
{
}
TQROOTPlotter::TQROOTPlotter(TQSampleFolder * baseSampleFolder) :
TQPlotter(baseSampleFolder),
pads(new TObjArray())
{
}
TQROOTPlotter::TQROOTPlotter(TQSampleDataReader * dataSource) :
TQPlotter(dataSource),
pads(new TObjArray())
{
}
void TQROOTPlotter::reset() {
TQPlotter::reset();
this->setStyleAtlas();
}
void TQROOTPlotter::addHistogramToLegend(TQTaggable& tags, TLegend * legend, TQNamedTaggable* process, const TString& options){
if(!process) return;
TQTaggable opts(options);
opts.importTags(process);
this->addHistogramToLegend(tags,legend,this->makeHistogramIdentifier(process),opts);
}
void TQROOTPlotter::addHistogramToLegend(TQTaggable& tags, TLegend * legend, const TString& identifier, TQTaggable& options){
this->addHistogramToLegend(tags,legend,this->getObject<TH1>(identifier),options);
}
void TQROOTPlotter::addHistogramToLegend(TQTaggable& tags, TLegend * legend, TH1* histo, const TString& options){
TQTaggable opts(options);
this->addHistogramToLegend(tags,legend,histo,opts);
}
void TQROOTPlotter::addHistogramToLegend(TQTaggable& tags, TLegend * legend, TH1* histo, TQTaggable& options){
bool showMissing = tags.getTagBoolDefault("style.showMissing",true);
bool showEventYields = tags.getTagBoolDefault("style.showEventYields",false);
bool showMean = tags.getTagBoolDefault("style.showMean");
bool showRMS = tags.getTagBoolDefault("style.showRMS");
bool showUnderOverflow = tags.getTagBoolDefault("style.showEventYields.useUnderOverflow",true);
bool showEventYieldErrors = tags.getTagBoolDefault("style.showEventYields.showErrors",false);
bool verbose = tags.getTagBoolDefault("verbose",false);
TString title = options.getTagStringDefault("defaultTitle", "");
if (histo) title = histo->GetTitle();
options.getTagString("title", title);
title = TQStringUtils::convertLaTeX2ROOTTeX(title);
if (options.getTagBoolDefault("showInLegend", true)) {
if (histo) {
if (showEventYields){
double err;
double val = TQHistogramUtils::getIntegralAndError(histo,err,showUnderOverflow);
if(showEventYieldErrors){
title.Append(TString::Format(" {%.3g #pm %.3g}", val, err));
} else {
title.Append(TString::Format(" {%.3g}", val ));
}
}
if (showMean)
title.Append(TString::Format("#mu=%.3g", histo->GetMean()));
if (showRMS)
title.Append(TString::Format("(%.3g)", histo->GetRMS()));
if(verbose) VERBOSEclass("adding legend entry '%s', attributed to histogram '%s'",title.Data(),histo->GetName());
title.Prepend(" ");
legend->AddEntry(histo, title, options.getTagStringDefault(".legendOptions", "f"));
} else {
if (showMissing){
if(verbose) VERBOSEclass("adding empty legend entry for missing histogram (showMissing=true)");
title.Prepend(" ");
legend->AddEntry(new TObject(), title, "");
}
}
} else {
DEBUGclass("process '%s' is not added to legend (showInLegend=false)",title.Data());
}
}
void TQROOTPlotter::addAllHistogramsToLegend(TQTaggable& tags, TLegend * legend, const TString& processFilter,
const TString& options, bool reverse){
bool verbose = tags.getTagBoolDefault("verbose",false);
if(verbose) VERBOSEclass("entering function, processFilter='%s'",processFilter.Data());
TQTaggableIterator itr(this->fProcesses->MakeIterator(reverse ? kIterBackward : kIterForward),true);
while(itr.hasNext()){
TQNamedTaggable * process = itr.readNext();
if(!process){
if(verbose) VERBOSEclass("skipping NULL entry");
} else {
if(!processFilter.IsNull() && !process->getTagBoolDefault(processFilter,false)){
if(verbose) VERBOSEclass("skipping empty legend entry for '%s' - does not match filter '%s'",process->getTagStringDefault(".path",process->GetName()).Data(),processFilter.Data());
} else {
this->addHistogramToLegend(tags,legend,process,options);
}
}
}
}
TCanvas * TQROOTPlotter::plot(TString histogram, const TString& inputTags) {
TQTaggable taggable(inputTags);
return plot(histogram, taggable);
}
TCanvas * TQROOTPlotter::plot(TString histogram, TQTaggable* inputTags) {
TQTaggable tags;
tags.importTags(inputTags);
TCanvas* c = this->plot(histogram, tags);
return c;
}
TCanvas * TQROOTPlotter::plot(TString histogram, TQTaggable& tags) {
bool verbose = tags.getTagBoolDefault("verbose",false);
if(verbose) VERBOSEclass("TQROOTPlotter::plot");
this->deleteObjects();
this->clearObjects();
if(tags.getTagBoolDefault("printProcesses",false)){
this->printProcesses();
}
TQTaggable copyOfTags(tags);
if(copyOfTags.getTagBoolDefault("useNamePrefix",true)){
TString tmp(histogram);
TString prefix = TQFolder::getPathTail(tmp);
TQStringUtils::ensureTrailingText(prefix,".");
copyOfTags.importTagsWithoutPrefix(tags,prefix);
}
copyOfTags.setGlobalOverwrite(false);
copyOfTags.importTags(this);
if(histogram.Contains("=")){
copyOfTags.importTagsWithPrefix(histogram,"input");
} else {
copyOfTags.setTagString("input.histogram",histogram);
}
if(copyOfTags.getTagBoolDefault("printTags",false)){
copyOfTags.printTags();
}
TCanvas* c = this->makePlot(copyOfTags);
if(copyOfTags.getTagBoolDefault("printObjects",false)){
std::cout << TQStringUtils::makeBoldBlue(this->Class()->GetName()) << TQStringUtils::makeBoldWhite(" - objects:") << std::endl;
this->printObjects();
}
if(copyOfTags.getTagBoolDefault("printLegend",false)){
std::cout << TQStringUtils::makeBoldBlue("TQPlotter") << TQStringUtils::makeBoldWhite(" - legend entries:") << std::endl;
TLegend* leg = this->getObject<TLegend>("legend");
if(!leg){
ERRORclass("no legend found!");
} else {
TQLegendEntryIterator itr(leg->GetListOfPrimitives());
while(itr.hasNext()){
TLegendEntry* entry = itr.readNext();
if(!entry) continue;
TObject* obj = entry->GetObject();
if(obj){
std::cout << TQStringUtils::makeBoldBlue(TQStringUtils::fixedWidth(obj->Class()->GetName(),20)) << TQStringUtils::makeBoldWhite(TQStringUtils::fixedWidth(obj->GetName(),20))<< '"' << TQStringUtils::makeBoldWhite(entry->GetLabel()) << '"' << std::endl;
} else {
std::cout << TQStringUtils::makeBoldRed(TQStringUtils::fixedWidth("NULL",40)) << '"' << TQStringUtils::makeBoldWhite(entry->GetLabel()) << '"' << std::endl;
}
}
}
}
if(tags.getTagBoolDefault("printStyle",false))
copyOfTags.printTags();
return c;
}
TPad * TQROOTPlotter::getPad(const TString& name){
if(!this->pads) return NULL;
if(name.IsNull()) return NULL;
TQIterator itr(this->pads);
while(itr.hasNext()){
TObject* obj = itr.readNext();
if(!obj) continue;
if(!TQStringUtils::matches(obj->GetName(),name)) continue;
TPad* p = dynamic_cast<TPad*>(obj);
if(!p) return NULL;
p->cd();
return p;
}
return NULL;
}
TCanvas * TQROOTPlotter::plot(TString histogram, const char* inputTags) {
TQTaggable taggable(inputTags);
TCanvas * c = plot(histogram, taggable);
return c;
}
bool TQROOTPlotter::plotAndSaveAsInternal(const TString& histogram, const TString& saveAs, TQTaggable& tags) {
bool verbose = tags.getTagBoolDefault("verbose",false);
if(verbose) VERBOSEclass("TQROOTPlotter::plotAndSaveAs");
TDirectory* tmpObjects = NULL;
TDirectory* oldDir = gDirectory;
if(saveAs.EndsWith(".root")){
tmpObjects = this->objects;
if(verbose) VERBOSEclass("opening .root output file");
this->objects = TFile::Open(saveAs,"RECREATE");
}
gDirectory = oldDir;
TCanvas * canvas = plot(histogram, tags);
bool success = canvas;
if(success && tags.getTagBoolDefault("sortObjects",false)){
if(verbose) VERBOSEclass("sorting objects");
TQListUtils::sortByName(const_cast<TList*>(canvas->GetListOfPrimitives()));
TQIterator paditr(this->pads);
while(paditr.hasNext()){
TPad* pad = dynamic_cast<TPad*>(paditr.readNext());
if(!pad) continue;
TList* primitives = const_cast<TList*>(pad->GetListOfPrimitives());
if(!primitives) continue;
TQListUtils::sortByName(primitives);
}
}
if (success && !tmpObjects) {
this->pads->SetOwner(true);
int capturingSuccessful = TQLibrary::captureStdout();
canvas->SaveAs(saveAs.Data());
if (capturingSuccessful == 0){
TString output = TQLibrary::readCapturedStdout();
TQLibrary::restore_stdout();
if (!TQROOTPlotter::isDefaultPlotMessage(output, saveAs)){
WARNclass("Unexpected output from ROOT:");
std::cout << output.Data();
std::cout.flush();
}
}
if(saveAs.EndsWith(".pdf") && tags.getTagBoolDefault("embedfonts",false)){
TQLibrary::embedFonts(saveAs);
}
if(saveAs.EndsWith(".pdf") || saveAs.EndsWith(".jpg") || saveAs.EndsWith(".png")){
TString exifinfostring = histogram;
this->getTagString("exiftitle",exifinfostring);
tags.getTagString("exiftitle",exifinfostring);
if(TQLibrary::hasEXIFsupport() && !TQLibrary::setEXIF(saveAs,exifinfostring)){
ERRORclass("setting EXIF meta-information on %s failed!",saveAs.Data());
}
}
}
if(success && tmpObjects){
this->objects->Add(canvas);
this->objects->Add(this->pads);
this->objects->Write();
this->objects->Close();
this->objects = tmpObjects;
this->pads = new TObjArray();
std::cout << "Info in " << this->IsA()->GetName() << ": created file " << saveAs << std::endl;
this->clearObjects();
} else if(tags.getTagBoolDefault("deleteObjects",true)){
this->deleteObjects();
delete canvas;
}
return success;
}
TQROOTPlotter::~TQROOTPlotter() {
}
void TQROOTPlotter::setStyleAtlas() {
int icol = 0;
gStyle->SetFrameBorderMode(icol);
gStyle->SetFrameFillColor(icol);
gStyle->SetCanvasBorderMode(icol);
gStyle->SetCanvasColor(icol);
gStyle->SetPadBorderMode(icol);
gStyle->SetPadColor(icol);
gStyle->SetStatColor(icol);
gStyle->SetPaperSize(20,26);
gStyle->SetPadTopMargin(0.05);
gStyle->SetPadRightMargin(0.05);
gStyle->SetPadBottomMargin(0.16);
gStyle->SetPadLeftMargin(0.16);
gStyle->SetTitleXOffset(1.4);
gStyle->SetTitleYOffset(1.4);
int font=42;
double tsize=0.05;
gStyle->SetTextFont(font);
gStyle->SetTextSize(tsize);
gStyle->SetLabelFont(font,"x");
gStyle->SetTitleFont(font,"x");
gStyle->SetLabelFont(font,"y");
gStyle->SetTitleFont(font,"y");
gStyle->SetLabelFont(font,"z");
gStyle->SetTitleFont(font,"z");
gStyle->SetLabelSize(tsize,"x");
gStyle->SetTitleSize(tsize,"x");
gStyle->SetLabelSize(tsize,"y");
gStyle->SetTitleSize(tsize,"y");
gStyle->SetLabelSize(tsize,"z");
gStyle->SetTitleSize(tsize,"z");
gStyle->SetMarkerStyle(20);
gStyle->SetMarkerSize(1.2);
gStyle->SetHistLineWidth((Width_t)2.);
gStyle->SetLineStyleString(2,"[12 12]");
gStyle->SetOptTitle(0);
gStyle->SetOptStat(0);
gStyle->SetOptFit(0);
gStyle->SetPadTickX(1);
gStyle->SetPadTickY(1);
}
void TQROOTPlotter::clearObjects(){
this->pads->SetOwner(false);
this->pads->Clear();
TQPlotter::clearObjects();
}
void TQROOTPlotter::deleteObjects(){
this->pads->SetOwner(true);
this->pads->Clear();
TQPlotter::deleteObjects();
}
TPad* TQROOTPlotter::createPad(TQTaggable& tags, const TString& key){
double padscaling = tags.getTagDoubleDefault("geometry."+key+".scaling",1.);
double ymin = tags.getTagDoubleDefault("geometry."+key+".yMin",0.);
double ymax = tags.getTagDoubleDefault("geometry."+key+".yMax",ymin + tags.getTagDoubleDefault("geometry."+key+".height",1.));
TPad * pad = new TPad(key,key,
tags.getTagDoubleDefault("geometry."+key+".xMin",0.),
ymin,
tags.getTagDoubleDefault("geometry."+key+".xMax",1.),
ymax);
pad->SetFillStyle(tags.getTagIntegerDefault("style."+key+".fillStyle",0));
pad->SetFillColor(tags.getTagIntegerDefault("style."+key+".fillColor",0));
pad->SetMargin(tags.getTagDoubleDefault("geometry."+key+".margins.left" ,0.16),
tags.getTagDoubleDefault("geometry."+key+".margins.right" ,0.05),
padscaling*tags.getTagDoubleDefault("geometry."+key+".margins.bottom",0.16),
padscaling*tags.getTagDoubleDefault("geometry."+key+".margins.top" ,0.05));
pad->SetTickx(tags.getTagIntegerDefault("style."+key+".tickx",tags.getTagIntegerDefault("style.tickx",1)));
pad->SetTicky(tags.getTagIntegerDefault("style."+key+".ticky",tags.getTagIntegerDefault("style.ticky",1)));
pad->SetBorderSize(tags.getTagIntegerDefault("style."+key+".borderSize",0));
pad->SetBorderMode(tags.getTagIntegerDefault("style."+key+".borderMode",0));
pad->SetLogy(tags.getTagBoolDefault ("style."+key+".logScaleY", false));
pad->SetGridy(tags.getTagBoolDefault("style."+key+".gridy",false));
pad->SetGridx(tags.getTagBoolDefault("style."+key+".gridx",false));
this->pads->Add(pad);
return pad;
}
TCanvas* TQROOTPlotter::createCanvas(TQTaggable& tags){
bool verbose = tags.getTagBoolDefault("verbose",false);
TString canvasName = tags.getTagStringDefault("input.name","histogram");
canvasName.ReplaceAll("/", "_");
canvasName=TQStringUtils::makeValidIdentifier(canvasName,"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890._","");
int iCanvas = 1;
TCollection * listOfCanvases = gROOT->GetListOfCanvases();
const TString origCanvasName(canvasName);
while (listOfCanvases && listOfCanvases->FindObject(canvasName.Data()))
canvasName = TString::Format("%s_n%d", origCanvasName.Data(), iCanvas++);
if(verbose) VERBOSEclass("creating canvas with name '%s'",canvasName.Data());
int width = tags.getTagIntegerDefault("geometry.canvas.width",800);
int height = tags.getTagIntegerDefault("geometry.canvas.height",600);
TCanvas* canvas = new TCanvas(TQFolder::makeValidIdentifier(canvasName),canvasName,0,0,width,height);
canvas->SetWindowSize(width + (width - canvas->GetWw()), height + (height - canvas->GetWh()));
canvas->SetMargin(0.,0.,0.,0);
canvas->cd();
TPad* pad = this->createPad(tags,"main");
if (tags.getTagBoolDefault ("style.logScale",false ) && (
(tags.getTagIntegerDefault("style.nDim",1) == 1) || (tags.getTagIntegerDefault("style.nDim",1) == -1)
) ){
pad->SetLogy();
}
if (tags.getTagBoolDefault ("style.logScaleX",false )){
pad->SetLogx();
}
pad->Draw();
if (tags.getTagBoolDefault ("style.showSub",false)){
canvas->cd();
TPad * ratioPad = this->createPad(tags,"sub");
if (tags.getTagBoolDefault ("style.logScaleX",false )){
ratioPad->SetLogx();
}
ratioPad->Draw();
}
canvas->cd();
return canvas;
}
void TQROOTPlotter::drawCutLines1D(TQTaggable& tags){
TH1* hMaster = this->getObject<TH1>("Graph_master");
double upper = hMaster->GetMaximum();
double lower = hMaster->GetMinimum();
bool logScale = tags.getTagBoolDefault ("style.logScale",false );
int iCut = 0;
double threshold = 0.;
while (tags.getTagDouble(TString::Format("cuts.%d", iCut++), threshold)) {
int iBlock = 0;
double block_x = 0;
double block_x_old = 0;
double block_y = 1;
while(tags.getTag(TString::Format("blocks.x.%d",iBlock),block_x) && tags.getTag(TString::Format("blocks.y.%d",iBlock),block_y)){
if(threshold > block_x_old){
break;
}
block_x_old = block_x;
iBlock++;
}
auto cutLineHeightScaleFactor = tags.getTagDoubleDefault("style.cutLineHeightScaleFactor",1.0);
block_y *= cutLineHeightScaleFactor;
double max = logScale ? TMath::Exp((TMath::Log(upper) - TMath::Log(lower)) * block_y + TMath::Log(lower)) : (upper - lower) * block_y + lower;
TLine * line = new TLine(threshold, lower, threshold, max);
line->SetLineStyle(tags.getTagIntegerDefault("style.cutLineStyle",7));
line->SetLineWidth(tags.getTagIntegerDefault("style.cutLineWidth",2));
line->SetLineColor(tags.getTagIntegerDefault("style.cutLineColor",kRed));
line->Draw();
bool arrowsToDraw = tags.getTagBoolDefault("style.cutArrows",false);
if (arrowsToDraw){
auto arrowDirection = tags.getTagStringDefault("style.cutLineArrowDirection", "left");
double arrow_lo = 0.;
double arrow_hi = 0.;
TString arrowDir;
if (arrowDirection == "left") {
arrow_lo = threshold*0.8;
arrow_hi = threshold;
arrowDir = "<";
}
else if (arrowDirection == "right") {
arrow_lo = threshold;
arrow_hi = threshold*1.2;
arrowDir = ">";
}
TArrow * arrow = new TArrow(arrow_lo, max, arrow_hi, max, 0.015, arrowDir);
arrow->SetLineWidth(tags.getTagIntegerDefault("style.cutLineWidth",2));
arrow->SetLineStyle(1);
arrow->SetLineColor(tags.getTagIntegerDefault("style.cutLineColor",kBlack));
arrow->Draw();
}
}
}
int TQROOTPlotter::drawHeightLines(TQTaggable& tags){
if(!tags.getTagBoolDefault("heightlines.show",false))
return 0;
bool verbose = tags.getTagBoolDefault("verbose",false);
if(verbose) VERBOSEclass("attempting to draw height lines");
TH1* hMaster = this->getObject<TH1>("Graph_master");
double xCoeff = tags.getTagDoubleDefault("heightlines.xCoeff",0.);
double yCoeff = tags.getTagDoubleDefault("heightlines.yCoeff",0.);
double constCoeff = tags.getTagDoubleDefault("heightlines.constCoeff",0.);
bool rotate = tags.getTagBoolDefault("heightlines.rotateLabels",true);
double labelSize = tags.getTagDoubleDefault("style.axes.labelSize",0.03);
double labelOffset = tags.getTagDoubleDefault("style.axes.labelOffset",0.005);
int color = tags.getTagIntegerDefault("heightlines.color",kBlack);
int linestyle = tags.getTagIntegerDefault("heightlines.style",1.);
std::vector<double> vals = tags.getTagVDouble("heightlines.values");
double xmin = TQHistogramUtils::getAxisXmin(hMaster);
double xmax = TQHistogramUtils::getAxisXmax(hMaster);
int n = 0;
double slope = - xCoeff/yCoeff;
TLatex latex;
latex.SetTextColor(color);
latex.SetTextSize(labelSize);
double latexXOffset, latexYOffset;
if(rotate){
double visualSlope = - TQUtils::convertdYtoPixels(xCoeff)/TQUtils::convertdXtoPixels(yCoeff);
double visualAngle = atan(visualSlope) * 180./TMath::Pi();
latex.SetTextAngle(visualAngle);
latexXOffset = TQUtils::convertdXfromNDC(labelOffset * sin(-visualSlope));
latexYOffset = TQUtils::convertdYfromNDC(labelOffset * cos( visualSlope));
} else {
latexXOffset = 0.;
latexYOffset = TQUtils::convertdYfromNDC(labelOffset);
}
for(size_t i=0; i<vals.size(); i++){
if(verbose) VERBOSEclass("drawing height line for z = %g",vals[i]);
double offset = (vals[i] - constCoeff) / yCoeff;
double y0 = offset + slope * xmin;
double y1 = offset + slope * xmax;
double x0 = xmin;
double x1 = xmax;
if(verbose) VERBOSEclass("pre-crop coordinates are x0=%g, x1=%g, y0=%g, y1=%g",x0,x1,y0,y1);
TLine* l = new TLine(x0,y0,x1,y1);
if(TQHistogramUtils::cropLine(hMaster,l)){
if(verbose) VERBOSEclass("post-crop coordinates are x0=%g, x1=%g, y0=%g, y1=%g",l->GetX1(),l->GetX2(),l->GetY1(),l->GetY2());
l->SetLineColor(color);
l->SetLineStyle(linestyle);
l->Draw();
latex.DrawLatex(latexXOffset + 0.5*(l->GetX2()+l->GetX1()),latexYOffset + 0.5*(l->GetY2()+l->GetY1()),TString::Format("%g",vals[i]));
n++;
} else {
if(verbose) VERBOSEclass("line-crop failed - no line plotted");
delete l;
}
}
return n;
}
int TQROOTPlotter::drawAdditionalAxes(TQTaggable& tags){
double defaultLabelSize = tags.getTagDoubleDefault("style.axes.labelSize",0.03);
double defaultTitleSize = tags.getTagDoubleDefault("style.axes.titleSize",0.03);
double defaultTitleOffset = tags.getTagDoubleDefault("style.axes.titleOffset",1.0);
int iAxis = -1;
bool show = false;
while (tags.getTagBool(TString::Format("axis.%d.show", ++iAxis), show)) {
if(!show) continue;
double xmin, xmax, ymin, ymax;
if(!tags.getTagDouble(TString::Format("axis.%d.xMin", iAxis), xmin)) continue;
if(!tags.getTagDouble(TString::Format("axis.%d.xMax", iAxis), xmax)) continue;
if(!tags.getTagDouble(TString::Format("axis.%d.yMin", iAxis), ymin)) continue;
if(!tags.getTagDouble(TString::Format("axis.%d.yMax", iAxis), ymax)) continue;
double wmin = tags.getTagDoubleDefault (TString::Format("axis.%d.wMin" , iAxis), 0);
double wmax = tags.getTagDoubleDefault (TString::Format("axis.%d.wMax" , iAxis), 1);
int nDiv = tags.getTagIntegerDefault (TString::Format("axis.%d.nDiv" , iAxis), 110);
TString title = tags.getTagStringDefault(TString::Format("axis.%d.title", iAxis), "");
double labelSize = tags.getTagDoubleDefault (TString::Format("axis.%d.labelSize", iAxis), defaultLabelSize);
double titleSize = tags.getTagDoubleDefault (TString::Format("axis.%d.titleSize", iAxis), defaultTitleSize);
double titleOffset = tags.getTagDoubleDefault (TString::Format("axis.%d.titleOffset", iAxis), defaultTitleOffset);
TGaxis *addAxis = new TGaxis(xmin, ymin, xmax, ymax,wmin,wmax,nDiv);
if(!title.IsNull()) addAxis->SetTitle(title);
addAxis->SetLabelSize(labelSize);
addAxis->SetTitleSize(titleSize);
addAxis->SetTitleOffset(titleOffset);
addAxis->Draw();
show = false;
}
return iAxis;
}
bool TQROOTPlotter::isDefaultPlotMessage(TString message, const TString& filename){
if (message.EqualTo(""))
return true;
if (!(message.CountChar(10) == 1))
return false;
if ((filename.EndsWith(".C")) || (filename.EndsWith(".cxx")) || (filename.EndsWith(".json"))){
if ((TQStringUtils::removeLeadingText(message, "TCanvas::SaveSource") == 1)
&& (TQStringUtils::removeLeadingBlanks(message) > 0)
&& (TQStringUtils::removeLeadingText(message, "INFO") == 1)
&& (message.EndsWith("has been generated\n"))){
return true;
}
}
else if (filename.EndsWith(".root") || filename.EndsWith(".xml")){
if ((TQStringUtils::removeLeadingText(message, "TCanvas::SaveAs") == 1)
&& (TQStringUtils::removeLeadingBlanks(message) > 0)
&& (TQStringUtils::removeLeadingText(message, "INFO") == 1)
&& (message.EndsWith("has been created\n"))){
return true;
}
}
else{
if ((TQStringUtils::removeLeadingText(message, "TCanvas::Print") == 1)
&& (TQStringUtils::removeLeadingBlanks(message) > 0)
&& (TQStringUtils::removeLeadingText(message, "INFO") == 1)
&& (message.EndsWith("has been created\n"))){
return true;
}
}
return false;
}
void TQROOTPlotter::stackHistograms(TQTaggable& tags, const TString& stackname){
bool verbose = tags.getTagBoolDefault("verbose",false );
if(verbose) VERBOSEclass("stacking histograms");
bool normalize = tags.getTagBoolDefault("normalize",false ) || tags.getTagBoolDefault("normalizeWithoutOverUnderflow",false );
bool normalizeWithoutOverUnderflow = !tags.getTagBoolDefault("normalizeWithoutOverUnderflow",false );
TH1* hMaster = this->getObject<TH1>("Graph_master");
TH1* hTotalStack = TQHistogramUtils::copyHistogram(hMaster,"totalStack");
hTotalStack->SetTitle("SM");
hTotalStack->SetLineColor(kBlue);
hTotalStack->SetFillColor(14);
hTotalStack->SetFillStyle(3245);
hTotalStack->SetMarkerStyle(0);
TH1* hTotalBkg = TQHistogramUtils::copyHistogram(hTotalStack,"totalBkg");
std::vector<TH1*> otherHistList;;
std::vector<TH1*> histStackList;
std::vector<TQNamedTaggable*> processStackList;
TQTaggableIterator itr(this->fProcesses);
while(itr.hasNext()){
TQNamedTaggable* process = itr.readNext();
if(!process) continue;
TH1 * h = this->getObject<TH1>(this->makeHistogramIdentifier(process));
if(!h) continue;
bool bkg = process->getTagBoolDefault(".isBackground",false);
bool sig = process->getTagBoolDefault(".isSignal",false);
bool bkgStack = bkg && process->getTagBoolDefault("stack", true);
bool sigStack = sig && tags.getTagBoolDefault ("style.autoStackSignal",false);
if(bkg) hTotalBkg->Add(h);
if(bkgStack || sigStack){
histStackList.push_back(h);
processStackList.push_back(process);
hTotalStack->Add(h);
} else {
otherHistList.push_back(h);
}
}
double totalBkgScale = 0;
if (hTotalBkg){
totalBkgScale = TQHistogramUtils::getIntegral(hTotalBkg, normalizeWithoutOverUnderflow);
}
if (normalize){
if (hTotalBkg && totalBkgScale > 0.){
hTotalBkg->Scale(1. / totalBkgScale);
for(auto h:histStackList){
h->Scale(1. / totalBkgScale);
}
}
for(auto h:otherHistList){
if (TQHistogramUtils::getIntegral(h, normalizeWithoutOverUnderflow) > 0) h->Scale(1./TQHistogramUtils::getIntegral(h, normalizeWithoutOverUnderflow));
}
}
if (!tags.getTagBoolDefault("style.manualStacking",false) && tags.getTagBoolDefault("style.autoStack",tags.getTagBoolDefault("style.logScale",false))){
for (unsigned iHist = 0; iHist < histStackList.size(); iHist++) {
unsigned iHistMin = iHist;
double intMin = ((TH1*)histStackList.at(iHistMin))->GetSumOfWeights();
for (unsigned iHist2 = iHist + 1; iHist2 < histStackList.size(); iHist2++) {
double intMin2 = ((TH1*)histStackList.at(iHist2))->GetSumOfWeights();
if (intMin2 < intMin) {
iHistMin = iHist2;
intMin = intMin2;
}
}
if (iHistMin != iHist) {
TH1 * temp = (TH1*)(histStackList)[iHist];
(histStackList)[iHist] = (histStackList)[iHistMin];
(histStackList)[iHistMin] = temp;
TQNamedTaggable * temptag = (processStackList)[iHist];
(processStackList)[iHist] = (processStackList)[iHistMin];
(processStackList)[iHistMin] = temptag;
}
}
}
THStack * stack = new THStack(stackname, tags.getTagBoolDefault("style.stackSignal",false) ? "Signal+Background Stack" : "Background Stack");
if (tags.getTagBoolDefault ("style.reverseStacking",false )) {
for (int iHist = histStackList.size(); iHist > 0 ; iHist--){
TH1* h = (histStackList.at(iHist-1));
stack->Add(h);
}
} else {
for (unsigned iHist = 0; iHist < histStackList.size(); iHist++){
TH1* h = dynamic_cast<TH1*>(histStackList.at(iHist));
stack->Add(h);
}
}
TQTaggableIterator sitr(this->fProcesses);
bool stackSignal = tags.getTagBoolDefault ("style.stackSignal",false);
if (tags.getTagBoolDefault ("style.autoStackSignal",false)) stackSignal = false;
while(sitr.hasNext()){
TQNamedTaggable* process = sitr.readNext();
if(!process) continue;
if(!process->getTagBoolDefault(".isSignal",false)) continue;
TH1 * h = this->getObject<TH1>(this->makeHistogramIdentifier(process));
if(!h) continue;
if(totalBkgScale > 0 && process->getTagBoolDefault("normalizeToTotalBkg",false) && !normalize){
h->Scale(totalBkgScale / TQHistogramUtils::getIntegral(h));
}
if (process->getTagBoolDefault("stack", stackSignal)){
stack->Add(h);
hTotalStack->Add(h);
}
}
if(stack->GetNhists() > 0){
if(verbose) VERBOSEclass("successfully stacked %d histograms",stack->GetNhists());
this->addObject(stack,stackname);
} else {
DEBUGclass("stack is empty!");
delete stack;
}
}
void TQROOTPlotter::addObject(TLegend* obj, const TString& key){
if(!obj) return;
if(!key.IsNull()) obj->SetName(key);
this->objects->Add(obj);
}
void TQROOTPlotter::makeLegend(TQTaggable& tags, TObjArray* histos){
bool showEventYields = tags.getTagBoolDefault ("style.showEventYields",false);
int nLegendCols = tags.getTagIntegerDefault ("style.nLegendCols",showEventYields ? 1 : 2);
bool showTotalBkg = tags.getTagBoolDefault ("style.showTotalBkg",true);
double legendHeight = tags.getTagDoubleDefault ("geometry.legend.height",1. );
double scaling = tags.getTagDoubleDefault("geometry.main.scaling",1.);
bool drawData = tags.getTagBoolDefault ("style.drawData",true);
bool verbose = tags.getTagBoolDefault("verbose",false);
std::vector<TString> showDataAtCuts;
if (drawData) {
tags.getTag("showDataAtCuts", showDataAtCuts);
}
TString histogramTag = tags.getTagStringDefault("input.histogram", "");
TString cutName(histogramTag(0, histogramTag.First('/')));
bool drawDataThisCut = (showDataAtCuts.size() == 0);
if (std::find(showDataAtCuts.begin(), showDataAtCuts.end(), cutName) != showDataAtCuts.end()) {
drawDataThisCut = true;
}
int nEntries = 0;
bool showMissing = tags.getTagBoolDefault ("style.showMissing",true );
nEntries += (showMissing ? histos->GetEntriesFast() : histos->GetEntries());
TH1* hTotalStack = this->getObject<TH1>("totalStack");
if (showTotalBkg && (hTotalStack || (showMissing && this->getNProcesses(".isBackground") > 0))) nEntries++;
int nLegendRows = (int)nEntries / nLegendCols + ((nEntries % nLegendCols) > 0 ? 1 : 0);
TLegend* legend = NULL;
bool legpad = tags.getTagBoolDefault("style.useLegendPad",false);
if(legpad){
if(verbose) VERBOSEclass("creating legend with unity coordinates");
legend = new TLegend(tags.getTagDoubleDefault("geometry.legend.margins.left",0),
tags.getTagDoubleDefault("geometry.legend.margins.bottom",0),
1.-tags.getTagDoubleDefault("geometry.legend.margins.right",0),
1.-tags.getTagDoubleDefault("geometry.legend.margins.top",0));
legend->SetFillColor(0);
legend->SetFillStyle(0);
} else {
double x1 = tags.getTagDoubleDefault("geometry.legend.xMin",0.59);
double y1 = tags.getTagDoubleDefault("geometry.legend.yMin",0.75);
double x2 = tags.getTagDoubleDefault("geometry.legend.xMax",0.90);
double y2 = tags.getTagDoubleDefault("geometry.legend.yMax",0.92);
y1 = y2 - (y2 - y1) * scaling;
legendHeight *= (y2 - y1) * (double)nLegendRows / tags.getTagDoubleDefault("geometry.legend.nRows",5.);
y1 = y2 - legendHeight;
double tmpx1 = x1;
double tmpx2 = x2;
double tmpy1 = y1;
double tmpy2 = y2;
if(verbose) VERBOSEclass("creating legend with coordinates %g/%g - %g/%g",tmpx1,tmpy1,tmpx2,tmpy2);
legend = new TLegend(tmpx1, tmpy1, tmpx2, tmpy2);
legend->SetFillColor(tags.getTagIntegerDefault("style.legend.fillColor",0));
legend->SetFillStyle(tags.getTagIntegerDefault("style.legend.fillStyle",0));
}
legend->SetBorderSize(0);
legend->SetNColumns(nLegendCols);
double textsize = tags.getTagDoubleDefault("style.legend.textSize",0.45*tags.getTagDoubleDefault("style.textSize",0.0375));
if (tags.getTagBoolDefault ("style.legend.textSizeFixed", false))
legend->SetTextSize(textsize);
else
legend->SetTextSize(textsize * scaling);
bool statMcErrors = tags.getTagBoolDefault("errors.drawStatMC",true );
bool sysMcErrors = tags.getTagBoolDefault("errors.drawSysMC",false ) || tags.hasTag("errors.showSys");
TH1* hTotalStackError = NULL;
if (statMcErrors || sysMcErrors) {
hTotalStackError = TQHistogramUtils::copyHistogram(hTotalStack,"totalStackError");
if(hTotalStackError){
hTotalStackError->Reset();
hTotalStackError->SetTitle("total background error (legend dummy)");
this->applyStyle (tags,hTotalStackError,"main.totalStackError");
if(verbose){ DEBUGclass("totalStackError style: %s",TQHistogramUtils::getDetailsAsString(hTotalStackError,5).Data()); }
}
}
TString totalStackLabel = tags.getTagStringDefault ("labels.totalStack", "SM");
TString legendTotalBkgLabel = tags.getTagStringDefault ("labels.totalStack", "SM");
legendTotalBkgLabel = " " + tags.getTagStringDefault ("labels.legendTotalBkg", legendTotalBkgLabel);
if(tags.getTagBoolDefault("legend.showTotalBkgErrorType",true)){
if (statMcErrors && sysMcErrors)
legendTotalBkgLabel.Append(" (sys #oplus stat)");
else if (sysMcErrors)
legendTotalBkgLabel.Append(" (sys)");
else if (statMcErrors)
legendTotalBkgLabel.Append(" (stat)");
}
if (tags.getTagBoolDefault("isCompPlot",false))
addAllHistogramsToLegend(tags,legend, ".isBackground", tags.getTagStringDefault("legend.dataDisplayType",".legendOptions='lep'"));
if(!tags.getTagBoolDefault("style.unsorted",false) || tags.getTagBoolDefault("style.listDataFirst", false)){
if (drawData && drawDataThisCut) {
addAllHistogramsToLegend(tags,legend, ".isData", tags.getTagStringDefault("legend.dataDisplayType",".legendOptions='lep'"));
}
}
if (showTotalBkg) {
if (hTotalStackError){
if(verbose) VERBOSEclass("adding total stack error to legend");
TString opt = tags.getTagStringDefault("legend.errorDisplayType","lf");
if(!opt.Contains("l")){
hTotalStackError->SetLineColor(0);
hTotalStackError->SetLineStyle(0);
hTotalStackError->SetLineWidth(0);
}
DEBUGclass(TQHistogramUtils::getDetailsAsString(hTotalStackError,5));
legend->AddEntry(hTotalStackError, legendTotalBkgLabel,opt);
} else {
if(verbose) VERBOSEclass("no total stack error found");
if (showMissing && this->getNProcesses(".isBackground") > 0){
legend->AddEntry((TObject*)NULL,"","");
}
}
}
bool stackSignal = tags.getTagBoolDefault ("style.stackSignal",false);
bool autoStackSignal = tags.getTagBoolDefault ("style.autoStackSignal",false);
bool listSignalFirst = tags.getTagBoolDefault ("style.listSignalFirst",false);
bool showSignal = tags.getTagBoolDefault ("style.showSignalInLegend",true);
if(tags.getTagBoolDefault("style.unsorted",false)){
if (tags.getTagBoolDefault("style.listDataFirst", false)){
addAllHistogramsToLegend(tags,legend, ".isBackground");
addAllHistogramsToLegend(tags,legend, ".isSignal");
}
else{
addAllHistogramsToLegend(tags,legend, "");
}
} else {
if (!tags.getTagBoolDefault ("style.manualStacking", false)) {
if(!tags.getTagBoolDefault("style.autoStackLegend",false)){
if(verbose) VERBOSEclass("generating legend in default mode");
if(listSignalFirst){
if (showSignal)
addAllHistogramsToLegend(tags,legend, ".isSignal");
if (!tags.getTagBoolDefault("isCompPlot",false))
addAllHistogramsToLegend(tags,legend, ".isBackground");
} else {
if (!tags.getTagBoolDefault("isCompPlot",false))
addAllHistogramsToLegend(tags,legend, ".isBackground");
if (showSignal)
addAllHistogramsToLegend(tags,legend, ".isSignal");
}
} else {
THStack* stack = this->getObject<THStack>("stack");
if(!stack){
if(verbose){
VERBOSEclass("cannot generate legend in auto-stack mode - no stack!");
}
return;
} else {
if(verbose) VERBOSEclass("generating legend in auto-stack mode");
if (!stackSignal && listSignalFirst && showSignal && !autoStackSignal) {
addAllHistogramsToLegend(tags,legend, ".isSignal");
}
TQTH1Iterator itr(stack->GetHists()->MakeIterator(kIterBackward),true);
while(itr.hasNext()){
TH1* hist = itr.readNext();
addHistogramToLegend(tags,legend,hist);
}
if (!stackSignal && !listSignalFirst && showSignal && !autoStackSignal) {
addAllHistogramsToLegend(tags,legend, ".isSignal");
}
}
}
} else {
if (stackSignal) {
if(verbose) VERBOSEclass("generating legend in manual stacking mode - stackSignal=true");
if (listSignalFirst) {
if (showSignal)
addAllHistogramsToLegend(tags,legend, ".isSignal","",true);
addAllHistogramsToLegend(tags,legend, ".isBackground","",true);
} else {
addAllHistogramsToLegend(tags,legend, ".isBackground","",true);
addAllHistogramsToLegend(tags,legend, ".isSignal","",true);
}
} else {
if(verbose) VERBOSEclass("generating legend in manual stacking mode - stackSignal=false");
if (listSignalFirst) {
if (showSignal)
addAllHistogramsToLegend(tags,legend, ".isSignal");
addAllHistogramsToLegend(tags,legend, ".isBackground","",true);
} else {
addAllHistogramsToLegend(tags,legend, ".isBackground","",true);
if (showSignal)
addAllHistogramsToLegend(tags,legend, ".isSignal");
}
}
}
}
this->addObject(legend,"legend");
}
bool TQROOTPlotter::drawHeatmap(TQTaggable& tags){
TString heatmap = "totalStack";
TString overlay = "totalSig";
TString overlayAdditional = "totalSig";
if(!tags.getTagString("style.heatmap",heatmap)) return false;
bool normalize = tags.getTagBoolDefault("normalizeheatmap",false );
TPad* pad = this->getPad("main");
pad->cd();
pad->SetRightMargin(tags.getTagDoubleDefault("geometry.main.rightMargin", 0.11));
if (tags.getTagBoolDefault("style.drawGrid",false)) pad->SetGrid(1,1);
TH2* hMaster = this->getObject<TH2>("Graph_master");
TH2* hHeatmap = this->getObject<TH2>(heatmap);
TH2* hOverlay = NULL;
TH2* hoverlayAdditional = NULL;
bool verbose = tags.getTagBoolDefault("verbose",false);
bool doOverlay = tags.getTagString("style.heatmapoverlay",overlay);
bool dooverlayAdditional = tags.getTagString("style.heatmapoverlayAdditional",overlayAdditional);
if(verbose) VERBOSEclass("making Heatmap ");
if (normalize){
if (hHeatmap){
double totalScale = TQHistogramUtils::getIntegral(hHeatmap);
hHeatmap->Scale(1. / totalScale * 100);
}
if (hOverlay){
double totalScale2 = TQHistogramUtils::getIntegral(hOverlay);
hOverlay->Scale(1. / totalScale2 * 100);
}
}
bool logScaleZ = tags.getTagBoolDefault("style.logScaleZ",false);
if (logScaleZ) {
pad->SetLogz();
}
if(doOverlay){
if(verbose) VERBOSEclass("retrieving overlay histogram by name '%s'",overlay.Data());
hOverlay = this->getObject<TH2>(overlay);
}
if(dooverlayAdditional){
if(verbose) VERBOSEclass("retrieving overlay histogram by name '%s'",overlayAdditional.Data());
hoverlayAdditional = this->getObject<TH2>(overlayAdditional);
}
if(hHeatmap){
hMaster->SetMaximum(hHeatmap->GetMaximum());
hMaster->SetMinimum(hHeatmap->GetMinimum());
}
if(verbose) VERBOSEclass("drawing master histogram");
hMaster->Draw("HIST");
if(hHeatmap){
hHeatmap->SetFillStyle(0);
hHeatmap->SetLineColor(kBlue-4);
if(verbose) VERBOSEclass("drawing histogram '%s' as heatmap",heatmap.Data());
TString ZAxis_title = "Events";
bool Zaxistitle = tags.getTagString("style.Zaxistitle",ZAxis_title);
hHeatmap->GetZaxis()->SetTitleOffset(1.1);
if (Zaxistitle) {hHeatmap->GetZaxis()->SetTitle(ZAxis_title.Data());}
if(verbose) VERBOSEclass("drawing histogram with title'%s' ",ZAxis_title.Data());
hHeatmap->Draw("COLZ1,SAME");
} else {
if(verbose){
VERBOSEclass("cannot draw '%s' as heatmap - object not found",heatmap.Data());
this->printObjects();
}
return false;
}
if(hOverlay){
if(verbose) VERBOSEclass("drawing histogram '%s' as heatmap overlay",overlay.Data());
hOverlay->SetFillStyle(0);
hOverlay->SetLineWidth(2);
hOverlay->SetLineStyle(1);
hOverlay->Draw("BOX,SAME");
} else if(doOverlay){
if(verbose){
VERBOSEclass("cannot draw '%s' as heatmap overlay - object not found",overlay.Data());
this->printObjects();
}
}
if(hoverlayAdditional){
if(verbose) VERBOSEclass("drawing histogram '%s' as heatmap overlay",overlay.Data());
hoverlayAdditional->SetFillStyle(0);
hoverlayAdditional->SetLineWidth(2);
hoverlayAdditional->SetLineStyle(1);
hoverlayAdditional->Draw("BOX,SAME");
} else if(dooverlayAdditional){
if(verbose){
VERBOSEclass("cannot draw '%s' as heatmap overlay - object not found",overlay.Data());
this->printObjects();
}
}
return true;
}
bool TQROOTPlotter::drawContours(TQTaggable& tags){
TPad* pad = this->getPad("main");
TH2* hMaster = this->getObject<TH2>("Graph_master");
TObjArray* histos = this->getObject<TObjArray>("histos");
bool verbose = tags.getTagBoolDefault("verbose",false);
double logMin = tags.getTagDoubleDefault("style.logMin",1.);
bool logScale = tags.getTagIntegerDefault("style.logScale",false);
double max = TQHistogramUtils::getMax(histos,false);
if (logScale && tags.hasTagDouble("style.logMinRel") ) logMin = max*tags.getTagDoubleDefault("style.logMinRel",42.);
double min = logScale ? std::max(logMin,TQHistogramUtils::getMin(histos,false)) : TQHistogramUtils::getMin(histos,false);
size_t nContours = tags.getTagIntegerDefault("style.nContours",6);
double step = logScale ? (log(max) - log(min))/(nContours+1) : (max-min)/(nContours+1);
std::vector<double> contours;
for(size_t i=0; i<nContours; i++){
double z_orig = logScale ? min*exp((i+1)*step) : min+(i+1)*step;
double z = TQUtils::roundAuto(z_orig,1);
contours.push_back(z);
}
TObjArray* contourGraphs = new TObjArray();
std::vector<double> contourVals;
double minContourArea = tags.getTagDoubleDefault("style.minContourArea",3*TQHistogramUtils::getMinBinArea(hMaster));
bool batch = gROOT->IsBatch();
gROOT->SetBatch(true);
bool contourLevelsPerHistogram = tags.getTagBoolDefault("style.doContourLevelsPerHistogram",false);
TQIterator histItr(histos);
while(histItr.hasNext()){
TCanvas* tmp = new TCanvas("tmp","tmp");
TH2* hist = dynamic_cast<TH2*>(histItr.readNext());
if(tags.getTagBoolDefault("style.smooth",false)){
hist->Smooth(1,"k5a");
}
if(!hist) continue;
if (contourLevelsPerHistogram) {
contours.clear();
max = TQHistogramUtils::getMax(hist,false,false);
if (logScale && tags.hasTagDouble("style.logMinRel") ) logMin = max*tags.getTagDoubleDefault("style.logMinRel",42.);
min = logScale ? std::max(logMin,TQHistogramUtils::getMin(hist,false,false)) : TQHistogramUtils::getMin(hist,false,false);
size_t nContours = tags.getTagIntegerDefault("style.nContours",2);
double step = logScale ? (log(max) - log(min))/(nContours+1) : (max-min)/(nContours+1);
for(size_t i=0; i<nContours; i++){
double z_orig = logScale ? min*exp((i+1)*step) : min+(i+1)*step;
double z = TQUtils::roundAuto(z_orig,1);
contours.push_back(z);
}
}
hist->SetContour(contours.size(), &contours[0]);
if(verbose) VERBOSEclass("drawing contours for %s",hist->GetName());
hist->Draw("CONT Z LIST");
tmp->Update();
TQIterator contItr2(dynamic_cast<TObjArray*>(gROOT->GetListOfSpecials()->FindObject("contours")));
while(contItr2.hasNext()){
TList* contLevel = dynamic_cast<TList*>(contItr2.readNext());
int idx = contItr2.getLastIndex();
double z0 = contours[idx];
if(verbose) VERBOSEclass("\tretrieving %d contours for level %f",contLevel->GetEntries(),z0);
int nGraphs = 0;
std::vector<double> contourAreas;
std::vector<double> contourIndices;
TQIterator contItr3(contLevel);
while(contItr3.hasNext()){
TGraph* curv = dynamic_cast<TGraph*>(contItr3.readNext());
if(!curv) continue;
double area = fabs(TQHistogramUtils::getContourArea(curv));
double triangle = 0.5*pow(TQHistogramUtils::getContourJump(curv),2);
double val = std::max(area,triangle);
if(verbose) VERBOSEclass("\t\tcontour %d has area=%f and triangle jump=%f -- contour value is %f",nGraphs,area,triangle,val);
contourAreas.push_back(val);
contourIndices.push_back(contItr3.getLastIndex());
nGraphs++;
}
if(verbose) VERBOSEclass("identified %i non-vanishing contours",contourAreas.size());
nGraphs = 0;
int nContoursMax = tags.getTagIntegerDefault("style.contourLimit",7);
while(nGraphs < nContoursMax){
size_t index = 0;
double max = 0;
for(size_t i=0; i<contourAreas.size(); i++){
if(contourAreas[i] > max){
max = contourAreas[i];
index = contourIndices[i];
contourAreas[i] = 0;
}
}
TGraph* curv = dynamic_cast<TGraph*>(contLevel->At(index));
if(max < minContourArea && nGraphs > 0) {
DEBUGclass("removing micro-blob");
break;
}
if(max <= 0) break;
if(!curv) continue;
TGraph* gc = dynamic_cast<TGraph*>(curv->Clone());
if(!gc) continue;
int color = hist->GetFillColor();
int style = 1;
if((color == kWhite) || (color == 0)){
color = hist->GetLineColor();
style = TQStringUtils::equal("hist_data",hist->GetName()) ? 3 : 7;
}
gc->SetLineColor(color);
gc->SetLineStyle(style);
if(tags.getTagBoolDefault("style.contourLines.shade",false)){
gc->SetFillStyle(3004+ (histItr.getLastIndex() % 4));
gc->SetLineWidth(-100
*TQUtils::sgn(tags.getTagIntegerDefault("style.contourLines.shadeDirection",1))
*tags.getTagIntegerDefault("style.contourLines.shadeWidth",3)
+ tags.getTagIntegerDefault("style.contourLines.width",1));
} else {
gc->SetLineWidth(tags.getTagIntegerDefault("style.contourLines.width",1));
}
gc->SetTitle(TString::Format("%s: contour #%d to level %g",hist->GetTitle(),(int)nGraphs,z0));
this->addObject(gc,TString::Format("contour_%d_%s_%g_%d",contourGraphs->GetEntries(),hist->GetName(),z0,(int)nGraphs));
contourGraphs->Add(gc);
contourVals.push_back(z0);
nGraphs++;
}
if(verbose) VERBOSEclass("\tretrieved %d (dismissed all others)",nGraphs);
}
delete tmp;
}
this->addObject(contourGraphs,"contours");
if(!batch) gROOT->SetBatch(false);
pad->cd();
hMaster->Draw("hist");
TLatex l;
bool autoColor = tags.getTagBoolDefault("style.contourLabels.autocolor",false);
int color = tags.getTagIntegerDefault("style.contourLabels.color",kBlack);
double size = tags.getTagDoubleDefault("style.contourLabels.size",0.03);
l.SetTextSize(size);
l.SetNDC(true);
if(!autoColor) l.SetTextColor(color);
std::vector<double> labelSpotsX;
std::vector<double> labelSpotsY;
TQIterator itrGraphs(contourGraphs);
while(itrGraphs.hasNext()){
TGraph* gc = dynamic_cast<TGraph*>(itrGraphs.readNext());
if(!gc) continue;
gc->Draw("C");
int index = 0.5*gc->GetN();
int indexStep = 1;
double x0, y0, z0;
double xNDC, yNDC;
z0 = contourVals[itrGraphs.getLastIndex()];
TString val = TString::Format("%g",z0);
double minDistX = 0.5*size*TQStringUtils::getWidth(val);
double minDistY = size;
bool acceptPosition = false;
if(tags.getTagBoolDefault("style.contourLabels.avoidCollisions",true)){
while(index > 0 && index < gc->GetN()){
acceptPosition = true;
gc->GetPoint(index, x0, y0);
xNDC = TQUtils::convertXtoNDC(x0);
yNDC = TQUtils::convertYtoNDC(y0);
for(size_t i=0; i<labelSpotsX.size(); i++){
double dx = fabs(xNDC - labelSpotsX[i]);
double dy = fabs(yNDC - labelSpotsY[i]);
if((dx < minDistX) && (dy < minDistY)) acceptPosition = false;
}
if(acceptPosition) break;
index += indexStep;
indexStep = -TQUtils::sgn(indexStep)*(abs(indexStep)+1);
}
}
if(!acceptPosition){
if(verbose) VERBOSEclass("did not find any suitable label position, using default");
gc->GetPoint((int)(0.5*gc->GetN()), x0, y0);
xNDC = TQUtils::convertXtoNDC(x0);
yNDC = TQUtils::convertYtoNDC(y0);
}
labelSpotsX.push_back(xNDC);
labelSpotsY.push_back(yNDC);
if(autoColor) l.SetTextColor(gc->GetLineColor());
l.DrawLatex(xNDC,yNDC,val);
if(verbose) VERBOSEclass("drawing label '%s' at (%f,%f) == (%f,%f) with minDist=(%f,%f)",val.Data(),x0,y0,xNDC,yNDC,minDistX,minDistY);
}
return true;
}
bool TQROOTPlotter::drawScatter(TQTaggable& tags){
TPad* pad = this->getPad("main");
TH2* hMaster = this->getObject<TH2>("Graph_master");
TObjArray* histos = this->getObject<TObjArray>("histos");
int markerStyle;
bool overrideMarkerStyle = tags.getTagInteger("style.scatter.markerStyle",markerStyle);
float alphaDecay = tags.getTagDoubleDefault("style.scatter.alphaDecay",1.0);
float markerSizeScale = tags.getTagDoubleDefault("style.scatter.markerScale", 1.0);
bool batch = gROOT->IsBatch();
gROOT->SetBatch(true);
pad->cd();
hMaster->Draw("");
TQIterator histItr(histos);
float alpha = 1.0;
while(histItr.hasNext()){
TH2* hist = dynamic_cast<TH2*>(histItr.readNext());
if(!hist) continue;
if (overrideMarkerStyle) {
hist->SetMarkerStyle(markerStyle);
}
hist->SetMarkerSize(hist->GetMarkerSize() * markerSizeScale);
hist->SetMarkerColorAlpha(hist->GetMarkerColor(), alpha);
alpha *= alphaDecay;
hist->Draw("SAME,SCAT");
}
if(!batch) gROOT->SetBatch(false);
return true;
}
bool TQROOTPlotter::drawMigration(TQTaggable& tags){
TString migration = "ggf";
if(!tags.getTagString("style.migration",migration)) return false;
TPad* pad = this->getPad("main");
TH2* hMaster = this->getObject<TH2>("Graph_master");
TQTaggableIterator itr_sig(fProcesses);
TQNamedTaggable* process_mig = NULL;
while(itr_sig.hasNext()){
TQNamedTaggable* process = itr_sig.readNext();
if(!process) continue;
if(process->getTagBoolDefault(".isData",false)) continue;
if(process->getName() == migration)
process_mig = process;
}
TH2* hMigration = this->getObject<TH2>(this->makeHistogramIdentifier(process_mig));
bool verbose = tags.getTagBoolDefault("verbose",false);
bool logScale = tags.getTagBoolDefault("style.logScale",false);
if (logScale) pad->SetLogz();
hMaster->Draw("HIST");
if(hMigration){
if(verbose) VERBOSEclass("drawing histogram '%s' as migration",migration.Data());
for(int i=0; i <= hMigration->GetNbinsY(); i++) {
double integral = hMigration->Integral(0,hMigration->GetNbinsY()+1,i,i);
if(integral > 0){
for(int j=0; j <= hMigration->GetNbinsX(); j++) {
double bincontent = hMigration->GetBinContent(j,i)/integral*100;
hMigration->SetBinContent(j,i,bincontent);
}
}
}
gStyle->SetPaintTextFormat("4.1f");
hMigration->SetContour(99);
hMigration->Draw("col4zsame");
hMigration->SetMarkerColor(kBlack);
hMigration->SetMarkerSize(1.4);
hMigration->Draw("textsame");
} else {
if(verbose){
VERBOSEclass("cannot draw '%s' as migration - object not found",migration.Data());
this->printObjects();
}
return false;
}
return true;
}
bool TQROOTPlotter::drawStack(TQTaggable& tags){
this->getPad("main");
bool statMcErrors = tags.getTagBoolDefault("errors.drawStatMC",true );
bool sysMcErrors = tags.getTagBoolDefault("errors.drawSysMC",false );
bool showTotalBkg = tags.getTagBoolDefault ("style.showTotalBkg",true);
bool drawData = tags.getTagBoolDefault ("style.drawData",true);
bool asymErrorsData = tags.getTagBoolDefault("style.data.asymErrors",false);
bool verbose = tags.getTagBoolDefault("verbose",false);
double scaling = tags.getTagDoubleDefault("geometry.main.scaling",1.);
std::vector<TString> showDataAtCuts;
if (drawData) {
tags.getTag("showDataAtCuts", showDataAtCuts);
}
TH1* hMaster = this->getObject<TH1>("Graph_master");
TH1* hTotalStack = this->getObject<TH1>(tags.getTagStringDefault("errors.shiftTo","totalStack"));
if(!hMaster) return false;
hMaster->Draw("hist");
if(verbose) VERBOSEclass("creating MC error band");
TGraphAsymmErrors * errorGraph = 0;
if (hTotalStack && (statMcErrors || sysMcErrors)){
TObjArray* histosAsymmSys = this->getObject<TObjArray>("asymmSys");
if(histosAsymmSys) {
errorGraph = TQHistogramUtils::getGraph(hTotalStack, histosAsymmSys);
} else {
errorGraph = TQHistogramUtils::getGraph(hTotalStack);
}
this->applyStyle(tags,errorGraph,"main.totalStackError",1.,scaling);
this->addObject(errorGraph,"totalStackErr");
}
if(tags.getTagBoolDefault("errors.showX",true)){
double errWidthX = 0.5;
if(tags.getTagDouble("errors.widthX", errWidthX))
gStyle->SetErrorX(errWidthX);
} else {
gStyle->SetErrorX(0.);
}
if(verbose) VERBOSEclass("calculating axis ranges & rescaling histograms");
bool axisOK = this->calculateAxisRanges1D(tags);
if(!axisOK){
if(verbose) VERBOSEclass("encountered invalid axis ranges, using defaults");
}
if(verbose) VERBOSEclass("drawing backgrounds");
THStack* stack = this->getObject<THStack>("stack");
TString stackDrawOptions = tags.getTagStringDefault ("style.stackDrawOptions","hist");
if (hTotalStack && stack) {
stack->Draw(stackDrawOptions + " same");
if (showTotalBkg && errorGraph) errorGraph->Draw("2");
} else {
if (verbose) VERBOSEclass("failed to obtain stack");
}
if(verbose) VERBOSEclass("drawing signal (and other non-stacked histograms)");
bool stackSignal = tags.getTagBoolDefault ("style.stackSignal",false);
TQTaggableIterator itr_sig(fProcesses);
while(itr_sig.hasNext()){
TQNamedTaggable* process = itr_sig.readNext();
if(!process) continue;
if(process->getTagBoolDefault(".isData",false)) continue;
if(!(process->getTagBoolDefault("stack",process->getTagBoolDefault(".isBackground") || stackSignal))){
TH1 * h = this->getObject<TH1>(this->makeHistogramIdentifier(process));
if(!h) continue;
TString drawOpt = process->getTagStringDefault("drawOptions", "hist") + " same";
if(process->getTagBoolDefault("stackShift",false)){
TH1* hcopy = TQHistogramUtils::copyHistogram(h,TString::Format("%s_shifted",h->GetName()));
hcopy->Add(hTotalStack);
hcopy->Draw(drawOpt);
} else {
h->Draw(drawOpt);
}
}
}
TString histogramTag = tags.getTagStringDefault("input.histogram", "");
TString cutName(histogramTag(0, histogramTag.First('/')));
bool drawDataThisCut = (showDataAtCuts.size() == 0);
if (std::find(showDataAtCuts.begin(), showDataAtCuts.end(), cutName) != showDataAtCuts.end()) {
drawDataThisCut = true;
}
if (drawData && drawDataThisCut) {
if(verbose) VERBOSEclass("drawing data");
TQTaggableIterator itr_data(fProcesses);
while(itr_data.hasNext()){
TQNamedTaggable* process = itr_data.readNext();
if(!process) continue;
if(!process->getTagBoolDefault(".isData",false)) continue;
TH1 * h = this->getObject<TH1>(this->makeHistogramIdentifier(process));
TList* myList = tags.getListOfTagNames();
TIter next(myList);
if (!h) {
continue;
} else if (h->Integral() < tags.getTagDoubleDefault("skipEmptyHistograms", 1e-5)) {
if (verbose) {
VERBOSEclass("skipping data histogram '%s' because it is empty", h->GetName());
}
continue;
}
this->applyStyle(tags,h,"main.data");
if (asymErrorsData) {
h->Sumw2(false);
h->SetBinErrorOption(TH1::kPoisson);
}
TString drawOpt = process->getTagStringDefault("drawOptions","ep") + " same";
if(verbose)
VERBOSEclass("drawing data histogram '%s' with option '%s'", h->GetName(),drawOpt.Data());
h->Draw(drawOpt);
}
}
if (tags.getTagBoolDefault("isCompPlot",false))
stack->Draw(stackDrawOptions + " same");
return true;
}
bool TQROOTPlotter::drawProfiles(TQTaggable& tags){
this->getPad("main");
bool verbose = tags.getTagBoolDefault("verbose",false);
TProfile* hMaster = this->getObject<TProfile>("Graph_master");
TProfile* hTotalStack = this->getObject<TProfile>("totalStack");
if(!hMaster || !hTotalStack) return false;
hMaster->SetMaximum(hMaster->GetYmax());
hMaster->SetMinimum(hMaster->GetYmin());
if(verbose) VERBOSEclass("calculating axis ranges & rescaling histograms");
bool axisOK = this->calculateAxisRangesProfile(tags);
if(!axisOK){
if(verbose) VERBOSEclass("encountered invalid axis ranges, using defaults");
}
hMaster->Draw("hist");
if(verbose) VERBOSEclass("drawing profiles");
TQTaggableIterator itr_bkg(fProcesses);
while(itr_bkg.hasNext()){
TQNamedTaggable* process = itr_bkg.readNext();
TString tmp1 = process->getName();
if(verbose){
INFOclass("drawing %s",tmp1.Data());
}
if(!process) continue;
TProfile * h = this->getObject<TProfile>(this->makeHistogramIdentifier(process));
if(!h) continue;
this->applyStyle(tags,h,"main.bkg");
h->Draw(process->getTagStringDefault("drawOptions", "ep") + " same");
}
return true;
}
void TQROOTPlotter::drawLegend(TQTaggable& tags){
bool verbose = tags.getTagBoolDefault("verbose",false);
bool legpad = tags.getTagBoolDefault("style.useLegendPad",false);
if (!tags.getTagBoolDefault("style.showLegend",true)) return;
TLegend* legend = this->getObject<TLegend>("legend");
if(legpad){
if(verbose) VERBOSEclass("drawing legend pad");
if(!this->getPad("legend")){
ERRORclass("error retrievling legend pad!");
}
legend->Draw();
} else {
if(verbose) VERBOSEclass("drawing legend on-pad");
this->getPad("main");
legend->Draw("same");
}
}
bool TQROOTPlotter::calculateAxisRanges1D(TQTaggable& tags){
bool logScale = tags.getTagBoolDefault ("style.logScale",false );
bool drawData = tags.getTagBoolDefault ("style.drawData",true);
bool verbose = tags.getTagBoolDefault("verbose",false);
std::vector<TString> showDataAtCuts;
if (drawData) {
tags.getTag("showDataAtCuts", showDataAtCuts);
}
TString histogramTag = tags.getTagStringDefault("input.histogram", "");
TString cutName(histogramTag(0, histogramTag.First('/')));
bool drawDataThisCut = (showDataAtCuts.size() == 0);
if (std::find(showDataAtCuts.begin(), showDataAtCuts.end(), cutName) != showDataAtCuts.end()) {
drawDataThisCut = true;
}
TH1* hTotalStack = this->getObject<TH1>("totalStack");
TList* histograms = new TList();
if (hTotalStack) histograms->Add(hTotalStack);
double min = std::numeric_limits<double>::infinity();
TQTaggableIterator itr(fProcesses);
while(itr.hasNext()){
TQNamedTaggable* process = itr.readNext();
if(!process) continue;
if(process->getTagBoolDefault(".isData",false) && (!drawData || !drawDataThisCut)) continue;
TH1 * h = this->getObject<TH1>(this->makeHistogramIdentifier(process));
if(!h) continue;
histograms->Add(h);
min = std::min(min,TQHistogramUtils::getMin(h, true, true, logScale ? tags.getTagDoubleDefault("style.logMinMin",1e-9) : -std::numeric_limits<double>::infinity() ));
}
tags.getTagDouble("style.min", min);
if(logScale){
tags.getTagDouble("style.logMin",min);
} else {
tags.getTagDouble("style.linMin",min);
}
if(logScale && min < tags.getTagDoubleDefault("style.logMinMin",1e-9) ) min = tags.getTagDoubleDefault("style.logMinMin",1e-9);
double max_precise = this->getHistogramUpperLimit(tags, histograms,min,true);
delete histograms;
double max;
if(max_precise <= 0 || !TQUtils::isNum(max_precise) || max_precise < min){
max = std::max(2*min,10.);
if(verbose) VERBOSEclass("using default range");
} else {
if(verbose) VERBOSEclass("using rounded range");
max = TQUtils::roundAutoUp(max_precise);
}
if(verbose) VERBOSEclass("calculated y-axis range is %g < y < %g (%g)",min,max,max_precise);
tags.getTagDouble("style.max", max);
double maxscale = 1.0;
tags.getTagDouble("style.max.scale", maxscale);
TH1* hMaster = this->getObject<TH1>("Graph_master");
hMaster->SetMinimum(min);
hMaster->SetMaximum(max * maxscale);
double xmin;
double xmax;
if (tags.getTagDouble("style.xmin", xmin)) {
hMaster->GetXaxis()->SetRangeUser(xmin, TQHistogramUtils::getAxisXmax(hMaster));
hTotalStack->GetXaxis()->SetRangeUser(xmin, TQHistogramUtils::getAxisXmax(hTotalStack));
}
if (tags.getTagDouble("style.xmax", xmax)) {
hMaster->GetXaxis()->SetRangeUser(TQHistogramUtils::getAxisXmin(hMaster), xmax);
hTotalStack->GetXaxis()->SetRangeUser(TQHistogramUtils::getAxisXmin(hTotalStack), xmax);
}
return !(max == 0);
}
bool TQROOTPlotter::calculateAxisRangesProfile(TQTaggable& tags){
bool logScale = tags.getTagBoolDefault ("style.logScale",false );
double logMin = tags.getTagDoubleDefault("style.logMin", -1.);
bool drawData = tags.getTagBoolDefault ("style.drawData",true);
bool verbose = tags.getTagBoolDefault("verbose",false);
TString profileRange = tags.getTagStringDefault("style.profileRange","filtered");
std::vector<TString> showDataAtCuts;
if (drawData) {
tags.getTag("showDataAtCuts", showDataAtCuts);
}
TString histogramTag = tags.getTagStringDefault("input.histogram", "");
TString cutName(histogramTag(0, histogramTag.First('/')));
bool drawDataThisCut = (showDataAtCuts.size() == 0);
if (std::find(showDataAtCuts.begin(), showDataAtCuts.end(), cutName) != showDataAtCuts.end()) {
drawDataThisCut = true;
}
TList* histograms = new TList();
double ymin = std::numeric_limits<double>::infinity();
double xmin = std::numeric_limits<double>::infinity();
double ymax = -std::numeric_limits<double>::infinity();
double xmax = -std::numeric_limits<double>::infinity();
double min,max;
TQTaggableIterator itr(fProcesses);
while(itr.hasNext()){
TQNamedTaggable* process = itr.readNext();
if(!process) continue;
if(process->getTagBoolDefault(".isData",false) && (!drawData || !drawDataThisCut)) continue;
TProfile * h = this->getObject<TProfile>(this->makeHistogramIdentifier(process));
if(!h) continue;
histograms->Add(h);
ymin=std::min(ymin,h->GetYmin() );
ymax=std::max(ymax,h->GetYmax() );
xmin=std::min(xmin,h->GetXaxis()->GetXmin());
xmax=std::max(xmax,h->GetXaxis()->GetXmax());
}
if (profileRange.EqualTo("filtered")) {
TQHistogramUtils::getFilteredRange(histograms, xmin, xmax, ymin, ymax, min, max, logMin);
min=std::max(min, ymin);
max=std::min(max, ymax);
if (min==ymax) min=ymin;
if (max==ymin) max=ymax;
} else if (profileRange.EqualTo("predefined")) {
min=ymin;
max=ymax;
} else if (profileRange.EqualTo("full")){
TQHistogramUtils::getUnFilteredRange(histograms, xmin, xmax, ymin, ymax, min, max);
}
if(logScale) min = std::max(min,tags.getTagDoubleDefault("style.logMinMin",1e-9));
int iBlock = 0;
double block_y;
double vetoFrac = 1;
while(tags.getTagDouble(TString::Format("blocks.y.%d",iBlock),block_y)){
vetoFrac = std::min(vetoFrac, block_y);
iBlock++;
}
if (logScale) max = std::max(max, exp(log(max/min) / vetoFrac ) * min);
else max = std::max(max, (max - min) / vetoFrac + min);
delete histograms;
if(verbose) VERBOSEclass("calculated y-axis range is %g < y < %g",min,max);
tags.getTagDouble("style.max", max);
double maxscale = 1.0;
tags.getTagDouble("style.max.scale", maxscale);
TProfile* hMaster = this->getObject<TProfile>("Graph_master");
hMaster->SetMinimum(min);
hMaster->SetMaximum(max * maxscale);
return !(max == 0);
}
namespace {
template<class T> size_t getN(T* obj);
template<class T> bool getXY(T*obj,size_t i,double& x,double& y);
template<> size_t getN<TGraphErrors>(TGraphErrors* obj){ return obj->GetN(); }
template<> size_t getN<TGraph>(TGraph* obj){ return obj->GetN(); }
template<> size_t getN<TH1> (TH1* obj){ return obj->GetNbinsX(); }
template<> bool getXY<TGraph> (TGraph* obj,size_t i,double& x,double& y){ return (i==(size_t)(obj->GetPoint(i,x,y))); }
template<> bool getXY<TGraphErrors> (TGraphErrors* obj,size_t i,double& x,double& y){ return (i==(size_t)(obj->GetPoint(i,x,y))); }
template<> bool getXY<TH1> (TH1* obj,size_t i,double& x,double& y){ x=obj->GetBinCenter(i+1); y=obj->GetBinContent(i+1); return true; }
}
template<class T>
void TQROOTPlotter::drawArrows(TQTaggable &tags,T *obj, double yMin, double yMax){
if(!tags.getTagBoolDefault("style.showArrows",true)) return;
bool verbose = tags.getTagBoolDefault("verbose",false);
size_t nBins = ::getN<T>(obj);
double arrowLength = tags.getTagDoubleDefault ("style.arrowLength",0.12 );
double arrowOffset = tags.getTagDoubleDefault ("style.arrowOffset",0.08 );
int arrowLineWidth = tags.getTagIntegerDefault ("style.arrowLineWidth",2 );
double arrowHeadSize = tags.getTagDoubleDefault ("style.arrowHeadSize",0.03 );
double padRatio = tags.getTagDoubleDefault("geometry.sub.height",0.35);
double canvasHeight = tags.getTagDoubleDefault("geometry.canvas.height",600.);
double canvasWidth = tags.getTagDoubleDefault("geometry.canvas.width",600.);
double frameWidthFrac = 1. - tags.getTagDoubleDefault("geometry.sub.margins.right",0.1) - tags.getTagDoubleDefault("geometry.sub.margins.left",0.1);
double frameWidth = frameWidthFrac * canvasWidth;
double arrowHeight = arrowHeadSize * canvasHeight;
double binWidth = frameWidth / nBins;
double alpha = 2*std::atan(binWidth/(2*arrowHeight)) * 180 / 3.1415926;
double arrowHeadAngle = tags.getTagDoubleDefault ("style.arrowHeadAngle",std::min(60.,alpha));
TString arrowType = tags.getTagStringDefault ("style.arrowType", "|>" );
int arrowColor = tags.getTagIntegerDefault ("style.arrowColor",kRed);
double plrange = yMax - yMin;
TArrow marker;
marker.SetLineWidth(arrowLineWidth);
marker.SetLineColor(arrowColor);
marker.SetFillColor(arrowColor);
marker.SetAngle(arrowHeadAngle);
for(size_t i=0; i < nBins; ++i){
double x; double y;
if(!::getXY<T>(obj,i,x,y)) continue;
if(y > yMax){
marker.DrawArrow(x,
yMax - (arrowOffset+arrowLength)*plrange,
x,
yMax - (arrowOffset)*plrange,
arrowHeadSize*padRatio,
arrowType);
if(verbose) VERBOSEclass("drawing marker for point %i, y > %f",i,yMax);
}
if(y < yMin && y != 0){
marker.DrawArrow(x,
yMin + (arrowOffset+arrowLength)*plrange,
x,
yMin + arrowOffset*plrange,
arrowHeadSize*padRatio,
arrowType);
if(verbose) VERBOSEclass("drawing marker for point %i, y < %f",i,yMin);
}
}
}
template void TQROOTPlotter::drawArrows<TGraph>(TQTaggable &tags,TGraph *obj, double yMin, double yMax);
template void TQROOTPlotter::drawArrows<TGraphErrors>(TQTaggable &tags,TGraphErrors *obj, double yMin, double yMax);
template void TQROOTPlotter::drawArrows<TH1>(TQTaggable &tags,TH1 *obj, double yMin, double yMax);