Back to home page

Project CMSSW displayed by LXR

 
 

    


File indexing completed on 2024-04-06 12:24:07

0001 """
0002 An API and a CLI for quickly building complex figures.
0003 """
0004 from __future__ import absolute_import
0005 from __future__ import print_function
0006 
0007 from builtins import range
0008 __license__ = '''\
0009 Copyright (c) 2009-2010 Jeff Klukas <klukas@wisc.edu>
0010 
0011 Permission is hereby granted, free of charge, to any person obtaining a copy
0012 of this software and associated documentation files (the "Software"), to deal
0013 in the Software without restriction, including without limitation the rights
0014 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
0015 copies of the Software, and to permit persons to whom the Software is
0016 furnished to do so, subject to the following conditions:
0017 
0018 The above copyright notice and this permission notice shall be included in
0019 all copies or substantial portions of the Software.
0020 
0021 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
0022 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
0023 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
0024 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
0025 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
0026 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
0027 THE SOFTWARE.
0028 '''
0029 
0030 usage="""\
0031 Usage: %prog [config.py] targets [options]
0032 
0033 Targets may be either multiple root files to compare or a single root file 
0034 followed by multiple histograms or folders to compare.  For example:
0035     %prog fileA.root fileB.root fileC.root
0036     %prog file.root dirA dirB dirC
0037     %prog file.root dirA/hist1 dirA/hist2
0038 
0039 Full documentation is available at: 
0040     http://packages.python.org/rootplot/"""
0041 
0042 ##############################################################################
0043 ######## Import python libraries #############################################
0044 
0045 import sys
0046 import optparse
0047 import shutil
0048 import math
0049 import os
0050 import re
0051 import tempfile
0052 import copy
0053 from os.path import join as joined
0054 
0055 
0056 ##############################################################################
0057 ######## Import ROOT and rootplot libraries ##################################
0058 
0059 from .utilities import RootFile, Hist, Hist2D, HistStack
0060 from .utilities import find_num_processors, loadROOT
0061 
0062 argstring = ' '.join(sys.argv)
0063 ## Use ROOT's batch mode, unless outputting to C macros, since there is
0064 ## a bug in pyROOT that fails to export colors in batch mode
0065 batch = (not re.search('--ext[ =]*C', argstring) and
0066          not re.search('-e[ ]*C', argstring))
0067 ROOT = loadROOT(batch=batch)
0068 
0069 
0070 ##############################################################################
0071 ######## Define globals ######################################################
0072 
0073 from .version import __version__          # version number
0074 prog = os.path.basename(sys.argv[0])     # rootplot or rootplotmpl
0075 use_mpl = False                          # set in plotmpl or rootplotmpl
0076 global_opts = ['filenames', 'targets', 'debug', 'path', 'processors', 
0077                'merge', 'noclean', 'output', 'numbering', 'html_template',
0078                'ncolumns_html']
0079 try:
0080     import multiprocessing
0081     use_multiprocessing = True
0082 except ImportError:
0083     use_multiprocessing = False
0084 
0085 
0086 ##############################################################################
0087 ######## Classes #############################################################
0088 
0089 class Options(dict):
0090     def __init__(self, options, arguments, scope='global'):
0091         for opt in dir(options):
0092             value = getattr(options, opt)
0093             if (not opt.startswith('__') and
0094                 type(value) in [int, float, str, bool, type(None)]):
0095                 self[opt] = value
0096         self.filenames = [x for x in arguments if x.endswith('.root')]
0097         self.configs   = [x for x in arguments if x.endswith('.py')]
0098         self.targets   = [x for x in arguments if not (x.endswith('.py') or
0099                                                        x.endswith('.root'))]
0100         self.process_configs(scope)
0101     def __setattr__(self, key, value):
0102         self[key] = value
0103     def __getattr__(self, key):
0104         return self[key]
0105     def clean_targets(self):
0106         for i in range(len(self.targets)):
0107             if self.targets[i][-1] == '/':
0108                 self.targets[i] = self.targets[i][:-1]
0109     def arguments(self):
0110         return self.filenames + self.targets + self.configs
0111     def kwarg_list(self):
0112         diffs = {}
0113         defaults = parse_arguments([])
0114         for key, value in self.items():
0115             if (key not in ['filenames', 'targets', 'configs'] and
0116                 defaults[key] != value):
0117                 diffs[key] = value
0118         return diffs
0119     def append_from_package(self, package, scope):
0120         for attribute in dir(package):
0121             if '__' not in attribute:
0122                 if ((scope == 'global' and attribute in global_opts) or 
0123                     (scope == 'plot' and attribute not in global_opts)):
0124                     value = getattr(package, attribute)
0125                     self[attribute] = value
0126     def process_configs(self, scope):
0127         #### Load variables from configs; scope is 'global' or 'plot'
0128         configdir = tempfile.mkdtemp()
0129         sys.path.insert(0, '')
0130         sys.path.insert(0, configdir)
0131         write_to_file(config_string(), 
0132                       joined(configdir, 'default_config.py'))
0133         configs = ['default_config.py']
0134         for i, c in enumerate(self.configs):
0135             shutil.copy(c, joined(configdir, 'rpconfig%i.py' % i))
0136             configs.append('rpconfig%i.py' % i)
0137         rc_name = use_mpl and 'rootplotmplrc' or 'rootplotrc'
0138         rc_path = os.path.expanduser('~/.%s' % rc_name)
0139         if os.path.exists(rc_path):
0140             print("Using styles and options from ~/.%s" % rc_name)
0141             shutil.copy(rc_path, joined(configdir, '%s.py' % rc_name))
0142             configs.insert(1, '%s.py' % rc_name)
0143         for f in configs:
0144             myconfig = __import__(f[:-3])
0145             self.append_from_package(myconfig, scope)
0146         self.clean_targets()
0147         shutil.rmtree(configdir)
0148 
0149 
0150 ##############################################################################
0151 ######## Templates ###########################################################
0152 
0153 config_template=r"""
0154 import ROOT         # allows access to ROOT colors (e.g. ROOT.kRed)
0155 
0156 ##############################################################################
0157 ######## About Config Files ##################################################
0158 
0159 ## This file can be generated by running '%prog --config'
0160 
0161 ## Options are loaded in the following order:
0162 ##   1. from the command line
0163 ##   2. from the default configuration file
0164 ##   3. from ~/.%progrc
0165 ##   4. from configuration files specified on the command-line
0166 ## This leads to two major points worth understanding:
0167 ##   1. you may delete any lines you like in this file and they will still
0168 ##      be loaded correctly from the default
0169 ##   2. values specified here will superceed the same options from the
0170 ##      command-line
0171 ## Therefore, you could set, for example, 'xerr = True' in this file,
0172 ## and x-errorbars will always be drawn, regardless of whether '--xerr' is
0173 ## given on the command-line or not.  You can do this with any of the command-
0174 ## line options, but note that dashes are translated to underscores, so
0175 ## '--ratio-split=1' becomes 'ratio_split = 1'.
0176 
0177 ## Most global style options like default line widths can be set through
0178 root::## a rootlogon.C, as described at:
0179 root::##    http://root.cern.ch/drupal/content/how-create-or-modify-style
0180 mpl:::## a matplotlibrc, as described at:
0181 mpl:::##    http://matplotlib.sourceforge.net/users/customizing.html
0182 
0183 ##############################################################################
0184 ######## Specifying Files and Targets ########################################
0185 
0186 ## You can specify the files to run on through the 'filenames' variable rather
0187 ## than entering them at the command-line, for example:
0188 ## filenames = ['histTTbar.root', 'histZmumu.root']
0189 
0190 ## Likewise, you can specify target histograms or directories here rather than 
0191 ## on the command-line, for example:
0192 ## targets = ['barrel/15to20', 'barrel/20to30']
0193 
0194 ## You might also want to specify fancy labels for the legend here rather 
0195 ## than on the command-line:
0196 root::## legend_entries = [r'#bar{t}t', r'Z#rightarrow#mu#mu']
0197 mpl:::## legend_entries = [r'$\bar{t}t$', r'$Z\rightarrow\mu\mu$']
0198 
0199 ##############################################################################
0200 ######## Different Options for Different Targets #############################
0201 
0202 ## Leave these lists empty to have them automatically filled according to the
0203 ## command-line options.  Any list that is filled must be at least as long
0204 ## as the number of targets or it will throw an error.
0205 
0206 line_colors = []                # normally filled by options.colors
0207 fill_colors = []                # normally filled by options.colors
0208 marker_colors = []              # normally filled by options.colors
0209 mpl:::errorbar_colors = []      # color for bars around the central value
0210 root::marker_sizes = []         # in pixels
0211 mpl:::marker_sizes = []         # in points
0212 root::line_styles = []          # 1 (solid), 2 (dashed), 4 (dashdot), 3 (dotted), ...
0213 mpl:::line_styles = []          # 'solid', 'dashed', 'dashdot', 'dotted'
0214 root::fill_styles = []          # 0 (hollow), 1001 (solid), 2001 (hatched), ...
0215 mpl:::fill_styles = []          # None, '/', '\', '|', '-', '+', 'x', 'o', 'O', ...
0216 root::draw_commands = []        # a TH1::Draw option, include 'stack' to make stacked
0217 mpl:::plot_styles = []          # 'bar', 'hist', 'errorbar', 'stack'
0218 mpl:::alphas = []               # transparencies for fills (value from 0 to 1)
0219 
0220 ##############################################################################
0221 ######## Global Style Options ################################################
0222 
0223 ## Colors can be specified as (r, g, b) tuples (with range 0. to 1. or range
0224 root::## 0 to 255), or ROOT color constants (ROOT.kBlue or 600)
0225 mpl:::## 0 to 255), ROOT color constants (ROOT.kBlue or 600), or any matplotlib
0226 mpl:::## color specification (names like 'blue' or 'b')
0227 
0228 colors = [
0229     ## a default set of contrasting colors the author happens to like
0230     ( 82, 124, 219), # blue
0231     (212,  58, 143), # red
0232     (231, 139,  77), # orange
0233     (145,  83, 207), # purple
0234     (114, 173, 117), # green
0235     ( 67,  77,  83), # dark grey
0236     ]
0237 
0238 ## Used when --marker_styles is specified; more info available at:
0239 root::## http://root.cern.ch/root/html/TAttMarker.html
0240 mpl:::## http://matplotlib.sourceforge.net/api/
0241 mpl:::##        artist_api.html#matplotlib.lines.Line2D.set_marker
0242 marker_styles = [
0243 mpl:::    'o', 's', '^', 'x', '*', 'D', 'h', '1'
0244 root::     4, # circle
0245 root::    25, # square
0246 root::    26, # triangle
0247 root::     5, # x
0248 root::    30, # five-pointed star
0249 root::    27, # diamond
0250 root::    28, # cross
0251 root::     3, # asterisk
0252     ]
0253 
0254 #### Styles for --data
0255 root::data_linestyle = 1
0256 mpl:::data_linestyle = 'solid'
0257 data_color = (0,0,0)      # black
0258 mc_color = (50, 150, 150) # used when there are exactly 2 targets; set to
0259                           # None to pick up the normal color
0260 root::data_marker = 4           # marker style (circle)
0261 mpl:::data_marker = 'o'         # marker style
0262 
0263 #### Settings for --ratio-split or --efficiency-split
0264 ratio_max  = None
0265 ratio_min  = None
0266 ratio_logy = False
0267 ratio_fraction = 0.3  # Fraction of the canvas that bottom plot occupies
0268 ratio_label = 'Ratio to %(ratio_file)s' # Label for the bottom plot
0269 efficiency_label = 'Efficiency vs. %(ratio_file)s'
0270 
0271 #### Titles produced by --area-normalize and --normalize
0272 area_normalized_title = 'Fraction of Events in Bin'
0273 target_normalized_title = 'Events Normalized to %(norm_file)s'
0274 
0275 #### Overflow and underflow text labels
0276 overflow_text = ' Overflow'
0277 underflow_text = ' Underflow'
0278 mpl:::overflow_size = 'small'
0279 mpl:::overflow_alpha = 0.5
0280 
0281 #### Define how much headroom to add to the plot
0282 top_padding_factor = 1.2
0283 top_padding_factor_log = 5.    # used when --logy is set
0284 
0285 #### Plotting options based on histogram names
0286 ## Apply options to histograms whose names match regular expressions
0287 ## The tuples are of the form (option_name, value_to_apply, list_of_regexs)
0288 ## ex: to rebin by 4 all histograms containing 'pt' or starting with 'eta':
0289 ##    ('rebin', 4, ['.*pt.*', 'eta.*'])
0290 options_by_histname = [
0291     ('area_normalize', True, []),
0292                        ]
0293 
0294 root::#### Legend
0295 root::legend_width = 0.38        # Fraction of canvas width
0296 root::legend_entry_height = 0.05 # Fraction of canvas height
0297 root::max_legend_height = 0.4    # Fraction of canvas height
0298 root::legend_left_bound = 0.20   # For left justification
0299 root::legend_right_bound = 0.95  # For right justification
0300 root::legend_upper_bound = 0.91  # For top justification
0301 root::legend_lower_bound = 0.15  # For bottom justification
0302 root::legend_codes = { 1 : 'upper right',
0303 root::                 2 : 'upper left',
0304 root::                 3 : 'lower left',
0305 root::                 4 : 'lower right',
0306 root::                 5 : 'right',
0307 root::                 6 : 'center left',
0308 root::                 7 : 'center right',
0309 root::                 8 : 'lower center',
0310 root::                 9 : 'upper center',
0311 root::                10 : 'center',
0312 root::                }
0313 root::
0314 root::#### Page numbers
0315 root::numbering_size_root = 0.03  # Fraction of canvas width
0316 root::numbering_align_root = 33   # Right-top adjusted
0317 root::numbering_x_root = 0.97     # Fraction of canvas width
0318 root::numbering_y_root = 0.985    # Fraction of canvas height
0319 root::
0320 root::#### Draw style for TGraph
0321 root::draw_graph = 'ap'
0322 root::
0323 root::#### This code snippet will be executed after the histograms have all
0324 root::#### been drawn, allowing you to add decorations to the canvas
0325 root::decoration_root = '''
0326 root::## Draw a line to indicate a cut
0327 root::#line = ROOT.TLine(5.,0.,5.,9.e9)
0328 root::#line.Draw()
0329 root::## Add a caption
0330 root::#tt = ROOT.TText()
0331 root::#tt.DrawTextNDC(0.6, 0.15, "CMS Preliminary")
0332 root::'''
0333 mpl:::#### Legend
0334 mpl:::#### These options will override legend_location, allowing more precise control
0335 mpl:::## Upper right corner of legend in figure coordinates
0336 mpl:::legend_figure_bbox = None    # [1.0, 1.0] for legend outside the axes
0337 mpl:::## Upper right corner of legend in axes coordinates
0338 mpl:::legend_axes_bbox = None
0339 mpl:::
0340 mpl:::#### Page numbers
0341 mpl:::numbering_size_mpl = 'small'
0342 mpl:::numbering_ha_mpl = 'right'
0343 mpl:::numbering_va_mpl = 'top'
0344 mpl:::numbering_x_mpl = 0.98       # Fraction of canvas width
0345 mpl:::numbering_y_mpl = 0.98       # Fraction of canvas height
0346 mpl:::
0347 mpl:::#### Rotation for text x-axis labels
0348 mpl:::xlabel_rotation = -15
0349 mpl:::xlabel_alignment = 'left'
0350 mpl:::xlabel_alignmenth = 'bottom' # For barh
0351 mpl:::
0352 mpl:::#### Convert ROOT symbols to proper LaTeX, for matplotlib plotting
0353 mpl:::## By default, matplotlib renders only symbols between $'s as TeX, but if
0354 mpl:::## you enable the 'text.usetex' matplotlibrc setting, then everything is handled
0355 mpl:::## by the LaTeX engine on your system, in which case you can go wild with TeX.
0356 mpl:::
0357 mpl:::## ROOT-type strings on left get replaced with LaTeX strings on the right
0358 mpl:::replace = [
0359 mpl:::    # some defaults that should work for most cases
0360 mpl:::    (' pt '    , r' $p_\mathrm{T}$ '),
0361 mpl:::    ('pT '     , r'$p_\mathrm{T}$ '),
0362 mpl:::    (' pT'     , r' $p_\mathrm{T}$'),
0363 mpl:::    ('p_{T}'   , r'$p_\mathrm{T}$'),
0364 mpl:::    ('E_{T}'   , r'$E_\mathrm{T}$'),
0365 mpl:::    ('#eta'    , r'$\eta$'),
0366 mpl:::    ('#phi'    , r'$\phi$'),
0367 mpl:::    ('fb^{-1}' , r'$\mathrm{fb}^{-1}$'),
0368 mpl:::    ('pb^{-1}' , r'$\mathrm{pb}^{-1}$'),
0369 mpl:::    ('<'       , r'$<$'),
0370 mpl:::    ('>'       , r'$>$'),
0371 mpl:::    ('#'       , r''),
0372 mpl:::    ]
0373 mpl:::
0374 mpl:::## If you include 'use_regexp' as the first item, the patterns to be replaced
0375 mpl:::## will function as regular expressions using python's re module rather than
0376 mpl:::## as simple text.  The example below turn's ROOT's superscript and subscript
0377 mpl:::## syntax into LaTeX:
0378 mpl:::
0379 mpl:::## replace = [
0380 mpl:::##     ('use_regexp', True),
0381 mpl:::##     (r'\^\{(.*)\}', r'$^{\1}$'),
0382 mpl:::##     (r'\_\{(.*)\}', r'$_{\1}$'),
0383 mpl:::## ]
0384 mpl:::
0385 mpl:::#### A function that will be executed after all histograms have been drawn.
0386 mpl:::#### It can be used to add extra decorations to your figure.
0387 mpl:::def decoration_mpl(figure, axeses, path, options, hists):
0388 mpl:::    #### Draw a line to indicate a cut
0389 mpl:::    ## axeses[0].axvline(5., color='red', linestyle='--')
0390 mpl:::    #### Add a caption
0391 mpl:::    ## figure.text(0.6, 0.15, "CMS Preliminary")
0392 mpl:::    return
0393 
0394 ##############################################################################
0395 ######## HTML Output #########################################################
0396 
0397 #### Number of columns for images in HTML output
0398 ncolumns_html = 2
0399 
0400 #### Provide a template for the html index files
0401 html_template=r'''
0402     <html>
0403     <head>
0404     <link rel='shortcut icon' href='http://packages.python.org/rootplot/_static/rootplot.ico'>
0405     <link href='http://fonts.googleapis.com/css?family=Yanone+Kaffeesatz:bold' rel='stylesheet' type='text/css'>
0406     <style type='text/css'>
0407         body { padding: 10px; font-family:Arial, Helvetica, sans-serif;
0408                font-size:15px; color:#FFF; font-size: large; 
0409                background-image: url(
0410                    'http://packages.python.org/rootplot/_static/tile.jpg');}
0411         img    { border: solid black 1px; margin:10px; }
0412         object { border: solid black 1px; margin:10px; }
0413         h1   { text-shadow: 2px 2px 2px #000;
0414                font-size:105px; color:#fff; border-bottom: solid black 1px;
0415                font-size: 300%%; font-family: 'Yanone Kaffeesatz'}
0416         a, a:active, a:visited {
0417                color:#FADA00; text-decoration:none; }
0418         a:hover{ color:#FFFF00; text-decoration:none;
0419                  text-shadow: 0px 0px 5px #fff; }
0420     </style>
0421     <title>%(path)s</title>
0422     </head>
0423     <body>
0424     <a style="" href="http://packages.python.org/rootplot/"><img style="position: absolute; top:10 px; right: 10px; border: 0px" src="http://packages.python.org/rootplot/_static/rootplot-logo.png"></a>
0425     <h1>Navigation</h1>
0426       %(back_nav)s
0427       <ul>
0428           %(forward_nav)s
0429       </ul>
0430     <h1>Images</h1>
0431     %(plots)s
0432     <p style='font-size: x-small; text-align: center;'>
0433       <a href='http://www.greepit.com/resume-template/resume.htm'>
0434         Based on a template by Sarfraz Shoukat</a></p>
0435     </body>
0436     </html>
0437 '''
0438 """
0439 
0440 multi_call_template = '''
0441 calls.append("""
0442 %s
0443 %s
0444 """)
0445 '''
0446 
0447 allplots_template = '''
0448 ## This file contains all the necessary calls to the rootplot API to produce
0449 ## the same set of plots that were created from the command-line.
0450 
0451 ## You can use this file to intercept the objects and manipulate them before
0452 ## the figure is saved, making any custom changes that are not possible from
0453 ## the command-line.
0454 
0455 ## 'objects' is a python dictionary containing all the elements used in the
0456 ## plot, including 'hists', 'legend', etc.
0457 ##   ex: objects['hists'] returns a list of histograms
0458 
0459 try:
0460   ## the normal way to import rootplot
0461   from rootplot import plot, plotmpl
0462 except ImportError:
0463   ## special import for CMSSW installations of rootplot
0464   from PhysicsTools.PythonAnalysis.rootplot import plot, plotmpl
0465 
0466 import os
0467 os.chdir('..')  # return to the directory with the ROOT files
0468 
0469 %(call_statements)s
0470 '''
0471 
0472 allplots_multi_template = '''
0473 ## This file is the same as allplots.py, except that it uses multiprocessing
0474 ## to make better use of machines with multiple cores
0475 
0476 try:
0477   ## the normal way to import rootplot
0478   from rootplot import plot, plotmpl
0479   from rootplot.core import report_progress
0480 except ImportError:
0481   ## special import for CMSSW installations of rootplot
0482   from PhysicsTools.PythonAnalysis.rootplot import plot, plotmpl
0483   from PhysicsTools.PythonAnalysis.rootplot.core import report_progress
0484 import ROOT
0485 import multiprocessing as multi
0486 
0487 import os
0488 os.chdir('..')  # return to the directory with the ROOT files
0489 
0490 calls = []
0491 
0492 %(call_statements)s
0493 
0494 queue = multi.JoinableQueue()
0495 qglobals = multi.Manager().Namespace()
0496 qglobals.nfinished = 0
0497 qglobals.ntotal = len(calls)
0498 for call in calls:
0499     queue.put(call)
0500 
0501 def qfunc(queue, qglobals):
0502     from Queue import Empty
0503     while True:
0504         try: mycall = queue.get(timeout=5)
0505         except (Empty, IOError): break
0506         exec(mycall)
0507         ROOT.gROOT.GetListOfCanvases().Clear()
0508         qglobals.nfinished += 1
0509         report_progress(qglobals.nfinished, qglobals.ntotal, 
0510                         '%(output)s', '%(ext)s')
0511         queue.task_done()
0512 
0513 for i in range(%(processors)i):
0514     p = multi.Process(target=qfunc, args=(queue, qglobals))
0515     p.daemon = True
0516     p.start()
0517 queue.join()
0518 report_progress(len(calls), len(calls), '%(output)s', '%(ext)s')
0519 print ''
0520 '''
0521 
0522 ## remove leading blank lines from the templates
0523 for key, value in globals().items():
0524     if 'template' in key:
0525         globals()[key] = value[1:]
0526 
0527 ##############################################################################
0528 ######## The Command-Line Interface ##########################################
0529 
0530 def cli_rootplotmpl():
0531     """
0532     An application for plotting histograms from a ROOT file with |matplotlib|.
0533 
0534     It is invoked from the command-line as ``rootplotmpl``.
0535     """
0536     global use_mpl
0537     use_mpl = True
0538     cli_rootplot()
0539     
0540 def cli_rootplot():
0541     """
0542     An application for plotting histograms from a ROOT file.
0543 
0544     It is invoked from the command-line as ``rootplot``.
0545     """
0546     options = parse_arguments(sys.argv[1:])
0547     optdiff = options.kwarg_list()
0548     if options.debug:
0549         rootplot(*options.arguments(), **optdiff)
0550     else:
0551         try:
0552             rootplot(*options.arguments(), **optdiff)
0553         except Exception as e:
0554             print("Error:", e)
0555             print("For usage details, call '%s --help'" % prog)
0556             sys.exit(1)
0557 
0558 ##############################################################################
0559 ######## The Application Programming Interface ###############################
0560 
0561 def plotmpl(*args, **kwargs):
0562     """
0563     call signature::
0564 
0565       plotmpl(file1, file2, file3, ..., target, **kwargs):
0566 
0567     build a matplotlib figure, pulling the *target* histogram from each of the
0568     *files*.
0569 
0570     call signature::
0571 
0572       plotmpl(file, target1, target2, target3, ..., **kwargs):
0573 
0574     build a matplotlib figure, pulling all *target* histograms from *file*.
0575 
0576     With either of these signatures, the plot style is specified through
0577     *kwargs*, which can accept any of the options available to
0578     :mod:`rootplotmpl` at the command-line.
0579 
0580     Returns the tuple (*figure*, *axeses*, *stack*, *hists*, *plotpath*).
0581     """
0582     global use_mpl
0583     use_mpl = True
0584     return plot(*args, **kwargs)
0585 
0586 def plot(*args, **kwargs):
0587     """
0588     call signature::
0589 
0590       plot(file1, file2, file3, ..., target, **kwargs):
0591 
0592     build a ROOT canvas, pulling the *target* histogram from each of the
0593     *files*.
0594 
0595     call signature::
0596 
0597       plotmpl(file, target1, target2, target3, ..., **kwargs):
0598 
0599     build a ROOT canvas, pulling all *target* histograms from *file*.
0600 
0601     With either of these signatures, the plot style is specified through
0602     *kwargs*, which can accept any of the options available to
0603     :mod:`rootplot` at the command-line.
0604 
0605     Returns the tuple (*canvas*, *pads*, *stack*, *hists*, *plotpath*).
0606     """
0607     hists, options = initialize_hists(args, kwargs)
0608     if use_mpl:
0609         return plot_hists_mpl(hists, options)
0610     else:
0611         return plot_hists_root(hists, options)
0612 
0613 def rootplotmpl(*args, **kwargs):
0614     """
0615     call signature::
0616 
0617       rootplotmpl(file1, file2, file3, ..., **kwargs):
0618 
0619     build ROOT canvases from corresponding histograms in each of the *files*.
0620 
0621     call signature::
0622 
0623       rootplotmpl(file, folder1, folder2, folder3, ..., **kwargs):
0624 
0625     build ROOT canvases from corresponding histograms in each of the *folders*
0626     in *file*.
0627 
0628     call signature::
0629 
0630       rootplotmpl(file, target1, target2, target3, ..., **kwargs):
0631 
0632     build a ROOT canvas from each of the *targets* in *file*.
0633 
0634     With any of these call signatures, images are generated in an output
0635     directory along with a script with the necessary calls to :func:`plotmpl`
0636     to reproduce each of the canvases.  The plot style is specified through
0637     *kwargs*, which can accept any of the options available to
0638     :mod:`rootplotmpl` at the command-line.
0639     """
0640     global use_mpl
0641     use_mpl = True
0642     rootplot(args, kwargs)
0643 
0644 def rootplot(*args, **kwargs):
0645     """
0646     call signature::
0647 
0648       rootplot(file1, file2, file3, ..., **kwargs):
0649 
0650     build ROOT canvases from corresponding histograms in each of the *files*.
0651 
0652     call signature::
0653 
0654       rootplot(file, folder1, folder2, folder3, ..., **kwargs):
0655 
0656     build ROOT canvases from corresponding histograms in each of the *folders*
0657     in *file*.
0658 
0659     call signature::
0660 
0661       rootplot(file, target1, target2, target3, ..., **kwargs):
0662 
0663     build a ROOT canvas from each of the *targets* in *file*.
0664 
0665     With any of these call signatures, images are generated in an output
0666     directory along with a script with the necessary calls to :func:`plot`
0667     to reproduce each of the canvases.  The plot style is specified through
0668     *kwargs*, which can accept any of the options available to
0669     :mod:`rootplot` at the command-line.
0670     """
0671     if 'config' in kwargs:
0672         write_config()
0673     options = fill_options(args, kwargs, scope='global')
0674     nfiles = len(options.filenames)
0675     ntargets = len(options.targets)
0676     if nfiles < 1:
0677         raise TypeError("%s takes at least 1 filename argument (0 given)" %
0678                         prog)
0679     elif ntargets > 0 and nfiles > 1:
0680         raise TypeError("%s cannot accept targets (%i given) when "
0681                         "multiple files are specified (%i given)" %
0682                         (prog, ntargets, nfiles))
0683     rootfiles = [RootFile(filename) for filename in options.filenames]
0684     #### Create the output directory structure
0685     if not options.noclean and os.path.exists(options.output):
0686         shutil.rmtree(options.output)
0687     for path, folders, objects in walk_rootfile('', rootfiles[0], options):
0688         if not os.path.exists(joined(options.output, path)):
0689             os.makedirs(joined(options.output, path))
0690     #### Loop over plots to make, building the necessary calls
0691     plotargs = get_plot_inputs(rootfiles, options)
0692     call_lists = []
0693     ndigits = int(math.log10(len(plotargs))) + 1
0694     for i, (filenames, targets) in enumerate(plotargs):
0695         argstring = ', '.join(["'%s'" % x for x in (filenames + targets +
0696                                                     options.configs)])
0697         reduced_kwargs = dict(kwargs)
0698         for key, value in reduced_kwargs.items():
0699             if key in global_opts:
0700                 del reduced_kwargs[key]
0701             elif isinstance(value, str):
0702                 reduced_kwargs[key] = "'%s'" % value
0703         if 'numbering' in kwargs:
0704             reduced_kwargs['numbering'] = i + 1
0705         optstring = ', '.join(['%s=%s' % (key, value)
0706                                for key, value in reduced_kwargs.items()])
0707         if optstring:
0708             argstring = "%s, %s" % (argstring, optstring)
0709         plotpath, title, legentries = get_plotpath(filenames, targets)
0710         savepath = joined(options.output, plotpath)
0711         if 'numbering' in reduced_kwargs:
0712             dirs = savepath.split('/')
0713             dirs[-1] = str(i + 1).zfill(ndigits) + dirs[-1]
0714             savepath = '/'.join(dirs)
0715         call_vars = {'argstring' : argstring, 'ext' : options.ext,
0716                      'savepath' : savepath}
0717         if use_mpl:
0718             call_vars['trans'] = options.transparent
0719             call_vars['dpi'] = options.dpi
0720             api_call = ("figure, objects = "
0721                         "plotmpl(%(argstring)s)" % call_vars)
0722             save_call = ("figure.savefig('%(savepath)s', "
0723                          "transparent=%(trans)s, "
0724                          "dpi=%(dpi)s)" % call_vars)
0725         else:
0726             api_call = ("canvas, objects = "
0727                         "plot(%(argstring)s)" % call_vars)
0728             save_call = "canvas.SaveAs('%(savepath)s.%(ext)s')" % call_vars
0729         call_lists.append([api_call, save_call])
0730     #### Create scripts for that make the API calls
0731     ext = options.ext
0732     output = options.output
0733     processors = options.processors
0734     call_statements = '\n\n'.join([plotcall + '\n' + savecall 
0735                                    for plotcall, savecall in call_lists])
0736     allplots_script = allplots_template % locals()
0737     call_statements = "".join([multi_call_template % (plotcall, savecall) 
0738                                for plotcall, savecall in call_lists])
0739     allplots_multi_script = allplots_multi_template % locals()
0740     write_to_file(allplots_script, joined(options.output, 'allplots.py'))
0741     write_to_file(allplots_multi_script, 
0742                  joined(options.output, 'allplots_multi.py'))
0743     #### Execute the calls
0744     if use_multiprocessing:
0745         original_dir = os.getcwd()
0746         os.chdir(options.output)
0747         exec(allplots_multi_script)
0748         os.chdir(original_dir)
0749     else:
0750         for i, call_list in enumerate(call_lists):
0751             make_calls(*call_list)
0752             report_progress(i + 1, len(plotargs), options.output, options.ext)
0753         report_progress(len(plotargs), len(plotargs),
0754                         options.output, options.ext)
0755         print('')
0756     ## clean out empty directories
0757     for root, dirs, files in os.walk(options.output):
0758         if not os.listdir(root):
0759             os.rmdir(root)
0760     ## add index.html files to all directories
0761     if options.ext in ['png', 'gif', 'svg']:
0762         print("Writing html index files...")
0763         width, height = options.size
0764         if use_mpl:
0765             width, height = [x * options.dpi for x in options.size]
0766         for path, dirs, files in os.walk(options.output):
0767             dirs, files = sorted(dirs), sorted(files)
0768             make_html_index(path, dirs, files, options.ext,
0769                             options.html_template, options.ncolumns_html,
0770                             width, height)
0771     if options.merge:
0772         merge_pdf(options)
0773 
0774 
0775 ##############################################################################
0776 ######## Implementation ######################################################
0777 
0778 def write_to_file(script, destination):
0779     f = open(destination, 'w')
0780     f.write(script)
0781     f.close()
0782 
0783 def make_calls(api_call, save_call):
0784     exec(api_call)
0785     exec(save_call)
0786 
0787 def option_diff(default, modified):
0788     #### Return a dict with the values from modified not present in default.
0789     diff = {}
0790     for key in dir(default):
0791         default_val = getattr(default, key)
0792         modified_val = getattr(modified, key)
0793         if (type(default_val) in [int, float, str, bool, type(None)] and
0794             key in dir(modified) and default_val != modified_val):
0795             diff[key] = modified_val
0796     return diff
0797 
0798 def config_string():
0799     s = config_template
0800     if use_mpl:
0801         s = re.sub('root::.*\n', '', s)
0802         s = s.replace('mpl:::', '')
0803         s = s.replace('%prog', 'rootplotmpl')
0804     else:
0805         s = re.sub('mpl:::.*\n', '', s)
0806         s = s.replace('root::', '')
0807         s = s.replace('%prog', 'rootplot')
0808     return s
0809 
0810 def write_config():
0811     if use_mpl:
0812         filename = 'rootplotmpl_config.py'
0813     else:
0814         filename = 'rootplot_config.py'
0815     f = open(filename, 'w')
0816     f.write(config_string())
0817     f.close()
0818     print("Wrote %s to the current directory" % filename)
0819     sys.exit(0)
0820 
0821 def add_from_config_files(options, configs):
0822     def append_to_options(config, options):
0823         for attribute in dir(config):
0824             if '__' not in attribute:
0825                 attr = getattr(config, attribute)
0826                 setattr(options, attribute, attr)
0827     configdir = tempfile.mkdtemp()
0828     sys.path.insert(0, '')
0829     sys.path.insert(0, configdir)
0830     f = open(joined(configdir, 'default_config.py'), 'w')
0831     f.write(config_string())
0832     f.close()
0833     import default_config
0834     append_to_options(default_config, options)
0835     if use_mpl:
0836         rc_name = 'rootplotmplrc'
0837     else:
0838         rc_name = 'rootplotrc'
0839     rc_path = os.path.expanduser('~/.%s' % rc_name)
0840     if os.path.exists(rc_path):
0841         print("Using styles and options from ~/.%s" % rc_name)
0842         shutil.copy(rc_path, joined(configdir, '%s.py' % rc_name))
0843         configs.insert(0, '%s.py' % rc_name)
0844     for f in configs:
0845         user_config = __import__(f[:-3])
0846         append_to_options(user_config, options)
0847     shutil.rmtree(configdir)
0848 
0849 def walk_rootfile(path, rootfile, options):
0850     #### Yield (path, folders, objects) for each directory under path.
0851     keys = rootfile.file.GetDirectory(path).GetListOfKeys()
0852     folders, objects = [], []
0853     for key in keys:
0854         name = key.GetName()
0855         classname = key.GetClassName()
0856         newpath = joined(path, name)
0857         dimension = 0
0858         matches_path = re.match(options.path, newpath)
0859         if 'TDirectory' in classname:
0860             folders.append(name)
0861         elif ('TH1' in classname or 'TGraph' in classname or
0862             classname == 'TProfile'):
0863             dimension = 1
0864         elif options.draw2D and ('TH2' in classname or 
0865                                  classname == 'TProfile2D'):
0866             dimension = 2
0867         if (matches_path and dimension):
0868             objects.append(name)
0869     yield path, folders, objects
0870     for folder in folders:
0871         for x in walk_rootfile(joined(path, folder), rootfile, options):
0872             yield x
0873         
0874 def get_plot_inputs(files, options):
0875     #### Return a list of argument sets to be sent to plot().
0876     target_lists = []
0877     if options.targets:
0878         for path, folders, objects in walk_rootfile('', files[0], options):
0879             if path == options.targets[0]:
0880                 #### targets are folders
0881                 for obj in objects:
0882                     target_lists.append([joined(t, obj) 
0883                                          for t in options.targets])
0884     else:
0885         target_sets = [set() for f in files]
0886         for i, f in enumerate(files):
0887             for path, folders, objects in walk_rootfile('', f, options):
0888                 for obj in objects:
0889                     target_sets[i].add(joined(path, obj))
0890         target_set = target_sets[0]
0891         for s in target_sets:
0892             target_set &= s
0893         target_lists = [[t] for t in target_set]
0894     if not target_lists:
0895         return [(options.filenames, options.targets)]
0896     else:
0897         return [(options.filenames, list(t)) for t in target_lists]
0898 
0899 def fill_options(args, kwargs, scope):
0900     options = parse_arguments(args, scope=scope)
0901     for key, value in kwargs.items():
0902         options[key] = value
0903     options.process_configs(scope=scope)
0904     options.size = parse_size(options.size)
0905     options.split = options.ratio_split or options.efficiency_split
0906     options.ratio = (options.ratio or options.efficiency or
0907                      options.ratio_split or options.efficiency_split)
0908     options.efficiency = options.efficiency or options.efficiency_split
0909     if len(options.filenames) > 1 or len(options.targets) > 1:
0910         options.draw2D = None
0911     return options
0912 
0913 def plot_hists_root(hists, options):
0914     #### Create a plot.
0915     canvas = ROOT.TCanvas("canvas", "", 
0916                           int(options.size[0]), int(options.size[1]))
0917     isTGraph = 'TGraph' in hists[0].rootclass
0918     objects = {'pads': [canvas]}
0919     if options.ratio:
0920         if options.split:
0921             objects['pads'] = divide_canvas(canvas, options.ratio_fraction)
0922             objects['pads'][0].cd()
0923         else:
0924             hists = make_ratio_hists(hists, options, options.ratio - 1)
0925             isTGraph = True
0926     if options.xerr:
0927         ROOT.gStyle.SetErrorX()
0928     histmax, first_draw, roothists = None, True, []
0929     if isTGraph:
0930         objects['multigraph'] = ROOT.TMultiGraph()
0931     else:
0932         objects['stack'] = ROOT.THStack(
0933             'st%s' % os.path.basename(options.plotpath),
0934             '%s;%s;%s' % (hists[0].title,
0935                           hists[0].xlabel, hists[0].ylabel))
0936     for i, hist in enumerate(hists):
0937         if not hist: continue
0938         name = "%s_%i" % (options.plotpath, i)
0939         if isTGraph:
0940             roothist = hist.TGraph(name=name)
0941         elif isinstance(hist, Hist):
0942             roothist = hist.TH1F(name=name.replace('/', '__'))
0943         else:
0944             roothist = hist.TH2F(name=name)
0945         roothist.SetLineStyle(options.line_styles[i])
0946         roothist.SetLineColor(options.line_colors[i])
0947         roothist.SetFillColor(options.fill_colors[i])
0948         roothist.SetMarkerColor(options.marker_colors[i])
0949         roothist.SetFillStyle(options.fill_styles[i])
0950         roothist.SetMarkerStyle(options.marker_styles[i])
0951         roothist.SetMarkerSize(options.marker_sizes[i])
0952         roothists.append(roothist)
0953         if (isinstance(hist, Hist) and not isTGraph and 
0954             'stack' in options.draw_commands[i]):
0955             objects['stack'].Add(roothist)
0956     if 'stack' in objects and objects['stack'].GetHists():
0957         histmax = objects['stack'].GetMaximum()
0958     for roothist in roothists:
0959         histmax = max(histmax, roothist.GetMaximum())
0960     dimension = 1
0961     if isinstance(hist, Hist2D):
0962         dimension = 2
0963     if options.gridx or options.grid:
0964         for pad in objects['pads']:
0965             pad.SetGridx(not pad.GetGridx())
0966     if options.gridy or options.grid:
0967         objects['pads'][0].SetGridy(not objects['pads'][0].GetGridy())
0968     objects['legend'] = ROOT.TLegend(*parse_legend_root(options))
0969     for com in options.draw_commands:
0970         if 'stack' in com:
0971             first_draw = prep_first_draw(objects['stack'], histmax, options)
0972             com = com.replace('stack', '')
0973             objects['stack'].Draw(com)
0974             break
0975     for i, roothist in enumerate(roothists):
0976         if isTGraph:
0977             objects['multigraph'].Add(roothist)
0978         elif dimension == 1:
0979             if 'stack' not in options.draw_commands[i]:
0980                 if first_draw:
0981                     first_draw = prep_first_draw(roothist, histmax, options)
0982                     roothist.Draw(options.draw_commands[i])
0983                 else:
0984                     roothist.Draw("same " + options.draw_commands[i])
0985         else:
0986             roothist.Draw(options.draw2D)
0987         legendopt = 'lp'
0988         if options.fill_styles[i]: legendopt += 'f'
0989         if 'e' in options.draw_commands[i]: legendopt += 'e'
0990         objects['legend'].AddEntry(roothist, options.legend_entries[i],
0991                                    legendopt)
0992     if isTGraph:
0993         objects['multigraph'].Draw(options.draw_graph)
0994         prep_first_draw(objects['multigraph'], histmax, options)
0995         objects['multigraph'].Draw(options.draw_graph)
0996     if options.split and dimension == 1:
0997         objects['pads'][1].cd()
0998         objects['ratio_multigraph'] = plot_ratio_root(
0999             hists, roothist.GetXaxis().GetTitle(), options)
1000         xmin = hists[0].xedges[0]
1001         xmax = hists[0].xedges[-1]
1002         objects['ratio_multigraph'].GetXaxis().SetRangeUser(xmin, xmax)
1003         objects['pads'][0].cd()
1004     if options.logx:
1005         for pad in objects['pads']:
1006             pad.SetLogx(True)
1007     if options.logy:
1008         objects['pads'][0].SetLogy(True)
1009     if options.ratio_logy:
1010         if len(objects['pads']) > 1:
1011             objects['pads'][1].SetLogy(True)
1012     if options.numbering:
1013         display_page_number(options)
1014     if roothist.InheritsFrom('TH1'):
1015         if options.overflow:
1016             display_overflow(objects['stack'], roothist)
1017         if options.underflow:
1018             display_underflow(objects['stack'], roothist)
1019     if options.legend_location and dimension == 1:
1020         objects['legend'].Draw()
1021     exec(options.decoration_root)
1022     objects['hists'] = roothists
1023     return canvas, objects
1024 
1025 def plot_hists_mpl(hists, options):
1026     #### Create a plot.
1027     fig = plt.figure(1, figsize=options.size)
1028     fig.clf()     # clear figure
1029     axes = plt.axes()
1030     objects = {'axes' : [axes, axes]}
1031     if options.ratio:
1032         if options.split:
1033             objects['axes'] = divide_axes(fig, axes, options.ratio_fraction)
1034             axes = objects['axes'][0]
1035             fig.sca(axes)
1036         else:
1037             hists = make_ratio_hists(hists, options, options.ratio - 1)
1038     refhist = hists[0]
1039     if refhist is None:
1040         refhist = hists[1]
1041     fullstack, objects['stack'] = HistStack(), HistStack()
1042     histmax, allempty = None, True
1043     for i, hist in enumerate(hists):
1044         if hist and hist.entries:
1045             allempty = False
1046         if isinstance(hist, Hist):
1047             # Avoid errors due to zero bins with log y axis
1048             if options.logy and options.plot_styles[i] != 'errorbar':
1049                 for j in range(hist.nbins):
1050                     hist.y[j] = max(hist.y[j], 1e-10)
1051             if options.plot_styles[i] in ['barh', 'barcluster', 'stack']:
1052                 objects['stack'].add(hist, log=options.logy,
1053                                      hatch=options.fill_styles[i],
1054                                      linestyle=options.line_styles[i],
1055                                      edgecolor=options.line_colors[i],
1056                                      facecolor=options.fill_colors[i])
1057             fullstack.add(hist)
1058     if 'stack' in options.plot_styles:
1059         histmax = max(histmax, objects['stack'].stackmax())
1060     elif 'barh' in options.plot_styles or 'barcluster' in options.plot_styles:
1061         histmax = max(histmax, objects['stack'].max())
1062     for hist in fullstack:
1063         histmax = max(histmax, max(hist))
1064     if allempty:
1065         fig.text(0.5, 0.5, "No Entries", ha='center', va='center')
1066     elif isinstance(refhist, Hist):
1067         for i, hist in enumerate(hists):
1068             if hist:
1069                 if options.plot_styles[i] == "errorbar":
1070                     if options.logy:
1071                         axes.set_yscale('log')
1072                         # Logy would fail if hist all zeroes
1073                         if not np.nonzero(hist.y)[0].tolist():
1074                             continue
1075                         # Errorbars get messed up when they extend down to zero
1076                         for j in range(hist.nbins):
1077                             yerr = hist.yerr[0][j]
1078                             if (hist[j] - yerr) < (0.01 * yerr):
1079                                 hist.yerr[0][j] *= 0.99
1080                     hist.errorbar(fmt=options.marker_styles[i],
1081                                   yerr=True,
1082                                   xerr=options.xerr,
1083                                   markersize=options.marker_sizes[i],
1084                                   color=options.fill_colors[i],
1085                                   ecolor=options.errorbar_colors[i],
1086                                   label_rotation=options.xlabel_rotation,
1087                                   label_alignment=options.xlabel_alignment)
1088                 elif options.plot_styles[i] == "bar":
1089                     hist.bar(alpha=options.alphas[i], 
1090                              log=options.logy,
1091                              width=options.barwidth,
1092                              hatch=options.fill_styles[i],
1093                              edgecolor=options.line_colors[i],
1094                              facecolor=options.fill_colors[i],
1095                              label_rotation=options.xlabel_rotation,
1096                              label_alignment=options.xlabel_alignment)
1097                 elif 'hist' in options.plot_styles[i]:
1098                     histtype = 'step'
1099                     if 'fill' in options.plot_styles[i]:
1100                         histtype = 'stepfilled'
1101                     hist.hist(alpha=options.alphas[i],
1102                               histtype=histtype,
1103                               log=options.logy,
1104                               hatch=options.fill_styles[i],
1105                               edgecolor=options.line_colors[i],
1106                               facecolor=options.fill_colors[i],
1107                               label_rotation=options.xlabel_rotation,
1108                               label_alignment=options.xlabel_alignment)
1109                 if options.logx:
1110                     for ax in objects['axes']:
1111                         ax.set_xscale('log')
1112         if objects['stack'].hists:
1113             if 'stack' in options.plot_styles:
1114                 objects['stack'].histstack(
1115                     histtype='stepfilled',
1116                     label_rotation=options.xlabel_rotation,
1117                     label_alignment=options.xlabel_alignment)
1118             elif 'barh' in options.plot_styles:
1119                 objects['stack'].barh(
1120                     width=options.barwidth,
1121                     label_rotation=options.xlabel_rotation,
1122                     label_alignment=options.xlabel_alignmenth)
1123             elif 'barcluster' in options.plot_styles:
1124                 objects['stack'].barcluster(
1125                     width=options.barwidth,
1126                     label_rotation=options.xlabel_rotation,
1127                     label_alignment=options.xlabel_alignment)
1128         if 'barh' not in options.plot_styles:
1129             axes.set_xlim(refhist.xedges[0], refhist.xedges[-1])
1130         if options.logy:
1131             my_min = fullstack.min(threshold=1.1e-10)
1132             rounded_min = 1e100
1133             while (rounded_min > my_min):
1134                 rounded_min /= 10
1135             axes.set_ylim(ymin=rounded_min)
1136         if options.xmin is not None:
1137             axes.set_xlim(xmin=options.xmin)
1138         if options.xmax is not None:
1139             axes.set_xlim(xmax=options.xmax)
1140         if options.ymin is not None:
1141             axes.set_ylim(ymin=options.ymin)
1142         if options.ymax is not None:
1143             axes.set_ylim(ymax=options.ymax)
1144         elif ('barh' not in options.plot_styles and 
1145               histmax != 0 and not options.ymax):
1146             axes.set_ylim(ymax=histmax * options.top_padding_factor)
1147         if options.overflow:
1148             axes.text(hist.x[-1], axes.set_ylim()[0], options.overflow_text,
1149                       rotation='vertical', ha='center',
1150                       alpha=options.overflow_alpha, size=options.overflow_size)
1151         if options.underflow:
1152             axes.text(hist.x[0], axes.set_ylim()[0], options.underflow_text,
1153                       rotation='vertical', ha='center',
1154                       alpha=options.overflow_alpha, size=options.overflow_size)
1155         if options.gridx or options.grid:
1156             axes.xaxis.grid()
1157         if options.gridy or options.grid:
1158             axes.yaxis.grid()
1159         if (options.legend_location != 'None' or options.legend_axes_bbox or 
1160             options.legend_figure_bbox):
1161             try:
1162                 options.legend_location = int(options.legend_location)
1163             except ValueError:
1164                 pass
1165             if options.legend_axes_bbox:
1166                 kwargs = {'bbox_to_anchor' : options.legend_axes_bbox}
1167             elif options.legend_figure_bbox:
1168                 kwargs = {'bbox_to_anchor' : options.legend_figure_bbox,
1169                           'bbox_transform' : fig.transFigure}
1170             else:
1171                 kwargs = {'loc' : options.legend_location}
1172             if options.legend_ncols:
1173                 kwargs['ncol'] = int(options.legend_ncols)
1174             objects['legend'] = axes.legend(numpoints=1, **kwargs)
1175     elif isinstance(refhist, Hist2D):
1176         drawfunc = getattr(hist, options.draw2D)
1177         if 'col' in options.draw2D:
1178             if options.cmap:
1179                 drawfunc(cmap=options.cmap)
1180             else:
1181                 drawfunc()
1182         else:
1183             drawfunc(color=options.fill_colors[0])
1184     axes.set_title(r2m.replace(refhist.title, options.replace))
1185     if 'barh' in options.plot_styles:
1186         set_ylabel = objects['axes'][1].set_xlabel
1187         set_xlabel = axes.set_ylabel
1188     else:
1189         set_ylabel = axes.set_ylabel
1190         set_xlabel = objects['axes'][1].set_xlabel
1191     if options.ratio and not options.split and not options.ylabel:
1192         ratio_file = options.legend_entries[options.ratio - 1]
1193         if options.efficiency:
1194             set_ylabel(options.efficiency_label % locals())
1195         else:
1196             set_ylabel(options.ratio_label % locals())
1197     else:
1198         set_ylabel(r2m.replace(refhist.ylabel, options.replace))
1199     set_xlabel(r2m.replace(refhist.xlabel, options.replace))
1200     if options.split:
1201         fig.sca(objects['axes'][1])
1202         plot_ratio_mpl(objects['axes'][1], hists, options)
1203         fig.sca(objects['axes'][0])
1204     if options.numbering:
1205         fig.text(options.numbering_x_mpl, options.numbering_y_mpl,
1206                  options.numbering, size=options.numbering_size_mpl,
1207                  ha=options.numbering_ha_mpl, va=options.numbering_va_mpl)
1208     options.decoration_mpl(fig, objects['axes'], options.plotpath,
1209                            options, hists)
1210     objects['hists'] = hists
1211     return fig, objects
1212 
1213 def initialize_hists(args, kwargs):
1214     options = fill_options(args, kwargs, scope='plot')
1215     if use_mpl:
1216         load_matplotlib(options.ext)
1217     nfiles = len(options.filenames)
1218     ntargets = len(options.targets)
1219     if nfiles < 1:
1220         raise TypeError("plot() takes at least 1 filename argument (0 given)")
1221     elif ntargets > 1 and nfiles > 1:
1222         raise TypeError("plot() takes exactly 1 target (%i given) when "
1223                         "multiple files are specified (%i given)" %
1224                         (nfiles, ntargets))
1225     options.nhists = max(nfiles, ntargets)
1226     process_options(options)
1227     files = [RootFile(filename) for filename in options.filenames]
1228     hists = []
1229     stack_integral = 0.
1230     for i, (f, target) in enumerate(cartesian_product(files, options.targets)):
1231         try:
1232             roothist = f.file.Get(target)
1233         except ReferenceError:
1234             hists.append(None)
1235             continue
1236         try:
1237             isTGraph = not roothist.InheritsFrom('TH1')
1238         except TypeError:
1239             raise TypeError("'%s' does not appear to be a valid target" %
1240                             target)
1241         dimension = 1
1242         if use_mpl:
1243             stacked = 'stack' in options.plot_styles[i]
1244         else:
1245             stacked = 'stack' in options.draw_commands[i]
1246         if not isTGraph:
1247             dimension = roothist.GetDimension()
1248             roothist.Scale(options.scale[i])
1249             if options.rebin:
1250                 roothist.Rebin(options.rebin)
1251         title, xlabel, ylabel = get_labels(roothist, options)
1252         if options.normalize:
1253             norm_file = options.legend_entries[options.normalize - 1]
1254             ylabel = options.target_normalized_title % locals()
1255         if options.area_normalize:
1256             ylabel = options.area_normalized_title
1257         if dimension == 1:
1258             hist = Hist(roothist, label=options.legend_entries[i],
1259                         title=title, xlabel=xlabel, ylabel=ylabel)
1260         else:
1261             hist = Hist2D(roothist, label=options.legend_entries[i],
1262                           title=title, xlabel=xlabel, ylabel=ylabel)
1263         if stacked:
1264             stack_integral += roothist.Integral()
1265         roothist.Delete()
1266         hists.append(hist)
1267     for i, hist in enumerate(hists):
1268         if use_mpl:
1269             stacked = 'stack' in options.plot_styles[i]
1270         else:
1271             stacked = 'stack' in options.draw_commands[i]
1272         if dimension == 1:
1273             if options.overflow:
1274                 hist.y[-1] += hist.overflow
1275             if options.underflow:
1276                 hist.y[0] += hist.underflow
1277             if options.area_normalize:
1278                 if sum(hist.y):
1279                     if stacked:
1280                         hist.scale(1./stack_integral)
1281                     else:
1282                         hist.scale(1./sum(hist.y))
1283             if options.normalize:
1284                 numerhist = hists[options.normalize - 1]
1285                 if options.norm_range:
1286                     lowbin, highbin = parse_range(hist.xedges,
1287                                                   options.norm_range)
1288                     numer = numerhist.TH1F().Integral(lowbin, highbin)
1289                     denom = hist.TH1F().Integral(lowbin, highbin)
1290                 else:
1291                     numer = sum(numerhist.y)
1292                     denom = sum(hist.y)
1293                     if stacked:
1294                         denom = stack_integral
1295                 if denom:
1296                     hist.scale(numer / denom)
1297     return hists, options
1298 
1299 def parse_size(size_option):
1300     #### Return a width and height parsed from size_option.
1301     try:
1302         xpos = size_option.find('x')
1303         return float(size_option[:xpos]), float(size_option[xpos+1:])
1304     except TypeError:
1305         return size_option
1306 
1307 def parse_color(color, tcolor=False):
1308     #### Return an rgb tuple or a ROOT TColor from a ROOT color index or
1309     #### an rgb(a) tuple.
1310     if color is None:
1311         return None
1312     elif color == 'none' or color == 'None':
1313         return 'none'
1314     r, g, b = 0, 0, 0
1315     try:
1316         color = ROOT.gROOT.GetColor(color)
1317         r, g, b = color.GetRed(), color.GetGreen(), color.GetBlue()
1318     except TypeError:
1319         try:
1320             if max(color) > 1.:
1321                 color = [x/256. for x in color][0:3]
1322         except TypeError:
1323             pass
1324         try:
1325             color = mpal.colors.colorConverter.to_rgb(color)
1326         except NameError:
1327             pass
1328         r, g, b = color[0:3]
1329     if tcolor:
1330         return ROOT.TColor.GetColor(r, g, b)
1331     return (r, g, b)
1332 
1333 def get_labels(hist, options):
1334     #### Return the appropriate histogram and axis titles for hist.
1335     title = hist.GetTitle().split(';')[0]
1336     xlabel = hist.GetXaxis().GetTitle()
1337     ylabel = hist.GetYaxis().GetTitle()
1338     if options.title:
1339         if options.title.startswith('+'):
1340             title += options.title[1:]
1341         else:
1342             title = options.title
1343     if options.xlabel:
1344         if options.xlabel.startswith('+'):
1345             xlabel += options.xlabel[1:]
1346         else:
1347             xlabel = options.xlabel
1348     if options.ylabel:
1349         if options.ylabel.startswith('+'):
1350             ylabel += options.ylabel[1:]
1351         else:
1352             ylabel = options.ylabel
1353     return title, xlabel, ylabel
1354 
1355 def report_progress(counter, nplots, output, ext, divisor=1):
1356     #### Print the current number of finished plots.
1357     if counter % divisor == 0:
1358         print(("\r%i plots of %i written to %s/ in %s format" %
1359               (counter, nplots, output, ext)), end=' ')
1360         sys.stdout.flush()
1361 
1362 def merge_pdf(options):
1363     #### Merge together all the produced plots into one pdf file.
1364     destination = joined(options.output, 'allplots.pdf')
1365     paths = []
1366     for path, dirs, files in os.walk(options.output):
1367         paths += [joined(path, x) for x in files if x.endswith('.pdf')]
1368     if not paths:
1369         print("No output files, so no merged pdf was made")
1370         return
1371     print("Writing %s..." % destination)
1372     os.system('gs -q -dBATCH -dNOPAUSE -sDEVICE=pdfwrite '
1373               '-dAutoRotatePages=/All '
1374               '-sOutputFile=%s %s' % (destination, ' '.join(paths)))
1375 
1376 def display_page_number(options):
1377     #### Add a page number to the top corner of the canvas.
1378     page_text = ROOT.TText()
1379     page_text.SetTextSize(options.numbering_size_root)
1380     page_text.SetTextAlign(options.numbering_align_root)
1381     page_text.DrawTextNDC(options.numbering_x_root, options.numbering_y_root,
1382                           '%i' % options.numbering)
1383 
1384 def display_overflow(stack, hist):
1385     #### Add the overflow to the last bin and print 'Overflow' on the bin.
1386     nbins = hist.GetNbinsX()
1387     x = 0.5 * (hist.GetBinLowEdge(nbins) +
1388                hist.GetBinLowEdge(nbins + 1))
1389     y = stack.GetMinimum('nostack')
1390     display_bin_text(x, y, nbins, 'Overflow')
1391 
1392 def display_underflow(stack, hist):
1393     #### Add the underflow to the first bin and print 'Underflow' on the bin.
1394     nbins = hist.GetNbinsX()
1395     x = 0.5 * (hist.GetBinLowEdge(1) +
1396                hist.GetBinLowEdge(2))
1397     y = stack.GetMinimum('nostack')
1398     display_bin_text(x, y, nbins, 'Underflow')
1399 
1400 def display_bin_text(x, y, nbins, text):
1401     #### Overlay TEXT on this bin.
1402     bin_text = ROOT.TText()
1403     bin_text.SetTextSize(min(1. / nbins, 0.04))
1404     bin_text.SetTextAlign(12)
1405     bin_text.SetTextAngle(90)
1406     bin_text.SetTextColor(13)
1407     bin_text.SetTextFont(42)
1408     bin_text.DrawText(x, y, text)
1409 
1410 def prep_first_draw(hist, histmax, options):
1411     #### Set all the pad attributes that depend on the first object drawn.
1412     hist.SetMaximum(histmax * options.top_padding_factor)
1413     if options.xmin is not None and options.xmax is not None:
1414         hist.GetXaxis().SetRangeUser(options.xmin, options.xmax)
1415     elif options.xmin is not None:
1416         original_max = hist.GetBinLowEdge(hist.GetNbinsX() + 1)
1417         hist.GetXaxis().SetRangeUser(options.xmin, original_max)
1418     elif options.xmax is not None:
1419         original_min = hist.GetBinLowEdge(1)
1420         hist.GetXaxis().SetRangeUser(original_min, options.xmax)
1421     if options.ymin is not None:
1422         hist.SetMinimum(options.ymin)
1423     if options.ymax is not None:
1424         hist.SetMaximum(options.ymax)
1425     if options.ratio:
1426         if options.split:
1427             hist.Draw()
1428             hist.GetXaxis().SetBinLabel(1, '') # Don't show tick labels
1429             if ';' in hist.GetTitle():
1430                 # dealing with bug in THStack title handling
1431                 titles = hist.GetTitle().split(';')
1432                 if len(titles) > 1: titles[1] = ''
1433                 hist.SetTitle(';'.join(titles))
1434             else:
1435                 hist.GetXaxis().SetTitle('')
1436             ## Avoid overlap of y-axis numbers by supressing zero
1437             if (not options.logy and
1438                 hist.GetMaximum() > 0 and
1439                 hist.GetMinimum() / hist.GetMaximum() < 0.25):
1440                 hist.SetMinimum(hist.GetMaximum() / 10000)
1441         else:
1442             ratio_file = options.legend_entries[options.ratio - 1]
1443             if options.efficiency:
1444                 hist.GetYaxis().SetTitle(options.efficiency_label % locals())
1445             else:
1446                 hist.GetYaxis().SetTitle(options.ratio_label % locals())
1447     return False
1448 
1449 def divide_canvas(canvas, ratio_fraction):
1450     #### Divide the canvas into two pads; the bottom is ratio_fraction tall.
1451     ## Both pads are set to the full canvas size to maintain font sizes
1452     ## Fill style 4000 used to ensure pad transparency because of this
1453     margins = [ROOT.gStyle.GetPadTopMargin(), ROOT.gStyle.GetPadBottomMargin()]
1454     useable_height = 1 - (margins[0] + margins[1])
1455     canvas.Clear()
1456     pad = ROOT.TPad('mainPad', 'mainPad', 0., 0., 1., 1.)
1457     pad.SetFillStyle(4000)
1458     pad.Draw()
1459     pad.SetBottomMargin(margins[1] + ratio_fraction * useable_height)
1460     pad_ratio = ROOT.TPad('ratioPad', 'ratioPad', 0., 0., 1., 1.);
1461     pad_ratio.SetFillStyle(4000)
1462     pad_ratio.Draw()
1463     pad_ratio.SetTopMargin(margins[0] + (1 - ratio_fraction) * useable_height)
1464     return pad, pad_ratio
1465 
1466 def divide_axes(fig, axes, ratio_fraction):
1467     #### Create two subaxes, the lower one taking up ratio_fraction of total.
1468     x1, y1, x2, y2 = axes.get_position().get_points().flatten().tolist()
1469     width = x2 - x1
1470     height = y2 - y1
1471     lower_height = height * ratio_fraction
1472     upper_height = height - lower_height
1473     lower_axes = fig.add_axes([x1, y1, width, lower_height], axisbg='None')
1474     upper_axes = fig.add_axes([x1, y1 + lower_height, width, upper_height],
1475                               axisbg='None', sharex=lower_axes)
1476     ## Make original axes and the upper ticklabels invisible
1477     axes.set_xticks([])
1478     axes.set_yticks([])
1479     plt.setp(upper_axes.get_xticklabels(), visible=False)
1480     return upper_axes, lower_axes
1481 
1482 def make_ratio_hists(hists, options, ratio_index):
1483     denom = hists[ratio_index]
1484     if options.efficiency:
1485         ratios = [hist.divide_wilson(denom) for hist in hists]
1486     else:
1487         ratios = [hist.divide(denom) for hist in hists]        
1488     ratios[ratio_index] = None
1489     return ratios
1490 
1491 def plot_ratio_root(hists, xlabel, options):
1492     #### Plot the ratio of each hist in hists to the ratio_indexth hist.
1493     ratio_index = options.ratio - 1
1494     ratio_file = options.legend_entries[ratio_index]
1495     if options.efficiency:
1496         ylabel = options.efficiency_label % locals()
1497     else:
1498         ylabel = options.ratio_label % locals()
1499     multigraph = ROOT.TMultiGraph("ratio_multi",
1500                                   ";%s;%s" % (xlabel, ylabel))
1501     if options.stack and options.data:
1502         numerator = hists[ratio_index]
1503         hists = hists[:]
1504         hists.pop(ratio_index)
1505         denominator = hists[0]
1506         for hist in hists[1:]:
1507             denominator += hist
1508         hists = [numerator, denominator]
1509         ratio_index = 1
1510     for i, ratio_hist in enumerate(make_ratio_hists(hists, options, 
1511                                                     ratio_index)):
1512         if i == ratio_index:
1513             continue
1514         graph = ratio_hist.TGraph()
1515         graph.SetLineColor(options.line_colors[i])
1516         graph.SetMarkerColor(options.marker_colors[i])
1517         graph.SetMarkerStyle(options.marker_styles[i])
1518         graph.SetMarkerSize(options.marker_sizes[i])
1519         multigraph.Add(graph)
1520     multigraph.Draw(options.draw_graph)
1521     multigraph.GetYaxis().SetNdivisions(507) # Avoids crowded labels
1522     if options.ratio_max is not None: multigraph.SetMaximum(options.ratio_max)
1523     if options.ratio_min is not None: multigraph.SetMinimum(options.ratio_min)
1524     multigraph.Draw(options.draw_graph)
1525     return multigraph
1526 
1527 def plot_ratio_mpl(axes, hists, options):
1528     #### Plot the ratio of each hist in hists to the ratio_indexth hist.
1529     ratio_index = options.ratio - 1
1530     stack = HistStack()
1531     if options.stack and options.data:
1532         numerator = hists[ratio_index]
1533         hists = hists[:]
1534         hists.pop(ratio_index)
1535         denominator = hists[0]
1536         for hist in hists[1:]:
1537             denominator += hist
1538         hists = [numerator, denominator]
1539         ratio_index = 1
1540     for i, ratio_hist in enumerate(make_ratio_hists(hists, options, 
1541                                                     ratio_index)):
1542         if i == ratio_index:
1543             continue
1544         ratio_hist.y = [item or 0 for item in ratio_hist.y] ## Avoid gaps
1545         stack.add(ratio_hist, fmt=options.marker_styles[i],
1546                   color=options.fill_colors[i],
1547                   ecolor=options.errorbar_colors[i])
1548     if options.ratio_logy:
1549         axes.set_yscale('log')
1550     stack.errorbar(yerr=True)
1551     axes.yaxis.set_major_locator(
1552         mpl.ticker.MaxNLocator(nbins=5, steps=[1, 2, 5, 10]))
1553     if options.ratio_max is not None: axes.set_ylim(ymax=options.ratio_max)
1554     if options.ratio_min is not None: axes.set_ylim(ymin=options.ratio_min)
1555     ratio_file = options.legend_entries[ratio_index]
1556     if options.efficiency:
1557         axes.set_ylabel(options.efficiency_label % locals())
1558     else:
1559         axes.set_ylabel(options.ratio_label % locals())
1560     axes.yaxis.tick_right()
1561     axes.yaxis.set_label_position('right')
1562     axes.yaxis.label.set_rotation(-90)
1563 
1564 def make_html_index(path, dirs, files, filetype, template, ncolumns, 
1565                     width, height):
1566     files = [x for x in files if x.endswith(filetype)]
1567     output_file = open(joined(path, 'index.html'), 'w')
1568     previous_dirs = [x for x in path.split('/') if x]
1569     ndirs = len(previous_dirs)
1570     back_nav = ['<a href="%s">%s</a>' %
1571                 ('../' * (ndirs - i - 1) + 'index.html', previous_dirs[i])
1572                 for i in range(ndirs)]
1573     back_nav = '/'.join(back_nav) + '/'
1574     forward_nav = ['<li><a href="%s/index.html">%s</a>/</li>' % (x, x)
1575                    for x in dirs]
1576     forward_nav = '\n    '.join(forward_nav)
1577     imgtemplate = '<a name="%(plot)s"><a href="index.html#%(plot)s">'
1578     if filetype.lower() == 'svg':
1579         imgtemplate += ('<object type="image/svg+xml" data="%(plot)s" '
1580                         'width=%(width)i height=%(height)i></object>')
1581     else:
1582         imgtemplate += '<img src="%(plot)s" height=%(height)i width=%(width)i>'
1583     imgtemplate += '</a></a>'
1584     plots = '\n'
1585     for plot in files:
1586         plots += imgtemplate % locals() + '\n'
1587     plots = re.sub('((\\n<a.*){%i})' % ncolumns, r'\1<br>', plots)
1588     output_file.write(template % locals())
1589     output_file.close()
1590     
1591 def parse_range(xedges, expression):
1592     #### Returns the indices of the low and high bins indicated in expression.
1593     closest = lambda l,y: l.index(min(l, key=lambda x:abs(x-y)))
1594     match = re.match(r'([^x]*)x([^x]*)', expression)
1595     lower, upper = float(match.group(1)), float(match.group(2))
1596     lowbin = closest(xedges, lower) + 1
1597     highbin = closest(xedges, upper)
1598     return lowbin, highbin
1599 
1600 def parse_legend_root(options):
1601     #### Return the corners to use for the legend based on options.
1602     legend_height = min(options.legend_entry_height * options.nhists + 0.02,
1603                         options.max_legend_height)
1604     if isinstance(options.legend_location, int):
1605         options.legend_location = options.legend_codes[options.legend_location]
1606     elif options.legend_location.lower() == 'none':
1607         options.legend_location = None
1608     if options.legend_location:
1609         if 'upper' in options.legend_location:
1610             top = options.legend_upper_bound
1611             bottom = options.legend_upper_bound - legend_height
1612         elif 'lower' in options.legend_location:
1613             bottom = options.legend_lower_bound
1614             top = options.legend_lower_bound + legend_height
1615         else:
1616             top = 0.5 + legend_height / 2
1617             bottom = 0.5 - legend_height / 2
1618         if 'left' in options.legend_location:
1619             left = options.legend_left_bound
1620             right = options.legend_left_bound + options.legend_width
1621         elif 'right' in options.legend_location:
1622             right = options.legend_right_bound
1623             left = options.legend_right_bound - options.legend_width
1624         else:
1625             right = 0.5 + options.legend_width / 2
1626             left = 0.5 - options.legend_width / 2
1627         return [left, bottom, right, top]
1628     return [0, 0, 0, 0]
1629 
1630 def load_matplotlib(ext):
1631     if 'mpl' not in globals().keys():
1632         global r2m, mpl, np, plt
1633         try:
1634             import matplotlib as mpl
1635         except ImportError:
1636             print("Unable to access matplotlib")
1637             sys.exit(1)
1638         import numpy as np
1639         mpldict = {'png' : 'AGG',
1640                    'pdf' : 'PDF',
1641                    'ps'  : 'PS',
1642                    'svg' : 'SVG'}
1643         if ext not in mpldict:
1644             raise ValueError("%s is not an output type recognized by "
1645                              "matplotlib" % ext)
1646         mpl.use(mpldict[ext])
1647         global Hist, Hist2D, HistStack
1648         import rootplot.root2matplotlib as r2m
1649         from rootplot.root2matplotlib import Hist, Hist2D, HistStack
1650         import matplotlib.pyplot as plt
1651 
1652 def samebase(targets):
1653     for target in targets:
1654         if os.path.basename(target) != os.path.basename(targets[0]):
1655             return False
1656     return True
1657 
1658 def allsame(targets):
1659     for target in targets:
1660         if target != targets[0]:
1661             return False
1662     return True
1663 
1664 def diffpart(targets):
1665     targets = [target.split('/') for target in targets]
1666     for i in range(len(targets[0])):
1667         for target in targets:
1668             if target[i] != targets[0][i]:
1669                 return ['/'.join(target[i:]) for target in targets]
1670 
1671 def get_plotpath(filenames, targets):
1672     if len(targets) >= 2:
1673         diffs = diffpart(targets)
1674         if not allsame([d.split('/')[-1] for d in diffs]):
1675             plotpath = 'plot'
1676             title = 'plot'
1677             legentries = diffs
1678         else:
1679             plotpath = '/'.join(diffs[0].split('/')[1:])
1680             title = diffs[0].split('/')[-1]
1681             legentries = [d.split('/')[0] for d in diffs]
1682     else:
1683         plotpath = targets[0]
1684         title = ''
1685         legentries = [f[:-5] for f in filenames]
1686     return plotpath, title, legentries
1687 
1688 def process_options(options):
1689     #### Refine options for this specific plot, based on plotname
1690     def comma_separator(obj, objtype, nhists):
1691         #### Split a comma-separated string into a list.
1692         if isinstance(obj, list):
1693             return obj
1694         if isinstance(obj, str) and ',' in obj:
1695             try:
1696                 return [objtype(x) for x in obj.split(',')]
1697             except TypeError:
1698                 return [eval(objtype)(x) for x in obj.split(',')]
1699         try:
1700             return [objtype(obj) for i in range(nhists)]
1701         except TypeError:
1702             return [eval(objtype)(obj) for i in range(nhists)]
1703     nhists = options.nhists
1704     if options.targets:
1705         plotpath, title, legentries = get_plotpath(options.filenames,
1706                                                    options.targets)
1707         options.plotpath = plotpath
1708         if not options.title:
1709             options.title = title
1710         if not options.legend_entries:
1711             options.legend_entries = legentries
1712         options.legend_entries = comma_separator(options.legend_entries,
1713                                                  str, nhists)
1714     options.scale = comma_separator(options.scale, float, nhists)
1715     if options.efficiency_split: options.ratio_max = 1.
1716     if nhists > 1: options.draw2D = None
1717     if use_mpl:
1718         plot_style = 'histfill'
1719         if options.bar: plot_style = 'bar'
1720         elif options.barh: plot_style = 'barh'
1721         elif options.barcluster: plot_style = 'barcluster'
1722         elif options.errorbar: plot_style = 'errorbar'
1723         elif options.hist: plot_style = 'hist'
1724         elif options.histfill: plot_style = 'histfill'
1725         if options.stack: plot_style = 'stack'
1726     if not options.markers and use_mpl:
1727         options.marker_styles = ['o' for i in range(nhists)]
1728     if not options.line_colors:
1729         options.line_colors = options.colors
1730     if not options.fill_colors:
1731         options.fill_colors = options.colors
1732     if not options.marker_colors:
1733         options.marker_colors = options.colors
1734     if use_mpl:
1735         if not options.line_styles:
1736             options.line_styles = ['solid' for i in range(nhists)]
1737         if not options.plot_styles:
1738             options.plot_styles = [plot_style for i in range(nhists)]
1739         if not options.errorbar_colors:
1740             options.errorbar_colors = [None for i in range(nhists)]
1741         if not options.alphas:
1742             options.alphas = [options.alpha for i in range(nhists)]
1743     else:
1744         if not options.line_styles:
1745             options.line_styles = [1 for i in range(nhists)]
1746         if not options.draw_commands:
1747             if options.stack:
1748                 options.draw_commands = ['stack ' + options.draw
1749                                          for i in range(nhists)]
1750             else:
1751                 options.draw_commands = [options.draw
1752                                          for i in range(nhists)]
1753     if not options.fill_styles:
1754         if use_mpl:
1755             options.fill_styles = [None for i in range(nhists)]
1756         else:
1757             if options.fill:
1758                 options.fill_styles = [1001 for i in range(nhists)]
1759             else:
1760                 options.fill_styles = [0 for i in range(nhists)]
1761     if not options.marker_sizes:
1762         if options.markers:
1763             if use_mpl: size = mpl.rcParams['lines.markersize']
1764             else: size = ROOT.gStyle.GetMarkerSize()
1765         else:
1766             size = 0
1767         options.marker_sizes = [size for i in range(nhists)]
1768     if options.data:
1769         i = options.data - 1
1770         options.line_styles[i] = options.data_linestyle
1771         options.line_colors[i] = options.data_color
1772         options.fill_colors[i] = options.data_color
1773         options.marker_colors[i] = options.data_color
1774         if use_mpl:
1775             options.plot_styles[i] = 'errorbar'
1776         else:
1777             options.fill_styles[i] = 0
1778             options.draw_commands[i] = 'e'
1779         options.marker_styles[i] = options.data_marker
1780         if not options.marker_sizes[i]:
1781             if use_mpl:
1782                 options.marker_sizes[i] = mpl.rcParams['lines.markersize']
1783             else:
1784                 options.marker_sizes[i] = ROOT.gStyle.GetMarkerSize()
1785         if nhists == 2 and options.mc_color:
1786             options.fill_colors[(i+1)%2] = options.mc_color
1787     for opt in [x for x in options.keys() if 'colors' in x]:
1788         try:
1789             colors = options[opt]
1790             options[opt] = [parse_color(x, not use_mpl) for x in colors]
1791         except AttributeError:
1792             pass
1793     if options.targets:
1794         #### Apply extra options based on hist name
1795         plotname = os.path.basename(options.plotpath)
1796         for option, value, regexes in options.options_by_histname:
1797             for regex in regexes:
1798                 if re.match(regex, plotname):
1799                     setattr(options, option, value)
1800         #### Final setup
1801         if options.logy:
1802             if options.ymin <= 0:
1803                 options.ymin = None
1804             options.top_padding_factor = options.top_padding_factor_log
1805 
1806 def cartesian_product(*args, **kwds):
1807     # product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy
1808     # product(range(2), repeat=3) --> 000 001 010 011 100 101 110 111
1809     pools = map(tuple, args) * kwds.get('repeat', 1)
1810     result = [[]]
1811     for pool in pools:
1812         result = [x+[y] for x in result for y in pool]
1813     for prod in result:
1814         yield tuple(prod)
1815 
1816 def parse_arguments(argv, scope='global'):
1817     if use_mpl:
1818         import matplotlib as mpl
1819         figsize = 'x'.join([str(x) for x in mpl.rcParams['figure.figsize']])
1820     else:
1821         figsize = 'x'.join([str(ROOT.gStyle.GetCanvasDefW()),
1822                             str(ROOT.gStyle.GetCanvasDefH())])
1823     def opt(**kwargs):
1824         return kwargs
1825     def addopt(group, *args, **kwargs):
1826         if use_mpl and 'mpl' in kwargs:
1827             opts = kwargs['mpl']
1828             kwargs = dict(kwargs, **opts)
1829         if not use_mpl and 'root' in kwargs:
1830             opts = kwargs['root']
1831             kwargs = dict(kwargs, **opts)
1832         if 'opts' in locals():
1833             del kwargs['mpl']
1834             del kwargs['root']
1835         if 'metadefault' in kwargs:
1836             val = kwargs.pop('metadefault')
1837             kwargs['default'] = val
1838             kwargs['metavar'] = val
1839         if 'metavar' in kwargs and ' ' in str(kwargs['metavar']):
1840             kwargs['metavar']="'%s'" % kwargs['metavar']
1841         group.add_option(*args, **kwargs)
1842 
1843     help_formatter = optparse.IndentedHelpFormatter()
1844     parser = optparse.OptionParser(usage=usage, formatter=help_formatter,
1845                                    version='%s %s' % ('%prog', __version__))
1846     Group = optparse.OptionGroup
1847     g_control = Group(parser, "Control the overall behavior of rootplot")
1848     g_output  = Group(parser, "Control the output format")
1849     g_hist    = Group(parser, "Manipulate your histograms")
1850     g_style   = Group(parser, "Fine-tune your style")
1851     parser.add_option_group(g_control)
1852     parser.add_option_group(g_output)
1853     parser.add_option_group(g_hist)
1854     parser.add_option_group(g_style)
1855     #### Process control options
1856     addopt(g_control, '--config', action='store_true',
1857            help="do nothing but write a template configuration file "
1858            "called rootplot_config.py")
1859     addopt(g_control, '--debug', action='store_true',
1860            help="turn off error-catching to more easily identify errors")
1861     addopt(g_control, '--path', metavar="'.*'", default='.*',
1862            help="only process plot(s) matching this regular expression")
1863     addopt(g_control, '--processors', type='int',
1864            metadefault=find_num_processors(),
1865            help="the number of parallel plotting processes to create")
1866     #### Output options
1867     addopt(g_output, '-e', '--ext', metadefault='png',
1868            help="choose an output extension")
1869     addopt(g_output, '--merge', action='store_true', default=False,
1870            help="sets --ext=pdf and creates a merged file "
1871            "containing all plots")
1872     addopt(g_output, '--noclean', action='store_true', default=False,
1873            help="skips destroying the output directory before drawing")
1874     addopt(g_output, '--output', metadefault='plots', 
1875            help="name of output directory")
1876     addopt(g_output, '--numbering', action='store_true', default=False,
1877            help="print page numbers on images and prepend them to file names; "
1878            "numbering will respect the order of objects in the ROOT file")
1879     addopt(g_output, '--size', metadefault=figsize,
1880            root=opt(help="set the canvas size to 'width x height' in pixels"),
1881            mpl=opt(help="set the canvas size to 'width x height' in inches"))
1882     if use_mpl:
1883         addopt(g_output, '--dpi', type=float,
1884                metadefault=mpl.rcParams['figure.dpi'],
1885                help="set the resolution of the output files")
1886         addopt(g_output, '--transparent', action="store_true", default=False,
1887             help="use a transparent background")
1888     #### Histogram manipulation options
1889     addopt(g_hist, '-n', '--area-normalize', action='store_true',
1890            default=False, help="area-normalize the histograms")
1891     addopt(g_hist, '--scale', metavar='VAL', default=1.,
1892            help="normalize all histograms by VAL, or by individual values "
1893            "if VAL is a comma-separated list")
1894     addopt(g_hist, '--normalize', metavar='NUM', type='int', default=0,
1895            help="normalize to the NUMth target (starting with 1)")
1896     addopt(g_hist, '--norm-range', metavar='LOWxHIGH',
1897            help="only use the specified data range in determining "
1898            "the normalization")
1899     addopt(g_hist, '--rebin', metavar="N", type=int,
1900            help="group together bins in sets of N")
1901     addopt(g_hist, '--ratio', type='int', default=0, metavar='NUM',
1902            help="divide histograms by the NUMth target (starting from 1)")
1903     addopt(g_hist, '--ratio-split', type='int', default=0, metavar='NUM',
1904            help="same as --ratio, but split the figure in two, displaying "
1905            "the normal figure on top and the ratio on the bottom")
1906     addopt(g_hist, '--efficiency', type='int', default=0, metavar='NUM',
1907            help="same as --ratio, but with errors computed by the Wilson "
1908            "score interval")
1909     addopt(g_hist, '--efficiency-split', type='int', default=0, metavar='NUM',
1910            help="same as --ratio-split, but with errors computed by the Wilson "
1911            "score interval")
1912     #### Style options
1913     if not use_mpl:
1914         addopt(g_style, '--draw', metadefault='p H',
1915                help="argument to pass to ROOT's Draw command; try 'e' for "
1916                "errorbars, or 'hist' to make sure no errorbars appear")
1917     addopt(g_style, '--draw2D',
1918            root=opt(metadefault='box',
1919                     help="argument to pass to TH2::Draw (ignored if multiple "
1920                     "targets specified); set "
1921                     'to "" to turn off 2D drawing'),
1922            mpl=opt(metadefault='box', 
1923                    help="command to use for plotting 2D hists; (ignored if "
1924                    "multiple targets specified) "
1925                    "choose from 'contour', 'col', 'colz', and 'box'")
1926            )
1927     if not use_mpl:
1928         addopt(g_style, '-f', '--fill', action='store_true', default=False,
1929                           help="Histograms will have a color fill")
1930     if use_mpl:
1931         addopt(g_style, '--errorbar', action="store_true", default=False,
1932                help="output a matplotlib errorbar graph")
1933         addopt(g_style, '--barcluster', action="store_true", default=False,
1934                help="output a clustered bar graph")
1935         addopt(g_style, '--barh', action="store_true", default=False,
1936                help="output a horizontal clustered bar graph")
1937         addopt(g_style, '--bar', action="store_true", default=False,
1938                help="output a bar graph with all histograms overlaid")
1939         addopt(g_style, '--hist', action="store_true", default=False,
1940             help="output a matplotlib hist graph (no fill)")
1941         addopt(g_style, '--histfill', action="store_true", default=False,
1942             help="output a matplotlib hist graph with solid fill")
1943     addopt(g_style, '--stack', action="store_true", default=False,
1944            help="stack histograms")
1945     addopt(g_style, '-m', '--markers', action='store_true', default=False,
1946            help="add markers to histograms")
1947     addopt(g_style, '--xerr', action="store_true", default=False,
1948            help="show width of bins")
1949     addopt(g_style, '--data', type='int', default=0, metavar='NUM',
1950            root=opt(help="treat the NUMth target (starting from 1) "
1951                     "specially, drawing it as black datapoints; to achieve "
1952                     "a classic data vs. MC plot, try this along with "
1953                     "--stack and --fill"),
1954            mpl=opt(help="treat the NUMth target (starting from 1) "
1955                    "specially, drawing it as black datapoints; to achieve "
1956                    "a classic data vs. MC plot, try this along with --stack"))
1957     addopt(g_style, '--xmax', type='float', default=None,
1958            help="set the maximum value of the x-axis")
1959     addopt(g_style, '--xmin', type='float', default=None,
1960            help="set the minimum value of the x-axis")
1961     addopt(g_style, '--ymax', type='float', default=None,
1962            help="set the maximum value of the y-axis")
1963     addopt(g_style, '--ymin', type='float', default=None,
1964            help="set the minimum value of the y-axis")
1965     addopt(g_style, '--legend-location', metavar='upper right', default=1,
1966            help="Place legend in LOC, according to matplotlib "
1967            "location codes; try 'lower left' or 'center'; "
1968            "to turn off, set to 'None'")
1969     addopt(g_style, '--legend-entries', default=None, metavar="''",
1970            help="A comma-separated string giving the labels to "
1971            "appear in the legend")
1972     if use_mpl:
1973         addopt(g_style, '--legend-ncols', default=None, metavar=1,
1974                help="Number of columns to use in the legend")
1975     addopt(g_style, '--title', default=None,
1976                       help="replace the plot titles, or append to them by "
1977                       "preceeding with a '+'")
1978     addopt(g_style, '--xlabel', default=None,
1979                       help="replace the x-axis labels, or append to them by "
1980                       "preceeding with a '+'")
1981     addopt(g_style, '--ylabel', default=None,
1982                       help="replace the y-axis labels, or append to them by "
1983                       "preceeding with a '+'")
1984     addopt(g_style, '--grid', action='store_true', default=False,
1985                       help="toggle the grid on or off for both axes")
1986     addopt(g_style, '--gridx', action='store_true', default=False,
1987                       help="toggle the grid on or off for the x axis")
1988     addopt(g_style, '--gridy', action='store_true', default=False,
1989                       help="toggle the grid on or off for the y axis")
1990     if use_mpl:
1991         addopt(g_style, '--cmap',
1992                help="matplotlib colormap to use for 2D plots")
1993         addopt(g_style, '--barwidth', metadefault=1.0, type=float,
1994                help="fraction of the bin width for bars to fill")
1995         addopt(g_style, '--alpha', type='float', metadefault=0.5,
1996                help="set the opacity of fills")
1997     addopt(g_style, '--logx', action='store_true', default=False,
1998            help="force log scale for x axis")
1999     addopt(g_style, '--logy', action='store_true', default=False,
2000            help="force log scale for y axis")
2001     addopt(g_style, '--overflow', action='store_true', default=False,
2002            help="display overflow content in the highest bin")
2003     addopt(g_style, '--underflow', action='store_true', default=False,
2004            help="display underflow content in the lowest bin")
2005     #### Do immediate processing of arguments
2006     options, arguments = parser.parse_args(list(argv))
2007     options = Options(options, arguments, scope=scope)
2008     options.replace = [] # must have default in case not using mpl
2009     if options.processors == 1 or options.ext == 'C':
2010         global use_multiprocessing
2011         use_multiprocessing = False
2012     if options.merge: options.ext = 'pdf'
2013     return options