3030import pandas as pd
3131import lz4
3232from machine_learning_hep .selectionutils import select_runs
33- from ROOT import TGraphAsymmErrors # pylint: disable=import-error, no-name-in-module
33+ from ROOT import TObject , TCanvas , TLegend , TH1 , TLatex , TGraph , TGraphAsymmErrors # pylint: disable=import-error, no-name-in-module
3434from ROOT import kBlack , kRed , kGreen , kBlue , kYellow , kOrange , kMagenta , kCyan , kGray # pylint: disable=import-error, no-name-in-module
3535from ROOT import kOpenCircle , kOpenSquare , kOpenDiamond , kOpenCross , kOpenStar , kOpenThreeTriangles # pylint: disable=import-error, no-name-in-module
3636from ROOT import kOpenFourTrianglesX , kOpenDoubleDiamond , kOpenFourTrianglesPlus , kOpenCrossX # pylint: disable=import-error, no-name-in-module
@@ -448,26 +448,67 @@ def get_plot_range(val_min, val_max, margin_min, margin_max, logscale=False):
448448 val_max_plot = val_max + k_max * val_range
449449 return val_min_plot , val_max_plot
450450
451- def get_y_window_gr (l_gr : list ):
452- '''Return the minimum and maximum value so that all the points of the graphs in the list
453- fit in the range including the error bars.'''
451+ def get_x_window_gr (l_gr : list , with_errors = True ):
452+ '''Return the minimum and maximum x value so that all the points of the graphs in the list
453+ fit in the range (by default including the error bars).'''
454+ def err_low (graph ):
455+ return graph .GetEXlow if isinstance (graph , TGraphAsymmErrors ) else graph .GetEX
456+ def err_high (graph ):
457+ return graph .GetEXhigh if isinstance (graph , TGraphAsymmErrors ) else graph .GetEX
458+
454459 if not isinstance (l_gr , list ):
455460 l_gr = [l_gr ]
456- y_min = min ([min ([(gr .GetY ())[i ] - (gr .GetEYlow ())[i ] \
457- for i in range (gr .GetN ())]) for gr in l_gr ])
458- y_max = max ([max ([(gr .GetY ())[i ] + (gr .GetEYhigh ())[i ] \
459- for i in range (gr .GetN ())]) for gr in l_gr ])
461+ x_min = float ("inf" )
462+ x_max = float ("-inf" )
463+ for gr in l_gr :
464+ for i in range (gr .GetN ()):
465+ x_min = min (x_min , (gr .GetX ())[i ] - ((err_low (gr )())[i ] if with_errors else 0 ))
466+ x_max = max (x_max , (gr .GetX ())[i ] + ((err_high (gr )())[i ] if with_errors else 0 ))
467+ return x_min , x_max
468+
469+ def get_x_window_his (l_his : list ):
470+ '''Return the minimum and maximum x value so that all the bins of the histograms in the list
471+ fit in the range.'''
472+ if not isinstance (l_his , list ):
473+ l_his = [l_his ]
474+ x_min = float ("inf" )
475+ x_max = float ("-inf" )
476+ for his in l_his :
477+ x_min = min (x_min , his .GetXaxis ().GetBinLowEdge (1 ))
478+ x_max = max (x_max , his .GetXaxis ().GetBinUpEdge (his .GetNbinsX ()))
479+ return x_min , x_max
480+
481+ def get_y_window_gr (l_gr : list , with_errors = True ):
482+ '''Return the minimum and maximum y value so that all the points of the graphs in the list
483+ fit in the range (by default including the error bars).'''
484+ def err_low (graph ):
485+ return graph .GetEYlow if isinstance (graph , TGraphAsymmErrors ) else graph .GetEY
486+ def err_high (graph ):
487+ return graph .GetEYhigh if isinstance (graph , TGraphAsymmErrors ) else graph .GetEY
488+
489+ if not isinstance (l_gr , list ):
490+ l_gr = [l_gr ]
491+ y_min = float ("inf" )
492+ y_max = float ("-inf" )
493+ for gr in l_gr :
494+ for i in range (gr .GetN ()):
495+ y_min = min (y_min , (gr .GetY ())[i ] - ((err_low (gr )())[i ] if with_errors else 0 ))
496+ y_max = max (y_max , (gr .GetY ())[i ] + ((err_high (gr )())[i ] if with_errors else 0 ))
460497 return y_min , y_max
461498
462- def get_y_window_his (l_his : list ):
463- '''Return the minimum and maximum value so that all the points of the histograms in the list
464- fit in the range including the error bars.'''
499+ def get_y_window_his (l_his : list , with_errors = True ):
500+ '''Return the minimum and maximum y value so that all the points of the histograms in the list
501+ fit in the range (by default including the error bars) .'''
465502 if not isinstance (l_his , list ):
466503 l_his = [l_his ]
467- y_min = min ([min ([his .GetBinContent (i + 1 ) - his .GetBinError (i + 1 ) \
468- for i in range (his .GetNbinsX ())]) for his in l_his ])
469- y_max = max ([max ([his .GetBinContent (i + 1 ) + his .GetBinError (i + 1 ) \
470- for i in range (his .GetNbinsX ())]) for his in l_his ])
504+ y_min = float ("inf" )
505+ y_max = float ("-inf" )
506+ for his in l_his :
507+ for i in range (his .GetNbinsX ()):
508+ cont = his .GetBinContent (i + 1 )
509+ err = his .GetBinError (i + 1 ) if with_errors else 0
510+ y_min = min (y_min , cont - err )
511+ y_max = max (y_max , cont + err )
471512 return y_min , y_max
472513
473514def get_colour (i : int ):
@@ -514,7 +555,7 @@ def setup_legend(legend, textsize=0.03):
514555 legend .SetTextSize (textsize )
515556 legend .SetTextFont (42 )
516557
517- def setup_tgraph (tg_ , colour = 1 , alphastyle = 0.3 , fillstyle = 1001 ):
558+ def setup_tgraph (tg_ , colour = 1 , markerstyle = kOpenCircle , size = 1.5 , alphastyle = 0.3 , fillstyle = 1001 ):
518559 tg_ .GetXaxis ().SetTitleSize (0.04 )
519560 tg_ .GetXaxis ().SetTitleOffset (1.0 )
520561 tg_ .GetYaxis ().SetTitleSize (0.04 )
@@ -523,7 +564,9 @@ def setup_tgraph(tg_, colour=1, alphastyle=0.3, fillstyle=1001):
523564 tg_ .SetLineWidth (2 )
524565 tg_ .SetLineColor (colour )
525566 tg_ .SetFillStyle (fillstyle )
526- tg_ .SetMarkerSize (0 )
567+ tg_ .SetMarkerSize (size )
568+ tg_ .SetMarkerStyle (markerstyle )
569+ tg_ .SetMarkerColor (colour )
527570
528571def draw_latex (latex , colour = 1 , textsize = 0.03 ):
529572 latex .SetNDC ()
@@ -532,6 +575,228 @@ def draw_latex(latex, colour=1, textsize=0.03):
532575 latex .SetTextFont (42 )
533576 latex .Draw ()
534577
578+ def make_plot (name , path = None , suffix = "eps" , title = "" , size = None , margins_c = None , # pylint: disable=too-many-arguments, too-many-branches, too-many-statements, too-many-locals
579+ list_obj = None , labels_obj = None ,
580+ leg_pos = None , opt_leg_h = "P" , opt_leg_g = "P" , opt_plot_h = "" , opt_plot_g = "P0" ,
581+ offsets_xy = None , maxdigits = 3 , colours = None , markers = None ,
582+ range_x = None , margins_y = None , with_errors = "xy" , logscale = None ):
583+ """
584+ Make a plot with objects from a list (list_obj).
585+ Returns a TCanvas and a list of other created ROOT objects.
586+ Minimum example:
587+ make_plot("canvas", list_obj=[histogram], path=".")
588+ To have access to the created object, do:
589+ canvas, list_can = make_plot("canvas", list_obj=[histogram])
590+ Features:
591+ - plotting of histograms (TH??), graphs (TGraph*), text fields (TLatex) and any other objects
592+ derived from TObject in any count and order
593+ - automatic calculation of plotting ranges (x, y) based on the data (histograms and graphs)
594+ - arbitrary x range
595+ - automatic style settings
596+ - optional plotting of the legend (enabled by providing the coordinates)
597+ - automatic adding of legend entries (in the plotting order)
598+ - logarithmic scale of x, y, z axes (logscale), (format: string containing any of x, y, z)
599+ - saving the canvas to a specified location (path) in a specified format (suffix)
600+ - access to created ROOT objects
601+ Adjustable parameters:
602+ - title and axis titles (title), (format: "title_plot;title_x;title_y")
603+ - canvas size (size), (format: [width, height])
604+ - plotting options for histograms and graphs (opt_plot_h, opt_plot_g),
605+ (format: see THistPainter and TGraphPainter, respectively)
606+ - legend position (leg_pos), (format: [x_min, y_min, x_max, y_max])
607+ - labels of legend entries (labels_obj)
608+ - styles of legend entries (opt_leg_h, opt_leg_g), (format: see TLegend::AddEntry)
609+ - colours and markers (colours, markers), (format: list of numbers or named values)
610+ - canvas margins (margins_c), (format: [bottom, left, top, right])
611+ - offsets of axis titles (offsets_xy), (format: [x, y])
612+ - maximum number of digits of the axis labels (maxdigits)
613+ - x range (range_x), (format: [x_min, x_max])
614+ - vertical margins between the horizontal axes and the data (margins_y), (format: [lower, upper]
615+ expressed as fractions of the total plotting range)
616+ - including the error bars in the range calculations (with_errors),
617+ (format: string containing any of x, y)
618+ """
619+
620+ # HELPING FUNCTIONS
621+
622+ def min0_gr (graph ):
623+ """ Get the minimum positive y value in the graph. """
624+ list_pos = [y for y in graph .GetY () if y > 0 ]
625+ return min (list_pos ) if list_pos else float ("inf" )
626+
627+ def get_my_colour (i : int ):
628+ if colours and isinstance (colours , list ) and len (colours ) > 0 :
629+ return colours [i % len (colours )]
630+ return get_colour (i )
631+
632+ def get_my_marker (i : int ):
633+ if markers and isinstance (markers , list ) and len (markers ) > 0 :
634+ return markers [i % len (markers )]
635+ return get_marker (i )
636+
637+ def plot_graph (graph ):
638+ setup_tgraph (graph , get_my_colour (counter_plot ), get_my_marker (counter_plot ))
639+ graph .SetTitle (title )
640+ graph .GetXaxis ().SetLimits (x_min_plot , x_max_plot )
641+ graph .GetYaxis ().SetRangeUser (y_min_plot , y_max_plot )
642+ graph .GetXaxis ().SetMaxDigits (maxdigits )
643+ graph .GetYaxis ().SetMaxDigits (maxdigits )
644+ if offsets_xy :
645+ graph .GetXaxis ().SetTitleOffset (offsets_xy [0 ])
646+ graph .GetYaxis ().SetTitleOffset (offsets_xy [1 ])
647+ if leg and n_labels > counter_plot :
648+ leg .AddEntry (graph , labels_obj [counter_plot ], opt_leg_g )
649+ graph .Draw (opt_plot_g + "A" if counter_plot == 0 else opt_plot_g )
650+
651+ def plot_histogram (histogram ):
652+ # If nothing has been plotted yet, plot an empty graph to set the exact ranges.
653+ if counter_plot == 0 :
654+ gr = TGraph (histogram )
655+ gr .SetMarkerSize (0 )
656+ gr .SetTitle (title )
657+ gr .GetXaxis ().SetLimits (x_min_plot , x_max_plot )
658+ gr .GetYaxis ().SetRangeUser (y_min_plot , y_max_plot )
659+ gr .GetXaxis ().SetMaxDigits (maxdigits )
660+ gr .GetYaxis ().SetMaxDigits (maxdigits )
661+ if offsets_xy :
662+ gr .GetXaxis ().SetTitleOffset (offsets_xy [0 ])
663+ gr .GetYaxis ().SetTitleOffset (offsets_xy [1 ])
664+ gr .Draw ("AP" )
665+ list_new .append (gr )
666+ setup_histogram (histogram , get_my_colour (counter_plot ), get_my_marker (counter_plot ))
667+ if leg and n_labels > counter_plot :
668+ leg .AddEntry (histogram , labels_obj [counter_plot ], opt_leg_h )
669+ histogram .Draw (opt_plot_h )
670+
671+ def plot_latex (latex ):
672+ draw_latex (latex )
673+
674+ def is_histogram (obj ):
675+ return isinstance (obj , TH1 )
676+
677+ def is_graph (obj ):
678+ return isinstance (obj , TGraph )
679+
680+ def is_latex (obj ):
681+ return isinstance (obj , TLatex )
682+
683+ # BODY STARTS HERE
684+
685+ if not (isinstance (list_obj , list ) and len (list_obj ) > 0 ):
686+ print ("Error: Empty list of objects" )
687+ return None , None
688+
689+ list_new = [] # list of created objects that need to exist outside the function
690+ if not (isinstance (offsets_xy , list ) and len (offsets_xy ) == 2 ):
691+ offsets_xy = None
692+ if not isinstance (labels_obj , list ):
693+ labels_obj = []
694+ n_labels = len (labels_obj )
695+ if margins_y is None :
696+ margins_y = [0.05 , 0.05 ]
697+
698+ # create and set canvas
699+ can = TCanvas (name , name )
700+ setup_canvas (can )
701+ if isinstance (size , list ) and len (size ) == 2 :
702+ can .SetCanvasSize (* size )
703+ # set canvas margins
704+ if isinstance (margins_c , list ) and len (margins_c ) > 0 :
705+ for setter , value in zip ([can .SetBottomMargin , can .SetLeftMargin ,
706+ can .SetTopMargin , can .SetRightMargin ], margins_c ):
707+ setter (value )
708+ # set logarithmic scale for selected axes
709+ log_y = False
710+ if isinstance (logscale , str ) and len (logscale ) > 0 :
711+ for setter , axis in zip ([can .SetLogx , can .SetLogy , can .SetLogz ], ["x" , "y" , "z" ]):
712+ if axis in logscale :
713+ setter ()
714+ if axis == "y" :
715+ log_y = True
716+
717+ # create and set legend
718+ leg = None
719+ if isinstance (leg_pos , list ) and len (leg_pos ) == 4 :
720+ leg = TLegend (* leg_pos )
721+ setup_legend (leg )
722+ list_new .append (leg )
723+
724+ # range calculation
725+ list_h = [] # list of histograms
726+ list_g = [] # list of graphs
727+ for obj in list_obj :
728+ if is_histogram (obj ):
729+ list_h .append (obj )
730+ elif is_graph (obj ):
731+ list_g .append (obj )
732+ # get x range of histograms
733+ x_min_h , x_max_h = float ("inf" ), float ("-inf" )
734+ if len (list_h ) > 0 :
735+ x_min_h , x_max_h = get_x_window_his (list_h )
736+ # get x range of graphs
737+ x_min_g , x_max_g = float ("inf" ), float ("-inf" )
738+ if len (list_g ) > 0 :
739+ x_min_g , x_max_g = get_x_window_gr (list_g , "x" in with_errors )
740+ # get total x range
741+ x_min = min (x_min_h , x_min_g )
742+ x_max = max (x_max_h , x_max_g )
743+ # get plotting x range
744+ x_min_plot , x_max_plot = x_min , x_max
745+ if isinstance (range_x , list ) and len (range_x ) == 2 :
746+ x_min_plot , x_max_plot = range_x
747+
748+ # get y range of histograms
749+ y_min_h , y_max_h = float ("inf" ), float ("-inf" )
750+ if len (list_h ) > 0 :
751+ y_min_h , y_max_h = get_y_window_his (list_h , "y" in with_errors )
752+ if log_y and y_min_h <= 0 :
753+ y_min_h = min ([h .GetMinimum (0 ) for h in list_h ])
754+ # get y range of graphs
755+ y_min_g , y_max_g = float ("inf" ), float ("-inf" )
756+ if len (list_g ) > 0 :
757+ y_min_g , y_max_g = get_y_window_gr (list_g , "y" in with_errors )
758+ if log_y and y_min_g <= 0 :
759+ y_min_g = min ([min0_gr (g ) for g in list_g ])
760+ # get total y range
761+ y_min = min (y_min_h , y_min_g )
762+ y_max = max (y_max_h , y_max_g )
763+ # get plotting y range
764+ y_min_plot , y_max_plot = y_min , y_max
765+ if isinstance (margins_y , list ) and len (margins_y ) == 2 :
766+ y_min_plot , y_max_plot = get_plot_range (y_min , y_max , * margins_y , log_y )
767+
768+ # append "same" to the histogram plotting option if needed
769+ opt_plot_h = opt_plot_h .lower ()
770+ opt_not_in = all (opt not in opt_plot_h for opt in ("same" , "lego" , "surf" ))
771+ if opt_not_in :
772+ opt_plot_h += " same"
773+
774+ # plot objects
775+ counter_plot = 0 # counter of plotted histograms and graphs
776+ for obj in list_obj :
777+ if is_histogram (obj ):
778+ plot_histogram (obj )
779+ counter_plot += 1
780+ elif is_graph (obj ):
781+ plot_graph (obj )
782+ counter_plot += 1
783+ elif is_latex (obj ):
784+ plot_latex (obj )
785+ elif isinstance (obj , TObject ):
786+ obj .Draw ()
787+ else :
788+ continue
789+
790+ # plot legend
791+ if leg :
792+ leg .Draw ()
793+
794+ # save canvas if necessary info provided
795+ if path and name and suffix :
796+ can .SaveAs ("%s/%s.%s" % (path , name , suffix ))
797+
798+ return can , list_new
799+
535800def tg_sys (central , variations ):
536801 shapebins_centres = []
537802 shapebins_contents = []
0 commit comments