cglayout.pro 17.2 KB
; docformat = 'rst'
;
; NAME:
;   cgLayout
;
; PURPOSE:
;   The purpose of this program is to return the normalized position coordinates for 
;   a line plot, contour plot, or image plot with a specific "layout" in the current
;   graphics window. A "layout" has a specified grid of columns and rows organized 
;   inside a graphics display window. This is similar to the positions calculated by 
;   !P.Multi, although a great deal more flexible. 
;
;******************************************************************************************;
;                                                                                          ;
;  Copyright (c) 2012, 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 this program is to return the normalized position coordinates for 
; a line plot, contour plot, or image plot with a specific "layout" in the current
; graphics window. A "layout" has a specified grid of columns and rows organized 
; inside a graphics display window. This is similar to the positions calculated by 
; !P.Multi, although a great deal more flexible. Of course, with additional flexibility 
; and power comes more responsibility. You will be required to use the NoErase keyword
; correctly and you will be responsible for setting the character size on your plots.
; These jobs are normally handled by !P.Multi. Some users will find this liberating, some 
; will find it a pain in the keister. There is always !P.Multi to go back to.
; 
; A grid position is a combination of the number of columns and rows desired, plus
; the application of inside and outside margins, as well as a desired aspect ratio.
; Margins are specified in units of one-third the !D.X_PX_CM value to be device independent. 
; The outside margins locate the grid inside the graphics display window.
; (These are equivalent to !X.OMargin and !Y.OMargin system variable when displaying
; a grid with !P.Multi, for example.) Inside margins use the same units, but are
; used to modify the initial grid positions in the window. (These are equivalent
; to using the XMargin and YMargin keywords on a plot command.) For example, inside margins
; might be used to leave room inside a larger position for color bars or other annotations
; you wish to put on a graphics display. The aspect ratio modifies the grid position
; after the outside and inside margins have been applied to create the final grid position,
; which will be centered in its initial grid position.
;
; :Categories:
;    Graphics, Utilities
;    
; :Returns:
;    This function returns the normalized position coordinates for a particular
;    location in a grid of locations (if the layout parameter is a three-element array),
;    or a 4-by-(ncols*nrows) array of grid positions with one position for each location
;    in the ncols by nrows grid of locations (if the layout parameter is a two-element array).
;    The return variable is suitable for passing to the POSITION keyword of an IDL graphics
;    command (e.g., cgPlot, cgContour, cgImage, etc.). The grid positions are organized in
;    row order, starting with the grid location in the upper-left of the graphihcs display window.
;    
; :Params:
; 
;    layout: in, required, type=integer
;         This parameter sets up the grid layout for the current graphics window. A grid
;         is organized by columns and rows, with the first grid position located in the upper-left
;         corner of the current graphics display window and proceeding in row order. This parameter
;         is either a two-element vector, giving the number of columns and number of rows,
;         respectively (e.g., [ncols,nrows]), or it is a three-element vector, giving, in addition, the
;         specific grid location for which a position is required (e.g, [ncols, nrows, gridPosition]).
;         Grid positions start at 1 with the first grid in the upper left corner of the graphics display 
;         window and proceed in row order, sequentually, until the last grid position, which is equal 
;         to the number of columns times the number of rows.
;       
; :Keywords:
; 
;    aspect: in, optional, type=float
;         This kewyord allows you to specify a specific aspect ratio for the return
;         positions. The aspect ratio is calculated as YDimension/XDimension. So, for
;         example, if you wish the positions to be twice as wide as they are high, you
;         would set the Aspect keyword to 1.0/2.0 or 0.5. If you wish your positions to
;         have a square aspect ratio, you would set the Aspect keyword to 1.0.
;         
;    ixmargin: in, optional, type=integer
;         This keyword is a two-element vector that sets the right and left, respectively, inside
;         X margin for the grid position. Units are multiples of !D.X_CH_SIZE. Default = [0,0].
;         
;    iymargin: in, optional, type=integer
;         This keyword is a two-element vector that sets the bottom and top, respectively, inside
;         Y margin for the grid position. Units are multiples of !D.Y_CH_SIZE. Default = [0,0].
;         
;    oxmargin: in, optional, type=integer
;         This keyword is a two-element vector that sets the right and left, respectively, inside
;         X margin for the grid position. The default OXMargins are suitable for displaying line
;         plots. If you are displaying image plots, you may wish to make the OXMargins the same on
;         both sides of the graphics display window (e.g, OXMargin=[5,5]). Units are multiples of 
;         !D.X_CH_SIZE. Default = [10,4].
;         
;    oymargin: in, optional, type=integer
;         This keyword is a two-element vector that sets the bottom and top, respectively, inside
;         Y margin for the grid position. The default OYMargins are suitable for displaying line
;         plots. There is a little additional room at the top of the plot in the defaults for adding 
;         a title to a multiple plot set-up. Units are multiples of !D.Y_CH_SIZE. Default = [6,8].
;         
;    unit: in, optional, type=float
;          The most difficult part of calculating a layout in a device-indepentent manner is 
;          coming up with a "unit" of measurement that makes sense. The current default unit is 
;          !D.X_PX_CM / 4.0. This gives respectable results for "normal" sized windows and a
;          "normal" number of multiplots. It may not work for you. If not, feel free to set your
;          own unit here. The margin and gap keywords are multiplied by this value before the 
;          layout is calculated.
;         
;    xgap: in, optional, type=integer, default=14
;         This keywords sets the distance between plots in the X dimension. Units are multiples
;         of !D.X_CH_SIZE.
;         
;    ygap: in, optional, type=integer, default=8
;         This keywords sets the distance between plots in the Y dimension. Units are multiples
;         of !D.Y_CH_SIZE.
;    
; :Examples:
;    Here is how to use this program to display line plots::
;    
;        cgDisplay, WID=0
;        pos = cgLayout([2,2])
;        FOR j=0,3 DO BEGIN
;          cgPlot, cgDemoData(17), NoErase=j NE 0, Position=pos[*,j], Title='Plot ' + StrTrim(j+1,2)
;        ENDFOR
;        cgText, 0.5, 0.925, /Normal, 'Example Plot Layout', Alignment=0.5, Charsize=cgDefCharsize()*1.25
;       
;    Here is how to use this program to display contour plots or images with colorbars::
;    
;        cgDisplay, WID=1
;        cgLoadCT, 22, /Brewer, /Reverse
;        pos = cgLayout([2,2], OXMargin=[5,5], OYMargin=[5,12], XGap=3, YGap=10)
;        FOR j=0,3 DO BEGIN
;          p = pos[*,j]
;          cgImage, cgDemoData(18), NoErase=j NE 0, Position=p
;          cgColorBar, position=[p[0], p[3]+0.05, p[2], p[3]+0.1]
;        ENDFOR
;        cgText, 0.5, 0.925, /Normal, 'Example Image Layout', Alignment=0.5, Charsize=cgDefCharsize()*1.25
;        
;    .. image:: cglayout.png
;    
;    Here is how to display square plots in a PostScript file::
;    
;        cgPS_Open, 'cglayout_example.ps'
;        cgDisplay
;        pos = cgLayout([2,2], Aspect=1.0)
;        FOR j=0,3 DO BEGIN
;          cgPlot, cgDemoData(17), NoErase=j NE 0, Position=pos[*,j], Title='Plot ' + StrTrim(j+1,2)
;        ENDFOR
;        cgText, 0.5, 0.925, /Normal, 'Example Plot Layout', Alignment=0.5, Charsize=cgDefCharsize()*1.25
;        cgPS_Close
;        
;    Here is how to draw the third plot in a 3 column by 2 row layout::
;    
;        cgDisplay, 800, 600, WID=3
;        cgPlot, cgDemoData(17), Position=cgLayout([3,2,3])
;        
; :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, 19 December 2012 by David W. Fanning, from suggestions from Matthew Argall.
;       Changed the notion of one "unit" from the values of !D.X_CH_SIZE and !D.Y_CH_SIZE to
;           1/3 of the value of !D.X_PX_CM. This gives me more consistent measurements on the
;           display and in a PostScript file. 12 Feb 2013. DWF.
;       Modified the default unit to be !D.X_PX_CM/4.0 and added a UNIT keyword so users can
;           choose a value that makes senses for their layouts. 25 Nov 2014. DWF.
;       Added checks to be sure margin keywords contain two elements. 15 Dec 2014. DWF.
;
; :Copyright:
;     Copyright (c) 2012-2014, Fanning Software Consulting, Inc.
;-
FUNCTION cgLayout, layout, $
   ASPECT=aspect, $
   IXMARGIN=ixMargin, $
   IYMARGIN=iyMargin, $
   OXMARGIN=oxMargin, $
   OYMARGIN=oyMargin, $
   UNIT=unit, $
   XGAP=xgap, $
   YGAP=ygap
   
   Compile_Opt idl2
   
   Catch, theError
   IF theError NE 0 THEN BEGIN
      Catch, /CANCEL
      void = cgErrorMsg()
      RETURN, -1
   ENDIF
   
   ; Check parameters.
   IF N_Elements(layout) EQ 0 THEN BEGIN
      Print, 'Syntax: pos = cgLayout([2,3,1])'
      RETURN, -1
   ENDIF
   IF N_Elements(layout) EQ 3 THEN layoutIndex = layout[2]-1
   
   ; Check keywords.
   area = Keyword_Set(area)
   IF N_Elements(ixmargin) EQ 0 THEN ixmargin = [0,0]
   IF N_Elements(iymargin) EQ 0 THEN iymargin = [0,0]
   IF N_Elements(oxmargin) EQ 0 THEN oxmargin = [10,4]
   IF N_Elements(oymargin) EQ 0 THEN oymargin = [6,8]
   IF N_Elements(xgap) EQ 0 THEN xgap = 14
   IF N_Elements(ygap) EQ 0 THEN ygap = 8
   
   ; Make sure all margins are two-element arrays.
   IF N_Elements(ixmargin) EQ 1 THEN ixmargin = [ixmargin, 0]
   IF N_Elements(iymargin) EQ 1 THEN iymargin = [iymargin, 0]
   IF N_Elements(oxmargin) EQ 1 THEN oxmargin = [oxmargin, 4]
   IF N_Elements(oymargin) EQ 1 THEN oymargin = [oymargin, 8]
   
   ; Get the size of the window. If the current device supports windows,
   ; a window has to be open.
   IF ((!D.FLAGS AND 256) NE 0) AND !D.Window LT 0 THEN BEGIN
        createdWindow = 1
        Window, /Pixmap
   ENDIF ELSE createdWindow = 0
   xsize = Double(!D.X_Size)
   ysize = Double(!D.Y_Size)
   IF createdWindow THEN WDelete, !D.Window
   
   ; Number of columns and rows.
   IF N_Elements(layout) LT 2 THEN Message, 'Layout parameter must be a 2- or 3-element vector.'
   ncols = layout[0]
   nrows = layout[1]
   
   ; Calculate a default "unit" that works in a device-independent way.
   IF N_Elements(unit) EQ 0 THEN BEGIN
       ;IF xsize GT ysize THEN unit = !D.X_CH_SIZE ELSE unit = !D.Y_CH_SIZE
       unit = !D.X_PX_CM / 4.0
   ENDIF
   
   ; Set up the inside and outside margins and the gaps between positions.
   ; All values multiplied by "unit" to achieve device-independance.
   xomargin = oxmargin * unit
   yomargin = oymargin * unit
   ximargin = ixmargin * unit / xsize
   yimargin = iymargin * unit / ysize
   gapx = xgap * unit
   gapy = ygap * unit
   
    ; Debugging code. Left in for users to take advantage of.
;   print, 'XOMargin:', xomargin
;   print, 'YOMargin:', yomargin
;   print, 'XIMargin:', ximargin
;   print, 'YIMargin:', yimargin
;   print, 'GapX:', gapx
;   print, 'GapY:', gapy
;   print, 'Unit:', unit
;   
   ; Calculate the window or drawing area inside the graphics window.
   winarea = [ xomargin[0], yomargin[0], xsize - xomargin[1], ysize - yomargin[1] ]   
   
   ; Calculate the plot width and height.
   plot_width  = (winarea[2] - winarea[0] - gapx*(ncols-1)) / ncols
   plot_height = (winarea[3] - winarea[1] - gapy*(nrows-1)) / nrows
   
   ; Calculate the plot areas inside the drawing area.
   plot_areas = FltArr(4, ncols, nrows)
   FOR j=0,nrows-1 DO BEGIN
      FOR k=0,ncols-1 DO BEGIN
         plot_areas[0,k,j] = winarea[0] + (plot_width + gapx) * k  ; x0
         plot_areas[2,k,j] = plot_areas[0,k,j] + plot_width        ; x1
         plot_areas[1,k,j] = winarea[3] - (plot_height + gapy) * j ; y0
         plot_areas[3,k,j] = plot_areas[1,k,j] - plot_height       ; y1
      ENDFOR
   ENDFOR
   
   ; Normalize the plot areas.
   plot_areas[[0,2],*,*] = plot_areas[[0,2],*,*] / xsize
   plot_areas[[1,3],*,*] = plot_areas[[1,3],*,*] / ysize
   
   ; Calculate the plot positions. These are the plot areas with the
   ; inside margins subtracted.
   positions = FltArr(4, ncols, nrows)
   positions[0,*,*] = plot_areas[0,*,*] + ximargin[0]
   positions[2,*,*] = plot_areas[2,*,*] - ximargin[1]
   positions[3,*,*] = plot_areas[1,*,*] + yimargin[0]
   positions[1,*,*] = plot_areas[3,*,*] - yimargin[1]
   
   ; Reform the positions into a 4 by ncols*rows array.   
   positions = Reform(positions, 4, ncols*nrows)
   
   ; Did the user ask for an aspect ratio?
   IF N_Elements(aspect) NE 0 THEN BEGIN
   
      ; Make sure aspect is not 0.
      IF aspect[0] EQ 0 THEN Message, 'The aspect ratio cannot be zero.'
   
      ; Calculate the same aspect ratio for each of the positions.
      FOR j=0,ncols*nrows-1 DO BEGIN
      
          p = positions[*,j]
          xpixSize = (p[2] - p[0]) * xsize
          ypixSize = (p[3] - p[1]) * ysize
          ratio = aspect[0]
          
          ; Try to fit the width. If you can't maintain
          ; the aspect ratio, fit the height.
          trialX = xpixSize
          trialY = trialX * ratio
          IF trialY GT ypixSize THEN BEGIN
             trialY = ypixSize
             trialX = trialY / ratio
          ENDIF
          
          ; Recalculate the position of the plot in the window.
          IF xsize GE ysize THEN BEGIN
              p[0] = (((xpixSize - trialX) / 2.0) / xsize) + p[0]
              p[2] = p[0] + (trialX/Float(xsize))
              p[1] = (((ypixSize - trialY) / 2.0) / ysize)  + p[1]
              p[3] = p[1] + (trialY/Float(ysize))
          ENDIF 
          
          positions[*,j] = p
        ENDFOR
   ENDIF
   
   ; If you have a layout index use that to return a specific
   ; position. Otherwise, return all the positions calculated
   ; for the window.
   IF N_Elements(layoutIndex) EQ 0 THEN BEGIN
      RETURN, positions
   ENDIF ELSE BEGIN
      RETURN, positions[*,layoutIndex]
   ENDELSE
   
END