text: # This is the fix for a well-known bug in Matplotlib's
  # axes.Axes.hist() method for stepfilled histograms with a logarithmic
  # axis. The actual fix (see below) is quite small.
  
  # This is used by the create_public_lumi_plots.py script.
  
  from builtins import range
  import numpy as np
  import itertools
  import matplotlib.cbook as cbook
  iterable = cbook.iterable
  is_string_like = cbook.is_string_like
  is_sequence_of_strings = cbook.is_sequence_of_strings
  
  def hist(self, x, bins=10, range=None, normed=False, weights=None,
           cumulative=False, bottom=None, histtype='bar', align='mid',
           orientation='vertical', rwidth=None, log=False,
           color=None, label=None,
           **kwargs):
      """
      call signature::
  
        hist(x, bins=10, range=None, normed=False, cumulative=False,
             bottom=None, histtype='bar', align='mid',
             orientation='vertical', rwidth=None, log=False, **kwargs)
  
      Compute and draw the histogram of *x*. The return value is a
      tuple (*n*, *bins*, *patches*) or ([*n0*, *n1*, ...], *bins*,
      [*patches0*, *patches1*,...]) if the input contains multiple
      data.
  
      Multiple data can be provided via *x* as a list of datasets
      of potentially different length ([*x0*, *x1*, ...]), or as
      a 2-D ndarray in which each column is a dataset.  Note that
      the ndarray form is transposed relative to the list form.
  
      Masked arrays are not supported at present.
  
      Keyword arguments:
  
        *bins*:
          Either an integer number of bins or a sequence giving the
          bins.  If *bins* is an integer, *bins* + 1 bin edges
          will be returned, consistent with :func:`numpy.histogram`
          for numpy version >= 1.3, and with the *new* = True argument
          in earlier versions.
          Unequally spaced bins are supported if *bins* is a sequence.
  
        *range*:
          The lower and upper range of the bins. Lower and upper outliers
          are ignored. If not provided, *range* is (x.min(), x.max()).
          Range has no effect if *bins* is a sequence.
  
          If *bins* is a sequence or *range* is specified, autoscaling
          is based on the specified bin range instead of the
          range of x.
  
        *normed*:
          If *True*, the first element of the return tuple will
          be the counts normalized to form a probability density, i.e.,
          ``n/(len(x)*dbin)``.  In a probability density, the integral of
          the histogram should be 1; you can verify that with a
          trapezoidal integration of the probability density function::
  
            pdf, bins, patches = ax.hist(...)
            print np.sum(pdf * np.diff(bins))
  
          .. Note:: Until numpy release 1.5, the underlying numpy
                    histogram function was incorrect with *normed*=*True*
                    if bin sizes were unequal.  MPL inherited that
                    error.  It is now corrected within MPL when using
                    earlier numpy versions
  
        *weights*
          An array of weights, of the same shape as *x*.  Each value in
          *x* only contributes its associated weight towards the bin
          count (instead of 1).  If *normed* is True, the weights are
          normalized, so that the integral of the density over the range
          remains 1.
  
        *cumulative*:
          If *True*, then a histogram is computed where each bin
          gives the counts in that bin plus all bins for smaller values.
          The last bin gives the total number of datapoints.  If *normed*
          is also *True* then the histogram is normalized such that the
          last bin equals 1. If *cumulative* evaluates to less than 0
          (e.g. -1), the direction of accumulation is reversed.  In this
          case, if *normed* is also *True*, then the histogram is normalized
          such that the first bin equals 1.
  
        *histtype*: [ 'bar' | 'barstacked' | 'step' | 'stepfilled' ]
          The type of histogram to draw.
  
            - 'bar' is a traditional bar-type histogram.  If multiple data
              are given the bars are aranged side by side.
  
            - 'barstacked' is a bar-type histogram where multiple
              data are stacked on top of each other.
  
            - 'step' generates a lineplot that is by default
              unfilled.
  
            - 'stepfilled' generates a lineplot that is by default
              filled.
  
        *align*: ['left' | 'mid' | 'right' ]
          Controls how the histogram is plotted.
  
            - 'left': bars are centered on the left bin edges.
  
            - 'mid': bars are centered between the bin edges.
  
            - 'right': bars are centered on the right bin edges.
  
        *orientation*: [ 'horizontal' | 'vertical' ]
          If 'horizontal', :func:`~matplotlib.pyplot.barh` will be
          used for bar-type histograms and the *bottom* kwarg will be
          the left edges.
  
        *rwidth*:
          The relative width of the bars as a fraction of the bin
          width.  If *None*, automatically compute the width. Ignored
          if *histtype* = 'step' or 'stepfilled'.
  
        *log*:
          If *True*, the histogram axis will be set to a log scale.
          If *log* is *True* and *x* is a 1D array, empty bins will
          be filtered out and only the non-empty (*n*, *bins*,
          *patches*) will be returned.
  
        *color*:
          Color spec or sequence of color specs, one per
          dataset.  Default (*None*) uses the standard line
          color sequence.
  
        *label*:
          String, or sequence of strings to match multiple
          datasets.  Bar charts yield multiple patches per
          dataset, but only the first gets the label, so
          that the legend command will work as expected::
  
              ax.hist(10+2*np.random.randn(1000), label='men')
              ax.hist(12+3*np.random.randn(1000), label='women', alpha=0.5)
              ax.legend()
  
      kwargs are used to update the properties of the
      :class:`~matplotlib.patches.Patch` instances returned by *hist*:
  
      %(Patch)s
  
      **Example:**
  
      .. plot:: mpl_examples/pylab_examples/histogram_demo.py
      """
      if not self._hold: self.cla()
  
      # NOTE: the range keyword overwrites the built-in func range !!!
      #       needs to be fixed in numpy                           !!!
  
      # Validate string inputs here so we don't have to clutter
      # subsequent code.
      if histtype not in ['bar', 'barstacked', 'step', 'stepfilled']:
          raise ValueError("histtype %s is not recognized" % histtype)
  
      if align not in ['left', 'mid', 'right']:
          raise ValueError("align kwarg %s is not recognized" % align)
  
      if orientation not in [ 'horizontal', 'vertical']:
          raise ValueError(
              "orientation kwarg %s is not recognized" % orientation)
  
  
      if kwargs.get('width') is not None:
          raise DeprecationWarning(
              'hist now uses the rwidth to give relative width '
              'and not absolute width')
  
      # Massage 'x' for processing.
      # NOTE: Be sure any changes here is also done below to 'weights'
      if isinstance(x, np.ndarray) or not iterable(x[0]):
          # TODO: support masked arrays;
          x = np.asarray(x)
          if x.ndim == 2:
              x = x.T # 2-D input with columns as datasets; switch to rows
          elif x.ndim == 1:
              x = x.reshape(1, x.shape[0])  # new view, single row
          else:
              raise ValueError("x must be 1D or 2D")
          if x.shape[1] < x.shape[0]:
              warnings.warn('2D hist input should be nsamples x nvariables;\n '
                  'this looks transposed (shape is %d x %d)' % x.shape[::-1])
      else:
          # multiple hist with data of different length
          x = [np.array(xi) for xi in x]
  
      nx = len(x) # number of datasets
  
      if color is None:
          color = [next(self._get_lines.color_cycle)
                                          for i in range(nx)]
      else:
          color = mcolors.colorConverter.to_rgba_array(color)
          if len(color) != nx:
              raise ValueError("color kwarg must have one color per dataset")
  
      # We need to do to 'weights' what was done to 'x'
      if weights is not None:
          if isinstance(weights, np.ndarray) or not iterable(weights[0]) :
              w = np.array(weights)
              if w.ndim == 2:
                  w = w.T
              elif w.ndim == 1:
                  w.shape = (1, w.shape[0])
              else:
                  raise ValueError("weights must be 1D or 2D")
          else:
              w = [np.array(wi) for wi in weights]
  
          if len(w) != nx:
              raise ValueError('weights should have the same shape as x')
          for i in range(nx):
              if len(w[i]) != len(x[i]):
                  raise ValueError(
                      'weights should have the same shape as x')
      else:
          w = [None]*nx
  
  
      # Save autoscale state for later restoration; turn autoscaling
      # off so we can do it all a single time at the end, instead
      # of having it done by bar or fill and then having to be redone.
      _saved_autoscalex = self.get_autoscalex_on()
      _saved_autoscaley = self.get_autoscaley_on()
      self.set_autoscalex_on(False)
      self.set_autoscaley_on(False)
  
      # Save the datalimits for the same reason:
      _saved_bounds = self.dataLim.bounds
  
      # Check whether bins or range are given explicitly. In that
      # case use those values for autoscaling.
      binsgiven = (cbook.iterable(bins) or range != None)
  
      # If bins are not specified either explicitly or via range,
      # we need to figure out the range required for all datasets,
      # and supply that to np.histogram.
      if not binsgiven:
          xmin = np.inf
          xmax = -np.inf
          for xi in x:
              xmin = min(xmin, xi.min())
              xmax = max(xmax, xi.max())
          range = (xmin, xmax)
  
      #hist_kwargs = dict(range=range, normed=bool(normed))
      # We will handle the normed kwarg within mpl until we
      # get to the point of requiring numpy >= 1.5.
      hist_kwargs = dict(range=range)
      if np.__version__ < "1.3": # version 1.1 and 1.2
          hist_kwargs['new'] = True
  
      n = []
      for i in range(nx):
          # this will automatically overwrite bins,
          # so that each histogram uses the same bins
          m, bins = np.histogram(x[i], bins, weights=w[i], **hist_kwargs)
          if normed:
              db = np.diff(bins)
              m = (m.astype(float) / db) / m.sum()
          n.append(m)
      if normed and db.std() > 0.01 * db.mean():
          warnings.warn("""
          This release fixes a normalization bug in the NumPy histogram
          function prior to version 1.5, occuring with non-uniform
          bin widths. The returned and plotted value is now a density:
              n / (N * bin width),
          where n is the bin count and N the total number of points.
          """)
  
  
  
      if cumulative:
          slc = slice(None)
          if cbook.is_numlike(cumulative) and cumulative < 0:
              slc = slice(None,None,-1)
  
          if normed:
              n = [(m * np.diff(bins))[slc].cumsum()[slc] for m in n]
          else:
              n = [m[slc].cumsum()[slc] for m in n]
  
      patches = []
  
      if histtype.startswith('bar'):
          totwidth = np.diff(bins)
  
          if rwidth is not None:
              dr = min(1.0, max(0.0, rwidth))
          elif len(n)>1:
              dr = 0.8
          else:
              dr = 1.0
  
          if histtype=='bar':
              width = dr*totwidth/nx
              dw = width
  
              if nx > 1:
                  boffset = -0.5*dr*totwidth*(1.0-1.0/nx)
              else:
                  boffset = 0.0
              stacked = False
          elif histtype=='barstacked':
              width = dr*totwidth
              boffset, dw = 0.0, 0.0
              stacked = True
  
          if align == 'mid' or align == 'edge':
              boffset += 0.5*totwidth
          elif align == 'right':
              boffset += totwidth
  
          if orientation == 'horizontal':
              _barfunc = self.barh
          else:  # orientation == 'vertical'
              _barfunc = self.bar
  
          for m, c in zip(n, color):
              patch = _barfunc(bins[:-1]+boffset, m, width, bottom,
                                align='center', log=log,
                                color=c)
              patches.append(patch)
              if stacked:
                  if bottom is None:
                      bottom = 0.0
                  bottom += m
              boffset += dw
  
      elif histtype.startswith('step'):
          x = np.zeros( 2*len(bins), np.float )
          y = np.zeros( 2*len(bins), np.float )
  
          x[0::2], x[1::2] = bins, bins
  
          # FIX FIX FIX
          # This is the only real change.
          # minimum = min(bins)
          if log is True:
              minimum = 1.0
          elif log:
              minimum = float(log)
          else:
              minimum = 0.0
          # FIX FIX FIX end
  
          if align == 'left' or align == 'center':
              x -= 0.5*(bins[1]-bins[0])
          elif align == 'right':
              x += 0.5*(bins[1]-bins[0])
  
          if log:
              y[0],y[-1] = minimum, minimum
              if orientation == 'horizontal':
                  self.set_xscale('log')
              else:  # orientation == 'vertical'
                  self.set_yscale('log')
  
          fill = (histtype == 'stepfilled')
  
          for m, c in zip(n, color):
              y[1:-1:2], y[2::2] = m, m
              if log:
                  y[y<minimum]=minimum
              if orientation == 'horizontal':
                  x,y = y,x
  
              if fill:
                  patches.append( self.fill(x, y,
                      closed=False, facecolor=c) )
              else:
                  patches.append( self.fill(x, y,
                      closed=False, edgecolor=c, fill=False) )
  
          # adopted from adjust_x/ylim part of the bar method
          if orientation == 'horizontal':
              xmin0 = max(_saved_bounds[0]*0.9, minimum)
              xmax = self.dataLim.intervalx[1]
              for m in n:
                  xmin = np.amin(m[m!=0]) # filter out the 0 height bins
              xmin = max(xmin*0.9, minimum)
              xmin = min(xmin0, xmin)
              self.dataLim.intervalx = (xmin, xmax)
          elif orientation == 'vertical':
              ymin0 = max(_saved_bounds[1]*0.9, minimum)
              ymax = self.dataLim.intervaly[1]
              for m in n:
                  ymin = np.amin(m[m!=0]) # filter out the 0 height bins
              ymin = max(ymin*0.9, minimum)
              ymin = min(ymin0, ymin)
              self.dataLim.intervaly = (ymin, ymax)
  
      if label is None:
          labels = ['_nolegend_']
      elif is_string_like(label):
          labels = [label]
      elif is_sequence_of_strings(label):
          labels = list(label)
      else:
          raise ValueError(
              'invalid label: must be string or sequence of strings')
      if len(labels) < nx:
          labels += ['_nolegend_'] * (nx - len(labels))
  
      for (patch, lbl) in zip(patches, labels):
          for p in patch:
              p.update(kwargs)
              p.set_label(lbl)
              lbl = '_nolegend_'
  
      if binsgiven:
          if orientation == 'vertical':
              self.update_datalim([(bins[0],0), (bins[-1],0)], updatey=False)
          else:
              self.update_datalim([(0,bins[0]), (0,bins[-1])], updatex=False)
  
      self.set_autoscalex_on(_saved_autoscalex)
      self.set_autoscaley_on(_saved_autoscaley)
      self.autoscale_view()
  
      if nx == 1:
          return n[0], bins, cbook.silent_list('Patch', patches[0])
      else:
          return n, bins, cbook.silent_list('Lists of Patches', patches)
  