;+
; NAME:
;       HISTOPLOT
;
; PURPOSE:
;
;       This program is used to draw a histogram in IDL direct graphics.
;
; AUTHOR:
;
;       FANNING SOFTWARE CONSULTING
;       David Fanning, Ph.D.
;       1645 Sheely Drive
;       Fort Collins, CO 80526 USA
;       Phone: 970-221-0438
;       E-mail: davidf@dfanning.com
;       Coyote's Guide to IDL Programming: http://www.dfanning.com
;
; CATEGORY:
;
;       Graphics
;
; CALLING SEQUENCE:
;
;      HistoPlot, dataToHistogram
;
; ARGUMENTS:
;
;       dataToHistogram:  The data from which the histogram is created.
;
; INPUT KEYWORDS:
;
;       AXISCOLORNAME:    The name of the axis color. Default: "Navy". (All color names
;                         derived from FSC_COLOR.)
;
;       BACKCOLORNAME:    The name of the background color. Default: "White".
;
;       BINSIZE:          The binsize of the histogram. By default the data range, divided
;                         by NBINS. If the data is an integer, the binsize is rounded to the
;                         nearest integer. If the data is BYTE type, the binsize is 1. The
;                         binsize, if supplied, must have the same data type as the data.
;
;       DATACOLORNAME:    The name of the data color for drawing the histogram outlines.
;                         Default: "Indian Red".
;
;       FILE:             The name of a color name file to use with FSC_COLOR.
;
;       FILLPOLYGON:      Set this keyword to fill the histogram polygons. If this keyword
;                         is set, the following keyword can also be used.
;
;                         POLYCOLOR:    The name, or vector of names, of polygon colors.
;                                       If a vector, the names are cycled though, as needed.
;                                       Defaults to DATACOLORNAME.
;
;       L64:              If set, the return value of HISTOGRAM are 64-bit integers, rather than
;                         the default 32-bit integers.
;
;       LINE_FILL:        If set, the polygons are filled with lines instead of solid color. If
;                         this keyword is set, the following keywords can also be used.
;
;                         ORIENTATION:  The orientation of the lines in line-filled polygons in degrees.
;                         PATTERN:      Set to rectangular array of pixel giving fill pattern.
;                         POLYCOLOR:    The name, or vector of names, of line colors.
;                                       If a vector, the names are cycled though, as needed.
;                         SPACING:      The spacing, in centimeters, between parallel lines.
;
;       MAXINPUT:         The maximum value to use in calculating input histogram. Equivalent to MAX keyword
;                         in HISTOGRAM documentation.
;
;       MAX_VALUE:        The maximum Y data value to represent on graphics plot. Default: Max(histdataToPlot) * 1.05
;
;       MININPUT:         The minimum value to use in calculating input histogram. Equivalent to MIN keyword
;                         in HISTOGRAM documentation.
;
;       MIN_VALUE:        The minimum Y data value to represent on graphics plot. Default: 0.
;
;       NAN:              If set, ignore NAN values in calculating and plotting histogram.
;
;       NBINS:            The number of output bins in the histogram. Meaning is slightly different from
;                         meaning in the HISTOGRAM command. Used only to calculate BINSIZE when BINSIZE is
;                         not specified.
;
;       NOLINES:          Set this keyword if you prefer NOT to draw lines between individual histogram boxes.
;
;       OPLOT:            Set this keyword if you want to overplot data on already established axes.
;
;       OMAX:             The
;
;       The user may also enter any other keywords suitable for the PLOT and POLYFILL commands in IDL.
;
; OUTPUT KEYWORDS:
;
;       HISTDATA:         The output value of the internal HISTOGRAM command.
;
;       LOCATIONS:        Starting locations of each bin. (See HISTOGRAM documentation.)
;
;       OMAX:             The maximum output value used to construct the histogram. (See HISTOGRAM documentation.)
;
;       OMIN:             The minimum output value used to construct the histogram. (See HISTOGRAM documentation.)
;
;       REVERSE_INDICES:  List of reverse indices. (See HISTOGRAM documentation.)
;
; EXAMPLES:
;
;      IDL> Histoplot, Dist(256)
;      IDL> Histoplot, Fix(RandomU(seed, 200)*20), POLYCOLOR=['charcoal', 'steel blue'], /FILLPOLYGON
;      IDL> Histoplot, Fix(RandomU(seed, 200)*20), POLYCOLOR=['navy', 'forest green'], /LINE_FILL, ORIENTATION=[45,-45]
;
; REQUIRES:
;
;     Requires ERROR_MESSAGE and FSC_COLOR from the Coyote Library:
;
;        http://www.dfanning.com/programs/error_message.pro
;        http://www.dfanning.com/programs/fsc_color.pro
;
; MODIFICATION HISTORY:
;
;       Written by:  David W. Fanning, 14 November 2007.
;       Modified to work with !P.MULTI. 20 Nov 2007. DWF.
;       Slight problem with extra space at the right end of the plot resolved. 20 Nov 2007. DWF.
;       Added FILE and NOLINES keywords. 24 Nov 2007. DWF.
;       Added additional HISTOGRAM access via keywords. 24 Nov 2007. DWF.
;       Fixed a small problem with FILLPOLY keyword. 26 Nov 2007. DWF.
;-
;###########################################################################
;
; LICENSE
;
; This software is OSI Certified Open Source Software.
; OSI Certified is a certification mark of the Open Source Initiative.
;
; Copyright © 2007 Fanning Software Consulting
;
; This software is provided "as-is", without any express or
; implied warranty. In no event will the authors be held liable
; for any damages arising from the use of this software.
;
; Permission is granted to anyone to use this software for any
; purpose, including commercial applications, and to alter it and
; redistribute it freely, subject to the following restrictions:
;
; 1. The origin of this software must not be misrepresented; you must
;    not claim you wrote the original software. If you use this software
;    in a product, an acknowledgment in the product documentation
;    would be appreciated, but is not required.
;
; 2. Altered source versions must be plainly marked as such, and must
;    not be misrepresented as being the original software.
;
; 3. This notice may not be removed or altered from any source distribution.
;
; For more information on Open Source Software, visit the Open Source
; web site: http://www.opensource.org.
;
;###########################################################################
PRO HistoPlot , $                   ; The program name.
   dataToHistogram, $               ; The data to draw a histogram of.
   AXISCOLORNAME=axisColorName, $   ; The axis color.
   BACKCOLORNAME=backcolorName, $   ; The background color.
   DATACOLORNAME=datacolorName, $   ; The data color.
   _REF_EXTRA=extra, $              ; For passing extra keywords.
   FILE=file, $                     ; For specifying a color name file.
   MAX_VALUE=max_value, $           ; The maximum value to plot.
   MIN_VALUE=min_value, $           ; The minimum value to plot.
   NOLINES=nolines, $               ; No lines between histogram boxes.
   OPLOT=overplot, $                ; Set if you want overplotting.
   ;
   ; POLYFILL KEYWORDS
   ;
   FILLPOLYGON=fillpolygon, $       ; Set if you want filled polygons
   LINE_FILL=line_fill, $           ; Set if you want line-filled polygons.
   ORIENTATION=orientation, $       ; The orientation of the lines.
   PATTERN=pattern, $               ; The fill pattern.
   POLYCOLOR=polycolor, $           ; The name of the polygon draw/fill color.
   SPACING=spacing, $               ; The spacing of filled lines.
   ;
   ; HISTOGRAM OUTPUT KEYWORDS
   ;
   HISTDATA=histdata, $
   LOCATIONS=locations, $
   OMAX=omax, $
   OMIN=omin, $
   REVERSE_INDICES=ri, $
   ;
   ; HISTOGRAM INPUT KEYWORDS
   ;
   BINSIZE=binsize, $               ; The histogram bin size.
   L64=l64, $                       ; Input for HISTOGRAM.
   MAXINPUT=maxinput, $             ; The maximum value to HISTOGRAM.
   MININPUT=mininput, $             ; The minimum value to HISTOGRAM.
   NAN=nan, $                       ; Check for NAN.
   NBINS=nbins                      ; The number of bins to display.


   ; Catch any error in the HistoPlot program.
   Catch, theError
   IF theError NE 0 THEN BEGIN
      Catch, /Cancel
      ok = Error_Message(!Error_State.Msg + '. Returning...')
      RETURN
   ENDIF

   ; Check for positional parameter.
   IF N_Elements(dataToHistogram) EQ 0 THEN Message, 'Must pass data to histogram.'

   IF N_Elements(nbins) EQ 0 THEN theseBins = 128.0 ELSE theseBins = DOUBLE(nbins)

   ; Check for histogram keywords.
   IF N_Elements(binsize) EQ 0 THEN BEGIN
      range = Max(dataToHistogram) - Min(dataToHistogram)
      CASE Size(DataToHistogram, /TNAME) OF
         'BYTE': binsize = 1
         'FLOAT': binsize = range / theseBins
         'DOUBLE': binsize = range / theseBins
         ELSE: binsize = Round(range / theseBins) > 1
      ENDCASE
   ENDIF ELSE BEGIN
       IF Size(binsize, /TYPE) NE Size(dataToHistogram, /TYPE) THEN $
         Message, 'The BINSIZE type is not the same as DATA type.'
   ENDELSE

   ; Check for keywords.
   IF N_Elements(dataColorName) EQ 0 THEN dataColorName = "Indian Red"
   IF N_Elements(axisColorName) EQ 0 THEN axisColorName = "Navy"
   IF N_Elements(backColorName) EQ 0 THEN backColorName = "White"
   IF N_Elements(polycolor) EQ 0 THEN polycolor = dataColorName
   line_fill = Keyword_Set(line_fill)
   IF line_fill THEN fillpolygon = 1
   IF Keyword_Set(fillpolygon) THEN BEGIN
      fillpolygon = Keyword_Set(fillpolygon)
      IF N_Elements(orientation) EQ 0 THEN orientation = 0
      IF N_Elements(spacing) EQ 0 THEN spacing = 0
   ENDIF
   IF N_Elements(min_value) EQ 0 THEN min_value = 0
   IF N_Elements(mininput) EQ 0 THEN mininput = Min(dataToHistogram)
   IF N_Elements(maxinput) EQ 0 THEN maxinput = Max(dataToHistogram)

   ; Load plot colors.
   TVLCT, r, g, b, /GET
   axisColor = FSC_Color(axisColorName, FILE=file)
   dataColor = FSC_Color(datacolorName, FILE=file)
   backColor = FSC_Color(backColorName, FILE=file)

  ; Calculate the histogram.
   histdata = Histogram(dataToHistogram, $
      BINSIZE=binsize, $
      L64=l64, $
      MAX=maxinput, $
      MIN=mininput, $
      NAN=nan, $
      LOCATIONS=locations, $
      OMAX=omax, $
      OMIN=omin, $
      REVERSE_INDICES=ri)

   ; Have to fudge the bins and histdata variables to get the
   ; histogram plot to make sense.
   npts = N_Elements(histdata)
   halfbinsize = binsize / 2.0
   bins = Findgen(N_Elements(histdata)+1) * binsize + mininput
   binsToPlot = [bins[0], bins[0:npts-1] + halfbinsize, bins[npts-1] + binsize]
   histdataToPlot = [histdata[0], histdata, histdata[npts-1]]
   ytitle = 'Histogram Density'
   ytickformat = '(I)'
   IF N_Elements(max_value) EQ 0 THEN max_value = Max(histdataToPlot) * 1.05
   yrange = [min_value, max_value]

   ; Set up data coordinate space by drawing plot without anything showing.
   IF ~Keyword_Set(overplot) THEN BEGIN

      ; Trouble doing this with !P.MULTI, so check and save variables.
      IF Total(!P.MULTI) NE 0 THEN BEGIN
         bangp = !P
         bangx = !X
         bangy = !Y
         bangmap = !MAP
      ENDIF
      Plot, binsToPlot, histdataToPlot, $            ; The fudged histogram and bin data.
            Background=backcolor, $                  ; The background color of the display.
            NoData=1, $                              ; Draw the axes only. No data.
            XStyle=5, $                              ; Exact axis scaling. No autoscaled axes.
            YRange=yrange, $                         ; The Y data range.
            YStyle=5, $                              ; Exact axis scaling. No autoscaled axes.
            _Extra=extra, $                          ; Pass any extra PLOT keywords.
            XTICKFORMAT='(A1)', YTICKFORMAT='(A1)'
      IF Total(!P.MULTI) NE 0 THEN BEGIN
         bangAfterp = !P
         bangAfterx = !X
         bangAftery = !Y
         bangAftermap = !MAP
      ENDIF
   ENDIF

   ; Do we need to have things be filled?
   IF Keyword_Set(fillpolygon) THEN BEGIN

       ncolors = N_Elements(polycolor)

      ; Are we line filling?
      IF line_fill THEN BEGIN

         norient = N_Elements(orientation)
         nspace = N_Elements(spacing)

         FOR j=0L,N_Elements(bins)-2 DO BEGIN
            x = [bins[j], bins[j], bins[j+1],  bins[j+1], bins[j]]
            y = min_value > [!Y.CRange[0], histDataToPlot[j+1], $
               histDataToPlot[j+1], !Y.CRange[0], !Y.CRange[0]] < max_value
            fillcolor = polycolor[j MOD ncolors]
            orient = orientation[j MOD norient]
            space = spacing[j MOD nspace]
            PolyFill, x, y, COLOR=FSC_Color(fillcolor, FILE=file), /LINE_FILL, ORIENTATION=orient, $
               PATTERN=pattern, SPACING=space, _Extra=extra
         ENDFOR

      ENDIF ELSE BEGIN ; Normal polygon color fill.

         FOR j=0L,N_Elements(bins)-2 DO BEGIN
            x = [bins[j], bins[j], bins[j+1],  bins[j+1], bins[j]]
            y = min_value > [!Y.CRange[0], histDataToPlot[j+1], histDataToPlot[j+1], $
                             !Y.CRange[0], !Y.CRange[0]] < max_value
            fillcolor = polycolor[j MOD ncolors]
            PolyFill, x, y, COLOR=FSC_Color(fillcolor, FILE=file), _Extra=extra
         ENDFOR

      ENDELSE
   ENDIF

   ; Plot the histogram of the display dataToHistogram. Do this after to repair
   ; damage to plot from POLYFILL.
   IF ~Keyword_Set(overplot) THEN BEGIN
      IF Total(!P.MULTI) NE 0 THEN BEGIN
         !P = bangp
         !X = bangx
         !Y = bangy
         !MAP = bangmap
      ENDIF
      Plot, binsToPlot, histdataToPlot, $            ; The fudged histogram and bin data.
            Color=axiscolor, $                       ; The color of the axes.
            NoData=1, $                              ; Draw the axes only. No data.
            XStyle=1, $                              ; Exact axis scaling. No autoscaled axes.
            XTitle='Data Value', $                   ; The title of the X axis.
            YMinor=1, $                              ; No minor tick mark on X axis.
            YRange=yrange, $                         ; The Y data range.
            YStyle=1, $                              ; Exact axis scaling. No autoscaled axes.
            YTickformat=ytickformat, $               ; The format of the Y axis annotations.
            YTitle=ytitle, $                         ; The title of the Y axis.
            /NOERASE, $                              ; Don't want to erase now!
            _Strict_Extra=extra                      ; Pass any extra PLOT keywords.
      IF Total(!P.MULTI) NE 0 THEN BEGIN
         !P = bangAfterp
         !X = bangAfterx
         !Y = bangAftery
         !MAP = bangAftermap
      ENDIF
   ENDIF

   ; Make histogram boxes by drawing lines in data color.
   IF Keyword_Set(nolines) THEN BEGIN
      last = N_Elements(bins)-1
      PlotS, [bins[0], bins[0]], min_value > [!Y.CRange[0], histDataToPlot[0]] < max_value, $
            Color=dataColor
      PlotS, [bins[last], bins[last]], min_value > [!Y.CRange[0], histDataToPlot[last]] < max_value, $
            Color=dataColor
   ENDIF ELSE BEGIN
      FOR j=0L, N_Elements(bins)-1 DO BEGIN
            PlotS, [bins[j], bins[j]], min_value > [!Y.CRange[0], histDataToPlot[j]] < max_value, $
               Color=dataColor
      ENDFOR
   ENDELSE

   ; Overplot the histogram data in the data color.
   OPlot, binsToPlot, histdataToPlot, PSym=10, Color=dataColor

   ; Clean up.
   TVLCT, r, g, b
END
