; docformat = 'rst'
;
; NAME:
;   cgScatter2D
;
; PURPOSE:
;   The purpose of cgScatter2d is to create a two-dimensional scatter plot with the 
;   option of drawing a correlation coefficient on the plot.
;
;******************************************************************************************;
;                                                                                          ;
;  Copyright (c) 2010, by Fanning Software Consulting, Inc. All rights reserved.           ;
;                                                                                          ;
;  Redistribution and use in source and binary forms, with or without                      ;
;  modification, are permitted provided that the following conditions are met:             ;
;                                                                                          ;
;      * Redistributions of source code must retain the above copyright                    ;
;        notice, this list of conditions and the following disclaimer.                     ;
;      * Redistributions in binary form must reproduce the above copyright                 ;
;        notice, this list of conditions and the following disclaimer in the               ;
;        documentation and/or other materials provided with the distribution.              ;
;      * Neither the name of Fanning Software Consulting, Inc. nor the names of its        ;
;        contributors may be used to endorse or promote products derived from this         ;
;        software without specific prior written permission.                               ;
;                                                                                          ;
;  THIS SOFTWARE IS PROVIDED BY FANNING SOFTWARE CONSULTING, INC. ''AS IS'' AND ANY        ;
;  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES    ;
;  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT     ;
;  SHALL FANNING SOFTWARE CONSULTING, INC. BE LIABLE FOR ANY DIRECT, INDIRECT,             ;
;  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED    ;
;  TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;         ;
;  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND             ;
;  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT              ;
;  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS           ;
;  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                            ;
;******************************************************************************************;
;
;+
; The purpose of cgScatter2d is to create a two-dimensional scatter plot with the 
; option of drawing a correlation coefficient on the plot.
; 
; The program requires the `Coyote Library <http://www.idlcoyote.com/documents/programs.php>`
; to be installed on your machine.
;   
; :Params:
; 
;     x: in, required
;        The variable along the X or horizontal dimension.
;     y: in, required
;        The varaiable along the Y or vertical dimension.
;        
; :Keywords:
; 
;     addcmd: in, optional, type=boolean, default=0
;        Set this keyword to add the command to the resizeable graphics window cgWindow.
;     aspect: in, optional, type=float, default=none
;        Set this keyword to a floating point ratio that represents the aspect ratio 
;        (ysize/xsize) of the resulting plot. The plot position may change as a result
;        of setting this keyword. Note that `Aspect` cannot be used when plotting with
;        !P.MULTI.
;     axiscolor: in, optional, type=string, default='opposite'
;        The name of the axis color. May be specified as a color table index number, as well.
;     axescolor: in, optional, type=string
;        Provisions for bad spellers.
;     background: in, optional, type=string, default='background'
;        The name of the background color. May be specified as a color table index number, as well.
;     charsize: in, optional, type=float, default=cgDefCharSize()
;        The character size for axes annotations. Uses cgDefCharSize to select default
;        character size, unless !P.Charsize is set, in which case !P.Charsize is always used.
;     color: in, optional, type=string, default='black'
;        The name of the data or symbol color. May be specified as a color table index or color triple, as well.
;        May be a vector of the same length as the input data vectors.
;     coefficient: out, optional, type=double
;        The Pearson correlation coefficient of the two data sets. Calculated with the IDL routine CORRELATE.
;     fcharsize: in, optional, type=float
;        The character size of the fit parameters that are written on the plot. This keyword is only
;        in effect if the `Fit` keyword is set. The default is the same as `Charsize`.
;     fcolor: in, optional, type=string, default='red'
;        The name of the color for the fitting line though the data
;     fthick: in, optional, type=integer, default=1
;        The thickness of the fitting line.
;     fillcolor: in, optional, type=string
;        If this keyword is set to the name of a color, that color will be used to "fill"
;        the area of the plot enclosed by the axes. Unfortunately, at this time, the `FillColor`
;        keyword can NOT be used with multiple plots, just single plots.
;     fit: in, optional, type=boolean, default=1
;        If this keyword is set to 1 (the default), a straight line is fit through the data
;        with the IDL routine LINFIT. If this keyword is set, the Pearson correlation coeffcient
;        and the equation of the fitted line is displayed on the scatter plot, unless the `NoDisplay`
;        keyword is set.
;     font: in, optional, type=integer, default=!P.Font
;        The type of font desired for axis annotation.
;     gcolor: in, optional, type=string, default='gray'
;        The name of the grid color. May be specified as a color table index number, as well.
;     glinestyle: in, optional, type=integer, default=1
;        The grid linestyle. Dotted by default. An integer from 0 to 5. See the IDL LineStyle 
;        graphics keyword documentation.
;     grid: in, optional, type=boolean, default=0
;        Set this keword to 1 to draw a grid on the plot.
;     isotropic: in, optional, type=boolean, default=0
;        A short-hand way of setting the `Aspect` keyword to 1.
;     layout: in, optional, type=intarr(3)
;        This keyword specifies a grid with a graphics window and determines where the
;        graphic should appear. The syntax of LAYOUT is three numbers: [ncolumns, nrows, location].
;        The grid is determined by the number of columns (ncolumns) by the number of 
;        rows (nrows). The location of the graphic is determined by the third number. The
;        grid numbering starts in the upper left (1) and goes sequentually by column and then
;        by row.
;     nodisplay, in, optional, type=boolean default=0
;        If this keyword is set, then the Pearson correlation coefficient and the equation
;        of the fitting line is not displayed on the plot. This keyword is only considered if
;        the `Fit` keyword is set.
;     noerase: in, optional, type=boolean, default=0
;        Set this keyword to draw the plot without erasing the display first.
;     outfilename: in, optional, type=string
;        If the `Output` keyword is set, the user will be asked to supply an output
;        filename, unless this keyword is set to a non-null string. In that case, the
;        value of this keyword will be used as the filename and there will be no dialog
;        presented to the user.
;     output: in, optional, type=string, default=""
;        Set this keyword to the type of output desired. Possible values are these::
;            
;            'PS'   - PostScript file
;            'EPS'  - Encapsulated PostScript file
;            'PDF'  - PDF file
;            'BMP'  - BMP raster file
;            'GIF'  - GIF raster file
;            'JPEG' - JPEG raster file
;            'PNG'  - PNG raster file
;            'TIFF' - TIFF raster file
;            
;        Or, you can simply set this keyword to the name of the output file, and the type of
;        file desired will be determined by the file extension. If you use this option, the
;        user will not be prompted to supply the name of the output file.  
;            
;        All raster file output is created through PostScript intermediate files (the
;        PostScript files will be deleted), so ImageMagick and Ghostview MUST be installed 
;        to produce anything other than PostScript output. (See cgPS2PDF and cgPS_Close for 
;        details.) And also note that you should NOT use this keyword when doing multiple 
;        plots. The keyword is to be used as a convenient way to get PostScript or raster 
;        output for a single graphics command. Output parameters can be set with cgWindow_SetDefs.
;     overplot: in, optional, type=boolean, default=0
;        Set this keyword if you wish to overplot data on an already exisiting set of
;        axes. It is like calling the IDL OPLOT command.
;     params: out, optional, type=double
;        The output line fitting parameters [intercept, slope].
;     position: in, optional, type=vector
;        The usual four-element position vector for the Plot comamnd. Only monitored and
;        possibly changed if the `Aspect` keyword is used.
;     psym: in, optional, type=integer, default=2
;        Any normal IDL PSYM values, plus any value supported by the Coyote Library
;        routine cgSYMCAT. An integer between 1 and 46.
;     symsize: in, optional, type=float, default=1.0
;        The symbol size.
;     title: in, optional, type=string, default=""
;        The title of the plot.
;     traditional: in, optional, type=boolean, default=0
;        If this keyword is set, the traditional color scheme of a black background for
;        graphics windows on the display is used and PostScript files always use a white background.
;     window: in, optional, type=boolean, default=0
;        Set this keyword to replace all the commands in a current cgWindow or to
;        create a new cgWindow for displaying this command.
;     xrange: in, optional, type=float
;        The X range of the plot.
;     xstyle: in, optional, type=integer, default=0
;        The value sent to the XStyle keyword for the Plot command.
;     xticklen: in, optional, type=float
;        The X tick length. Will be set to 1.0 if the `Grid` keyword is set.
;     xtitle: in, optional, type=string', default=""
;        The X title of the plot.
;     yrange: in, optional, type=float
;        The Y range of the plot.
;     ystyle: in, optional, type=integer, default=0
;        The value sent to the YStyle keyword for the Plot command.
;     yticklen: in, optional, type=float
;        THe Y tick length. Will be set to 1.0 if the `Grid` keyword is set.
;     ytitle: in, optional, type=string', default=""
;        The Y title of the plot.
;     _ref_extra: in, optional, type=any
;        Any keyword appropriate for the IDL Plot command is allowed in the program.
;        
; :Examples:
;    Use to compare two data sets::
;       data_1 = cgDemoData(1)+ RandomU(seed, 101) * 10
;       data_2 = cgDemoData(1)+ RandomU(seed, 101) * 10
;       cgScatter2D, data_1, data_2 
;       
;    Add a grid to the plot::
;       cgScatter2D, data_1, data_2, SymColor='navy', FillColor='rose', /Grid
;       
;    Output the plot to a PNG file::
;       cgScatter2D, data_1, data_2,  SymColor='navy', /Grid, FillColor='rose', Output='scatter.png'
;    
;    Display the plot in a resizeable graphics window::
;       cgScatter2D, data_1, data_2,  SymColor='navy', /Grid, FillColor='rose', /Window
;    
;       
; :Author:
;    FANNING SOFTWARE CONSULTING::
;        David W. Fanning 
;        1645 Sheely Drive
;        Fort Collins, CO 80526 USA
;        Phone: 970-221-0438
;        E-mail: david@idlcoyote.com
;        Coyote's Guide to IDL Programming: http://www.idlcoyote.com
;
; :History:
;     Change History::
;        Written, 12 January 2012. DWF.
;        Removed an extra COLOR keyword and changed an OPLOT command to a PLOTS command
;           to allow a vector of colors to be used for the scatter points.
;         Added the ability to use escape characters in plot titles to specify cgSymbol symbols. 27 July 2012. DWF.
;         Bad logic when creating a GRID and setting [XY]Style keywords. 30 Aug 2013. DWF.
;         If you are setting a range, but not using the [XY]Style keywords, then I am going to set [XY]Style=1. 17 Feb 2014. DWF.
;         Corrected a problem with axis ranges when drawing the first plot in a PostScript file. The problem was that the 
;             Plot command there was not identical to the other Plot commands in the program and it needs to be
;             to get the spacing and axis notation correct. 7 January 2016. DWF.
; :Copyright:
;     Copyright (c) 2012-2014, Fanning Software Consulting, Inc.
;-
PRO cgScatter2D, x, y, $
    AddCmd=addcmd, $
    Aspect=aspect, $
    AxisColor=saxiscolor, $
    AxesColor=saxescolor, $
    Background=sbackground, $
    Charsize=charsize, $
    Color=scolor, $
    Coefficient=coefficient, $
    FCharsize=fcharsize, $
    FColor=sfcolor, $
    FThick=fthick, $
    FillColor=sfillcolor, $
    Fit=fit, $
    Font=font, $
    GColor=sgcolor, $
    GLinestyle=glinestyle, $
    Grid=grid, $
    Isotropic=isotropic, $
    Layout=layout, $
    NoDisplay=nodisplay, $
    NoErase=noerase, $
    OutFilename=outfilename, $
    Output=output, $
    Overplot=overplot, $
    Params=params, $
    Position=position, $
    PSym=psym, $
    SymSize=symsize, $
    Title=title, $
    Traditional=traditional, $
    Window=window, $
    XRange=xrange, $
    XStyle=xstyle, $
    XTickLen=xticklen, $
    XTitle=xtitle, $
    YRange=yrange, $
    YStyle=ystyle, $
    YTicklen=yticklen, $
    YTitle=ytitle, $
    _REF_EXTRA=extra
    
    Compile_Opt idl2

    Catch, theError
    IF theError NE 0 THEN BEGIN
        Catch, /CANCEL
        void = cgErrorMsg()
        IF N_Elements(thisMulti) NE 0 THEN !P.Multi = thisMulti
        IF N_Elements(currentState) NE 0 THEN cgSetColorState, currentState
        IF (N_Elements(output) NE 0) THEN cgPS_Close, /NOFIX
        RETURN
    ENDIF
    
    ; Set up PostScript device for working with colors.
    IF !D.Name EQ 'PS' THEN Device, COLOR=1, BITS_PER_PIXEL=8
    
    ; Check parameters.
    IF N_Params() EQ 0 THEN BEGIN
        Print, 'USE SYNTAX: cgScatter2D, x, y'
        RETURN
    ENDIF
    
    ; Pay attention to !P.Noerase in setting the NOERASE kewyord. This must be
    ; done BEFORE checking the LAYOUT properties.
    IF !P.NoErase NE 0 THEN noerase = !P.NoErase ELSE noerase = Keyword_Set(noerase)
    
    ; Do they want this plot in a resizeable graphics window?
    IF Keyword_Set(addcmd) THEN window = 1

        IF Keyword_Set(window) AND ((!D.Flags AND 256) NE 0) THEN BEGIN
    
        ; If you are using a layout, you can't ever erase.
        IF N_Elements(layout) NE 0 THEN noerase = 1
        
        ; Special treatment for overplotting or adding a command.
        IF Keyword_Set(overplot) OR Keyword_Set(addcmd) THEN BEGIN
        cgWindow, 'cgScatter2D', x, y, $
            Aspect=aspect, $
            AxisColor=saxiscolor, $
            AxesColor=saxescolor, $
            Background=sbackground, $
            Charsize=charsize, $
            Color=scolor, $
            Coefficient=coefficient, $
            FCharsize=fcharsize, $
            FColor=sfcolor, $
            FThick=fthick, $
            FillColor=sfillcolor, $
            Fit=fit, $
            Font=font, $
            GColor=sgcolor, $
            GLinestyle=glinestyle, $
            Grid=grid, $
            Isotropic=isotropic, $
            Layout=layout, $
            NoDisplay=nodisplay, $
            NoErase=noerase, $
            OutFilename=outfilename, $
            Output=output, $
            Overplot=overplot, $
            Params=params, $
            Position=position, $
            PSym=psym, $
            SymSize=symsize, $
            Title=title, $
            Traditional=traditional, $
            XRange=xrange, $
            XStyle=xstyle, $
            XTickLen=xticklen, $
            XTitle=xtitle, $
            YRange=yrange, $
            YStyle=ystyle, $
            YTicklen=yticklen, $
            YTitle=ytitle, $
            ADDCMD=1, $
            _Extra=extra
             RETURN
       ENDIF
        
        ; Open a new window or replace the current commands, as required.
        currentWindow = cgQuery(/CURRENT, COUNT=wincnt)
        IF wincnt EQ 0 THEN replaceCmd = 0 ELSE replaceCmd=1
        cgWindow, 'cgScatter2D', x, y, $
            Aspect=aspect, $
            AxisColor=saxiscolor, $
            AxesColor=saxescolor, $
            Background=sbackground, $
            Charsize=charsize, $
            Color=scolor, $
            Coefficient=coefficient, $
            FCharsize=fcharsize, $
            FColor=sfcolor, $
            FThick=fthick, $
            FillColor=sfillcolor, $
            Fit=fit, $
            Font=font, $
            GColor=sgcolor, $
            GLinestyle=glinestyle, $
            Grid=grid, $
            Isotropic=isotropic, $
            Layout=layout, $
            NoDisplay=nodisplay, $
            NoErase=noerase, $
            OutFilename=outfilename, $
            Output=output, $
            Overplot=overplot, $
            Params=params, $
            Position=position, $
            PSym=psym, $
            SymSize=symsize, $
            Title=title, $
            Traditional=traditional, $
            XRange=xrange, $
            XStyle=xstyle, $
            XTitle=xtitle, $
            XTickLen=xticklen, $
            YRange=yrange, $
            YStyle=ystyle, $
            YTicklen=yticklen, $
            YTitle=ytitle, $
            REPLACECMD=replaceCmd, $
            _Extra=extra
            
         RETURN
    ENDIF
    
    ; Are we doing some kind of output?
    IF (N_Elements(output) NE 0) && (output NE "") THEN BEGIN
    
       ; If the output string has a dot character, then this must be a
       ; filename, and we will determine the type of file from the filename extension.
       IF StrPos(output, '.') NE -1 THEN BEGIN
             root_name = cgRootName(output, DIRECTORY=theDir, EXTENSION=ext)
             IF theDir EQ "" THEN CD, CURRENT=theDir
             outfilename = output
             outputSelection = StrUpCase(ext)
       ENDIF
    
       IF N_Elements(outputSelection) EQ 0 THEN outputSelection = StrUpCase(output)
       typeOfOutput = ['PS','EPS','PDF','BMP','GIF','JPEG','JPG','PNG','TIFF', 'TIF']
       void = Where(typeOfOutput EQ outputSelection, count)
       IF count EQ 0 THEN Message, 'Cannot find ' + outputSelection + ' in allowed output types.'
       
       ; Set things up.
       CASE outputSelection OF
          'PS': BEGIN
              ext = '.ps'
              delete_ps = 0
              END    
          'EPS': BEGIN
              ext = '.eps'
              encapsulated = 1
              delete_ps = 0
              END
          'PDF': BEGIN
              ext = '.pdf'
              pdf_flag = 1
              delete_ps = 1
              END     
          'BMP': BEGIN
              ext = '.bmp'
              bmp_flag = 1
              delete_ps = 1
              END      
          'GIF': BEGIN
              ext = '.gif'
              gif_flag = 1
              delete_ps = 1
              END
          'JPEG': BEGIN
              ext = '.jpg'
              jpeg_flag = 1
              delete_ps = 1
              END      
          'JPG': BEGIN
              ext = '.jpg'
              jpeg_flag = 1
              delete_ps = 1
              END
          'PNG': BEGIN
              ext = '.png'
              png_flag = 1
              delete_ps = 1
              END      
          'TIFF': BEGIN
              ext = '.tif'
              tiff_flag = 1
              delete_ps = 1
              END
          'TIF': BEGIN
              ext = '.tif'
              tiff_flag = 1
              delete_ps = 1
              END    
       ENDCASE
       
       ; Do you need a filename?
       IF ( (N_Elements(outfilename) EQ 0) || (outfilename EQ "") ) THEN BEGIN 
            filename = 'cgplot' + ext
            outfilename = cgPickfile(FILE=filename, TITLE='Select Output File Name...', $
                FILTER=ext, /WRITE)
            IF outfilename EQ "" THEN RETURN
       ENDIF
       
       ; We need to know the root name of the file, because we have to make a PostScript
       ; file of the same name. At least we do if the type is not PS or EPS.
       IF (outputSelection NE 'PS') && (outputSelection NE 'EPS') THEN BEGIN
           root_name = cgRootName(outfilename, DIRECTORY=theDir)
           IF theDir EQ "" THEN CD, CURRENT=theDir
           ps_filename = Filepath(ROOT_DIR=theDir, root_name + '.ps')
       ENDIF ELSE ps_filename = outfilename
       
       ; Get the output default values.
       cgWindow_GetDefs, $
         PS_Charsize = ps_charsize, $          ; The PostScript character size.
         PS_FONT = ps_font, $                  ; Select the font for PostScript output.
         PS_Decomposed = ps_decomposed, $      ; Sets the PostScript color mode.
         PS_Delete = ps_delete, $              ; Delete PS file when making IM raster.
         PS_Metric = ps_metric, $              ; Select metric measurements in PostScript output.
         PS_Scale_factor = ps_scale_factor, $  ; Select the scale factor for PostScript output.
         PS_TT_Font = ps_tt_font               ; Select the true-type font to use for PostScript output.   
       
       ; Set up the PostScript device.
       cgPS_Open, $
          CHARSIZE=ps_charsize, $
          DECOMPOSED=ps_decomposed, $
          FILENAME=ps_filename, $
          FONT=ps_font , $
          ENCAPSULATED=encapsulated, $
          METRIC=ps_metric, $
          SCALE_FACTOR=ps_scale_factor, $
          TT_FONT=ps_tt_font, $
          QUIET=1
    
    
    ENDIF
    
    ; Get the current color table vectors.
    TVLCT, rr, gg, bb, /GET
    
    ; Going to do this in decomposed color, if possible.
    cgSetColorState, 1, CURRENTSTATE=currentState
    
    ; Set up the layout, if necessary.
    IF N_Elements(layout) NE 0 THEN BEGIN
       thisMulti = !P.Multi
       totalPlots = layout[0]*layout[1]
       !P.Multi = [0,layout[0], layout[1], 0, 0]
       IF layout[2] EQ 1 THEN BEGIN
            noerase = 1
            !P.Multi[0] = 0
       ENDIF ELSE BEGIN
            !P.Multi[0] = totalPlots - layout[2] + 1
       ENDELSE
    ENDIF

    ; Check the color keywords.
    traditional = Keyword_Set(traditional)
    background = cgDefaultColor(sbackground, /BACKGROUND, TRADITIONAL=traditional)
    IF (N_Elements(saxisColor) EQ 0) && (N_Elements(saxesColor) NE 0) THEN saxisColor = saxesColor
    axisColor = cgDefaultColor(saxisColor, TRADITIONAL=traditional)
    color = cgDefaultColor(sColor, DEFAULT=axisColor, TRADITIONAL=traditional)
    fcolor = cgDefaultColor(sfColor, DEFAULT='red', TRADITIONAL=traditional)
    gcolor = cgDefaultColor(sgColor, DEFAULT='gray', TRADITIONAL=traditional)
    IF (N_Elements(xrange) NE 0) && (N_Elements(xstyle) EQ 0) THEN xstyle = 1
    IF (N_Elements(yrange) NE 0) && (N_Elements(ystyle) EQ 0) THEN ystyle = 1
    
    ; Character size has to be determined *after* the layout has been decided.
    IF N_Elements(font) EQ 0 THEN font = !P.Font
    IF N_Elements(charsize) EQ 0 THEN charsize = cgDefCharSize(FONT=font)
    
    ; Other keywords.
    SetDefaultValue, fit, 1
    SetDefaultValue, fcharsize, charsize
    SetDefaultValue, fthick, 1
    IF !D.Name EQ 'PS' THEN fthick = 3*fthick
    SetDefaultValue, grid, 0, /Boolean
    SetDefaultValue, glinestyle, 1
    IF grid THEN BEGIN
        xticklen = 1
        yticklen = 1
    ENDIF
    SetDefaultValue, symsize, 1.0
    IF Keyword_Set(isotropic) THEN aspect = 1.0
    IF N_Elements(psym) EQ 0 THEN psym = 2 ELSE psym = 1 > psym 
    IF N_Elements(xrange) EQ 0 THEN BEGIN
        range = Max(x) - Min(x)
        IF Min(x) NE 0 THEN minx = Min(x)-(range*0.05) ELSE minx = 0.0
        xrange = [minx, Max(x)+(range*0.05)]
    ENDIF
    IF N_Elements(yrange) EQ 0 THEN BEGIN
        range = Max(y) - Min(y)
        IF Min(y) NE 0 THEN miny = Min(y)-(range*0.05) ELSE miny = 0.0
        yrange = [miny, Max(y)+(range*0.05)]
    ENDIF
    SetDefaultValue, xstyle, 0
    SetDefaultValue, ystyle, 0
    
    IF (N_Elements(aspect) NE 0) AND (Total(!P.MULTI) EQ 0) THEN BEGIN
    
        ; If position is set, then fit the plot into those bounds.
        IF (N_Elements(position) GT 0) THEN BEGIN
          trial_position = cgAspect(aspect, margin=0.)
          trial_width = trial_position[2]-trial_position[0]
          trial_height = trial_position[3]-trial_position[1]
          pos_width = position[2]-position[0]
          pos_height = position[3]-position[1]

          ; Same logic as cgImage: try to fit image width, then if you can't get the right aspect
          ; ratio, fit the image height instead.
          fit_ratio = pos_width / trial_width
          IF trial_height * fit_ratio GT pos_height THEN $
             fit_ratio = pos_height / trial_height

          ; new width and height
          trial_width *= fit_ratio
          trial_height *= fit_ratio

          ; calculate position vector based on trial_width and trial_height
          position[0] += 0.5*(pos_width - trial_width)
          position[2] -= 0.5*(pos_width - trial_width)
          position[1] += 0.5*(pos_height - trial_height)
          position[3] -= 0.5*(pos_height - trial_height)
        ENDIF ELSE position=cgAspect(aspect)   ; if position isn't set, just use output of Aspect
        
    ENDIF
    
    ; If we don't have a position yet, and we are not doing multiple plots, define a position.
    IF (N_Elements(position) EQ 0) && (Total(!P.Multi) EQ 0) THEN position = [0.15, 0.125, 0.9, 0.9]
    
    ; Do you need a PostScript background color? Lot's of problems here!
    ; Basically, I MUST draw a plot to advance !P.MULTI. But, drawing a
    ; plot of any sort erases the background color. So, I have to draw a 
    ; plot, store the new system variables, then draw my background, etc.
    ; I have tried LOTS of options. This is the only one that worked.
    IF !D.Name EQ 'PS' THEN BEGIN
         IF ~noerase THEN BEGIN
       
           ; I only have to do this, if this is the first plot.
           IF !P.MULTI[0] EQ 0 THEN BEGIN
           
                IF Keyword_Set(overplot) NE 1 THEN BEGIN
                
                    ; Save the current system variables. Will need to restore later.
                    bangx = !X
                    bangy = !Y
                    bangp = !P
                    
                    ; Draw the plot that doesn't draw anything.
                    Plot, x, y, POSITION=position, CHARSIZE=charsize, /NODATA, $
                        NOERASE=tempNoErase, FONT=font, $
                        XTICKLEN=xticklen, YTICKLEN=yticklen, XRANGE=xrange, YRANGE=yrange, $
                        XGRIDSTYLE=glinestyle, YGRIDSTYLE=glinestyle, $
                        XSTYLE=(2S^16-5) AND xstyle, YSTYLE=(2S^16-5) AND ystyle, $
                        _STRICT_EXTRA=extra, XTickFormat='(A1)', YTickFormat='(A1)', $
                        XTITLE="", YTITLE="", TITLE=""
                    
                    ; Save the "after plot" system variables. Will use later. 
                    afterx = !X
                    aftery = !Y
                    afterp = !P     
                    
                    ; Draw the background color and set the variables you will need later.
                    PS_Background, background
                    psnodraw = 1
                    tempNoErase = 1
                    
                    ; Restore the original system variables so that it is as if you didn't
                    ; draw the invisible plot.
                    !X = bangx
                    !Y = bangy
                    !P = bangp
                
                ENDIF
            ENDIF ELSE tempNoErase = noerase
        ENDIF ELSE tempNoErase = noerase
     ENDIF ELSE tempNoErase = noerase
 
    
    ; Load the drawing colors. If needed create a window first, so the drawing
    ; colors are correct for the window you want to draw into.
    IF ((!D.Flags AND 256) NE 0) && (!D.Window LT 0) THEN cgDisplay
    IF Size(axiscolor, /TNAME) EQ 'STRING' THEN axiscolor = cgColor(axiscolor)
    IF Size(color, /TNAME) EQ 'STRING' THEN color = cgColor(color)
    IF Size(gcolor, /TNAME) EQ 'STRING' THEN gcolor = cgColor(gcolor)
    IF Size(fcolor, /TNAME) EQ 'STRING' THEN fcolor = cgColor(fcolor)
    IF Size(background, /TNAME) EQ 'STRING' THEN background = cgColor(background)
    
    ; Do you need a plot fill color?
    IF (N_Elements(sfillColor) NE 0) && (Total(!P.Multi) EQ 0) THEN BEGIN
        p = position
        fillColor = cgColor(sfillColor)
        IF !D.Name NE 'PS' THEN cgErase, Color=background
        PolyFill, [p[0], p[0], p[2], p[2], p[0]], /Normal, $
                  [p[1], p[3], p[3], p[1], p[1]], Color=fillcolor
        tempNoErase = 1
    ENDIF ELSE BEGIN
         IF (N_Elements(sfillColor) NE 0) && (Total(!P.Multi) NE 0) THEN BEGIN
             Message, 'Cannot use the FillColor keyword with multiple plots.', /Informational
         ENDIF
    ENDELSE
    
    ; Check for symbols in titles.
    IF N_Elements(title) NE 0 THEN title = cgCheckForSymbols(title)
    IF N_Elements(xtitle) NE 0 THEN xtitle = cgCheckForSymbols(xtitle)
    IF N_Elements(ytitle) NE 0 THEN ytitle = cgCheckForSymbols(ytitle)    

    ; Draw the plot.
    IF Keyword_Set(overplot) THEN BEGIN
       IF psym LE 0 THEN OPlot, dep, indep, COLOR=color, _EXTRA=extra
    ENDIF ELSE BEGIN
      IF grid THEN BEGIN
          bangxgrid = !X
          bangygrid = !Y
          bangpgrid = !P
          Plot, x, y, BACKGROUND=background, COLOR=gcolor, CHARSIZE=charsize, $
                POSITION=position, /NODATA, NOERASE=tempNoErase, FONT=font, $
                XTICKLEN=xticklen, YTICKLEN=yticklen, XRANGE=xrange, YRANGE=yrange, $
                XGRIDSTYLE=glinestyle, YGRIDSTYLE=glinestyle, $
                XSTYLE=(2S^16-5) AND xstyle, YSTYLE=(2S^16-5) AND ystyle, $
                _STRICT_EXTRA=extra, XTickFormat='(A1)', YTickFormat='(A1)', $
                XTITLE="", YTITLE="", TITLE=""
           
          aftergridx = !X
          aftergridy = !Y
          aftergridp = !P
          !X = bangxgrid
          !Y = bangygrid    
          !P = bangpgrid 
          Plot, x, y, COLOR=axiscolor, CHARSIZE=charsize, $
                POSITION=position, /NODATA, NOERASE=(!P.Multi[0] GT 0) ? 0 : 1, FONT=font, $
                XRANGE=xrange, YRANGE=yrange, $
                XSTYLE=xstyle, YSTYLE=ystyle, _STRICT_EXTRA=extra, $
                XTITLE=xtitle, YTITLE=ytitle, TITLE=title
          IF !P.Multi[0] EQ 0 THEN BEGIN
             !X = aftergridx
             !Y = aftergridy
             !P = aftergridp
          ENDIF
      ENDIF ELSE BEGIN
          Plot, x, y, BACKGROUND=background, COLOR=axiscolor, CHARSIZE=charsize, $
                POSITION=position, /NODATA, NOERASE=tempNoErase, FONT=font, $
                XTICKLEN=xticklen, YTICKLEN=yticklen, XRANGE=xrange, YRANGE=yrange, $
                XSTYLE=xstyle, YSTYLE=ystyle, _STRICT_EXTRA=extra, $
                XTITLE=xtitle, YTITLE=ytitle, TITLE=title
      ENDELSE
    ENDELSE
    IF Abs(psym) GT 1 THEN BEGIN
        PlotS, x, y, COLOR=color, PSYM=cgSymCat(Abs(psym), _Extra=extra), $
           SYMSIZE=symsize, _EXTRA=extra
    ENDIF 
    
    ; Calculate the Pearson correlation coefficient.
    coefficient = Correlate(x, y, /DOUBLE)
    
    ; Add the fitting line, if needed.
    IF fit THEN BEGIN
       params = LinFit(x, y, /Double, YFIT=yfit)
       OPlot, x, yfit, COLOR=fcolor, THICK=fthick
       
       IF coefficient GE 0 THEN BEGIN
          yloc = !Y.Window[1] - 0.05
          xloc = !X.Window[0] + 0.05
          alignment = 0.0
          y1loc = yloc - ( 2.0 * (!D.Y_CH_SIZE / Float(!D.Y_Size) ) )
       ENDIF ELSE BEGIN
          yloc = !Y.Window[1] - 0.05
          xloc = (!X.Window[1]-!X.Window[0]) * (3.0/5.0) + !X.Window[0]
          alignment = 0.0
          y1loc = yloc - (2.0 * (!D.Y_CH_SIZE / Float(!D.Y_Size)))
       ENDELSE
       
       ; Write the correlation coefficient and the fitting equation on the display,
       ; if allowed to.
       IF Keyword_Set(nodisplay) EQ 0 THEN BEGIN
           cgText, xloc, yloc, /Normal, 'R = ' + String(coefficient, Format='(F0.3)'), $
               Charsize=fcharsize
           cgText, xloc, y1loc, /Normal, 'y = ' + String(params[1], Format='(F0.2)') $
               + 'x + ' + String(params[0], Format='(F0.2)'), Charsize=fcharsize
       ENDIF
    ENDIF
         
    ; If this is the first plot in PS, then we have to make it appear that we have
    ; drawn a plot, even though we haven't.
    IF N_Elements(psnodraw) EQ 1 THEN BEGIN
        !X = afterX
        !Y = afterY
        !P = afterP
    ENDIF
    
    ; Are we producing output? If so, we need to clean up here.
    IF (N_Elements(output) NE 0) && (output NE "") THEN BEGIN
    
       ; Get the output default values.
       cgWindow_GetDefs, $
           IM_Density = im_density, $                      ; Sets the density parameter on ImageMagick convert command.
           IM_Options = im_options, $                      ; Sets extra ImageMagick options on the ImageMagick convert command.
           IM_Resize = im_resize, $                        ; Sets the resize parameter on ImageMagick convert command.
           IM_Transparent = im_transparent, $              ; Sets the "alpha" keyword on ImageMagick convert command.
           IM_Width = im_width, $                          ; Sets the width of raster file output created with ImageMagick.
           PDF_Unix_Convert_Cmd = pdf_unix_convert_cmd, $  ; Command to convert PS to PDF.
           PDF_Path = pdf_path                             ; The path to the Ghostscript conversion command.
    
        ; Close the PostScript file and create whatever output is needed.
        cgPS_Close, DELETE_PS=delete_ps, $
             ALLOW_TRANSPARENT=im_transparent, $
             BMP=bmp_flag, $
             DENSITY=im_density, $
             GIF=gif_flag, $
             GS_PATH=pdf_path, $
             IM_OPTIONS=im_options, $
             JPEG=jpeg_flag, $
             PDF=pdf_flag, $
             PNG=png_flag, $
             RESIZE=im_resize, $
             TIFF=tiff_flag, $
             UNIX_CONVERT_CMD=pdf_unix_convert_cmd, $
             WIDTH=im_width

         basename = File_Basename(outfilename)
         dirname = File_Dirname(outfilename)
         IF dirname EQ "." THEN CD, CURRENT=dirname
         Print, 'Output File: ' + Filepath(ROOT_DIR=dirname, basename)
    ENDIF
    
    ; Restore the decomposed color state if you can.
    cgSetColorState, currentState
    
    ; Restore the color table. Can't do this for the Z-buffer or
    ; the snap shot will be incorrect.
    IF (!D.Name NE 'Z') THEN TVLCT, rr, gg, bb
    
    ; Clean up if you are using a layout.
    IF N_Elements(layout) NE 0 THEN !P.Multi = thisMulti
    
END
    