Viewing contents of file '../idllib/contrib/groupk/legend2.pro'
;+
; Name:
;       LEGEND2
; Purpose:
;   This procedure makes a legend for a plot.  The legend can contain
;   a mixture of symbols, linestyles, Hershey characters (vectorfont),
;   and filled polygons (usersym).  A test procedure, legendtest.pro,
;   shows legend's capabilities.  Placement of the legend is controlled
;   with keywords like /right, /top, and /center or by using a position
;   keyword for exact placement (position=[x,y]) or via mouse (/position).
; Examples:
;   The call:
;        legend,['Plus sign','Asterisk','Period'],psym=[1,2,3]
;     produces:
;        -----------------
;        |         |
;        |  + Plus sign |
;        |  * Asterisk  |
;        |  . Period    |
;        |         |
;        -----------------
;     Each symbol is drawn with a plots command, so they look OK.
;     Other examples are given in optional output keywords.
;
;   lines = indgen(6)             ; for line styles
;   items = 'linestyle '+strtrim(lines,2)   ; annotations
;   legend,items,linestyle=lines       ; vertical legend---upper left
;   items = ['Plus sign','Asterisk','Period']
;   sym = [1,2,3]
;   legend,items,psym=sym              ; ditto except using symbols
;   legend,items,psym=sym,/horizontal  ; horizontal format
;   legend,items,psym=sym,box=0        ; sans border
;   legend,items,psym=sym,delimiter='='     ; embed '=' betw psym & text
;   legend,items,psym=sym,margin=2          ; 2-character margin
;   legend,items,psym=sym,position=[x,y]    ; upper left in data coords
;   legend,items,psym=sym,pos=[x,y],/norm   ; upper left in normal coords
;   legend,items,psym=sym,pos=[x,y],/device ; upper left in device coords
;   legend,items,psym=sym,/position         ; interactive position
;   legend,items,psym=sym,/right       ; at upper right
;   legend,items,psym=sym,/bottom      ; at lower left
;   legend,items,psym=sym,/center      ; approximately near center
;   legend,items,psym=sym,number=2          ; plot two symbols, not one
;   legend,items,/fill,psym=[8,8,8],colors=[10,20,30]; 3 filled squares
; Usage:
;   legend[,items][,keyword options]
; Inputs:
;   items = text for the items in the legend, a string array.
;        For example, items = ['diamond','asterisk','square'].
;        You can omit items if you don't want any text labels.
; Optional Input Keywords:
;   linestyle = array of linestyle numbers  If linestyle(i) < 0, then omit
;        ith symbol or line to allow a multi-line entry.
;   psym = array of plot symbol numbers.  If psym(i) is negative, then a
;        line connects pts for ith item.  If psym(i) = 8, then the
;        procedure usersym is called with vertices define in the
;        keyword usersym. PSYM(i) = 8 corresponds to a circle symbol.
;   vectorfont = vector-drawn characters for the sym/line column, e.g.,
;        ['!9B!3','!9C!3','!9D!3'] produces an open square, a checkmark,
;        and a partial derivative, which might have accompanying items
;        ['BOX','CHECK','PARTIAL DERIVATIVE'].
;        There is no check that !p.font is set properly, e.g., -1 for
;        X and 0 for PostScript.  This can produce an error, e.g., use
;        !20 with PostScript and !p.font=0, but allows use of Hershey
;        *AND* PostScript fonts together.
;   N. B.: Choose any of linestyle, psym, and/or vectorfont.  If none is
;        present, only the text is output.  If more than one
;        is present, all need the
;        same number of elements, and normal plot behaviour occurs.
;        By default, if psym is positive, you get one point so there is
;        no connecting line.  If vectorfont(i) = '',
;        then plots is called to make a symbol or a line, but if
;        vectorfont(i) is a non-null string, then xyouts is called.
;   /help = flag to print header
;   /horizontal = flag to make the legend horizontal
;   /vertical = flag to make the legend vertical (D=vertical)
;   box = flag to include/omit box around the legend (D=include)
;   delimiter = embedded character(s) between symbol and text (D=none)
;   colors = array of colors for plot symbols/lines (D=!color)
;   textcolors = array of colors for text (D=!color)
;   margin = margin around text measured in characters and lines
;   spacing = line spacing (D=bit more than character height)
;   pspacing = psym spacing (D=3 characters)
;   charsize = just like !p.charsize for plot labels
;   position = data coordinates of the upper left of the legend
;   normal = use normal coordinates for position, not data
;   device = use device coordinates for position, not data
;   number = number of plot symbols to plot or length of line (D=1)
;   usersym = 2-D array of vertices, cf. usersym in IDL manual. (D=square)
;   /fill = flag to fill the usersym or PSYM=5,6 or 9
;   /left = flag to place legend snug against left side of plot window (D)
;   /right = flag to place legend snug against right side of plot window
;        If /right,pos=[x,y], then x is position of RHS and text
;        runs right-to-left.
;   /top = flag to place legend snug against top of plot window (D)
;   /bottom = flag to place legend snug against bottom of plot window
;        /top,pos=[x,y] and /bottom,pos=[x,y] produce same positions.
; Outputs:
;   legend to current plot device
; Optional Output Keywords:
;   corners = 4-element array, like !p.position, of the normalized
;     coords for the box (even if box=0): [llx,lly,urx,ury].
;     Useful for multi-column or multi-line legends, for example,
;     to make a 2-column legend, you might do the following:
;       c1_items = ['diamond','asterisk','square']
;       c1_psym = [4,2,6]
;       c2_items = ['solid','dashed','dotted']
;       c2_line = [0,2,1]
;       legend,c1_items,psym=c1_psym,corners=c1,box=0
;       legend,c2_items,line=c2_line,corners=c2,box=0,pos=[c1(2),c1(3)]
;       c = [c1(0)<c2(0),c1(1)<c2(1),c1(2)>c2(2),c1(3)>c2(3)]
;       plots,[c(0),c(0),c(2),c(2),c(0)],[c(1),c(3),c(3),c(1),c(1)],/norm
;     Useful also to place the legend.  Here's an automatic way to place
;     the legend in the lower right corner.  The difficulty is that the
;     legend's width is unknown until it is plotted.  In this example,
;     the legend is plotted twice: the first time in the upper left, the
;     second time in the lower right.
;       legend,['1','22','333','4444'],linestyle=indgen(4),corners=corners
;             ; BOGUS LEGEND---FIRST TIME TO REPORT CORNERS
;       xydims = [corners(2)-corners(0),corners(3)-corners(1)]
;             ; SAVE WIDTH AND HEIGHT
;       chdim=[!d.x_ch_size/float(!d.x_size),!d.y_ch_size/float(!d.y_size)]
;             ; DIMENSIONS OF ONE CHARACTER IN NORMALIZED COORDS
;       pos = [!x.window(1)-chdim(0)-xydims(0) $
;             ,!y.window(0)+chdim(1)+xydims(1)]
;             ; CALCULATE POSITION FOR LOWER RIGHT
;       plot,findgen(10)     ; SIMPLE PLOT; YOU DO WHATEVER YOU WANT HERE.
;       legend,['1','22','333','4444'],linestyle=indgen(4),pos=pos
;             ; REDO THE LEGEND IN LOWER RIGHT CORNER
;     You can modify the pos calculation to place the legend where you
;     want.  For example to place it in the upper right:
;       pos = [!x.window(1)-chdim(0)-xydims(0),!y.window(1)-xydims(1)]
; Common blocks:
;   none
; Procedure:
;   If keyword help is set, call doc_library to print header.
;   See notes in the code.  Much of the code deals with placement of the
;   legend.  The main problem with placement is not being
;   able to sense the length of a string before it is output.  Some crude
;   approximations are used for centering.
; Restrictions:
;   Here are some things that aren't implemented.
;   - Data and device coordinate specification of position (done 17 jan 94)
;   - An orientation keyword would allow lines at angles in the legend.
;   - An array of usersyms would be nice---simple change.
;   - An order option to interchange symbols and text might be nice.
;   - Somebody might like double boxes, e.g., with box = 2.
;   - Another feature might be a continuous bar with ticks and text.
;   - There are no guards to avoid writing outside the plot area.
;   - There is no provision for multi-line text, e.g., '1st line!c2nd line'
;     Sensing !c would be easy, but !c isn't implemented for PostScript.
;     A better way might be to simply output the 2nd line as another item
;     but without any accompanying symbol or linestyle.  A flag to omit
;     the symbol and linestyle is linestyle(i) = -1.
;   - There is no ability to make a title line containing any of titles
;     for the legend, for the symbols, or for the text.
;   - It might be nice to force the legend to be placed at hardwired
;     locations in the plot, e.g., with keywords like /left/bottom for
;     lower left.  Allowing this requires knowing the width of the text
;     before it is printed, which is difficult. (done on 18 Jun 93)
; Side Effects:
; Modification history:
;   write, 24-25 Aug 92, F K Knight (knight@ll.mit.edu)
;   allow omission of items or omission of both psym and linestyle, add
;     corners keyword to facilitate multi-column legends, improve place-
;     ment of symbols and text, add guards for unequal size, 26 Aug 92, FKK
;   add linestyle(i)=-1 to suppress a single symbol/line, 27 Aug 92, FKK
;   add keyword vectorfont to allow characters in the sym/line column,
;     28 Aug 92, FKK
;   add /top, /bottom, /left, /right keywords for automatic placement at
;     the four corners of the plot window.  The /right keyword forces
;     right-to-left printing of menu. 18 Jun 93, FKK
;   change default position to data coords and add normal, data, and
;     device keywords, 17 Jan 94, FKK
;   add /center keyword for positioning, but it is not precise because
;     text string lengths cannot be known in advance, 17 Jan 94, FKK
;   add interactive positioning with /position keyword, 17 Jan 94, FKK
;   allow a legend with just text, no plotting symbols.  This helps in
;     simply describing a plot or writing assumptions done, 4 Feb 94, FKK
;   18-JAN-1996    Added option to use FILL keyword on PSYM = 5 (triangle),
;                  6 (square) and 9 (circle).  Added PSYM=9 (circle). HCW.
;-
pro legend2,help=help,items,linestyle=linestyle,psym=psym2,vectorfont=vectorfont $
  ,horizontal=horizontal,vertical=vertical,box=box,margin=margin $
  ,delimiter=delimiter,spacing=spacing,charsize=charsize,pspacing=pspacing $
  ,position=position,number=number,colors=colors,textcolors=textcolors $
  ,fill=fill,usersym=usersym,corners=corners $
  ,left=left,right=right,top=top,bottom=bottom,center=center $
  ,data=data,normal=normal,device=device
;
;   =====>> HELP
;
on_error,2
if keyword_set(help) then begin & doc_library,'legend' & return & endif
;
;   =====>> SET DEFAULTS FOR SYMBOLS, LINESTYLES, AND ITEMS.
;
psym = psym2
ni = n_elements(items)
np = n_elements(psym)
nl = n_elements(linestyle)
nv = n_elements(vectorfont)
nlpv = max([np,nl,nv])
n = max([ni,np,nl,nv])                      ; NUMBER OF ENTRIES
strn = strtrim(n,2)                         ; FOR ERROR MESSAGES
if n eq 0 then message,'No inputs!  For help, type legend,/help.'
if ni eq 0 then begin
  items = replicate('',n)                   ; DEFAULT BLANK ARRAY
endif else begin
  szt = size(items)
  if (szt(szt(0)+1) ne 7) then message,'First parameter must be a string array.  For help, type legend,/help.'
  if ni ne n then message,'Must have number of items equal to '+strn
endelse
symline = (np ne 0) or (nl ne 0)            ; FLAG TO PLOT SYM/LINE
if (np ne 0) and (np ne n) then message,'Must have 0 or '+strn+' elements in psym array.'
if (nl ne 0) and (nl ne n) then message,'Must have 0 or '+strn+' elements in linestyle array.'
if n_elements(linestyle) ne n then linestyle = intarr(n); D=SOLID
if n_elements(psym) ne n then psym = intarr(n)        ; D=SOLID
if n_elements(vectorfont) ne n then vectorfont = replicate('',n)
;
;   =====>> CHOOSE VERTICAL OR HORIZONTAL ORIENTATION.
;
if n_elements(horizontal) eq 0 then begin        ; D=VERTICAL
  if n_elements(vertical) eq 0 then vertical = 1
endif else begin
  if n_elements(vertical) eq 0 then vertical = not horizontal
endelse
;
;   =====>> SET DEFAULTS FOR OTHER OPTIONS.
;
if n_elements(box) eq 0 then box = 1
if n_elements(margin) eq 0 then margin = 0.5
if n_elements(delimiter) eq 0 then delimiter = ''
if n_elements(charsize) eq 0 then charsize = !p.charsize
if charsize eq 0 then charsize = 1
if n_elements(number) eq 0 then number = 1
if n_elements(colors) eq 0 then colors = !color + intarr(n)
if n_elements(textcolors) eq 0 then textcolors = !color + intarr(n)
fill = keyword_set(fill)
if n_elements(usersym) eq 0 then usersym = 2*[[0,0],[0,1],[1,1],[1,0]]-1
;
;   =====>> INITIALIZE SPACING
;
if n_elements(spacing) eq 0 then spacing = 1.2
if n_elements(pspacing) eq 0 then pspacing = 3
xspacing = !d.x_ch_size/float(!d.x_size) * (spacing > charsize)
yspacing = !d.y_ch_size/float(!d.y_size) * (spacing > charsize)
;stop
ltor = 1                     ; flag for left-to-right
if n_elements(left) eq 1 then ltor = left eq 1
if n_elements(right) eq 1 then ltor = right ne 1
ttob = 1                     ; flag for top-to-bottom
if n_elements(top) eq 1 then ttob = top eq 1
if n_elements(bottom) eq 1 then ttob = bottom ne 1
xalign = ltor ne 1                ; x alignment: 1 or 0
yalign = -0.5*ttob + 1                 ; y alignment: 0.5 or 1
xsign = 2*ltor - 1                ; xspacing direction: 1 or -1
ysign = 2*ttob - 1                ; yspacing direction: 1 or -1
if not ttob then yspacing = -yspacing
if not ltor then xspacing = -xspacing
;
;   =====>> INITIALIZE POSITIONS: FIRST CALCULATE X OFFSET FOR TEXT
;
xt = 0
if nlpv gt 0 then begin                ; SKIP IF TEXT ITEMS ONLY.
if vertical then begin                 ; CALC OFFSET FOR TEXT START
  for i = 0,n-1 do begin
    if (psym(i) eq 0) and (vectorfont(i) eq '') then num = (number + 1) > 3 else num = number
    if psym(i) lt 0 then num = number > 2   ; TO SHOW CONNECTING LINE
    if psym(i) eq 0 then expand = 1 else expand = 2
    thisxt = (expand*pspacing*(num-1)*xspacing)
    if ltor then xt = thisxt > xt else xt = thisxt < xt
    endfor
endif    ; NOW xt IS AN X OFFSET TO ALIGN ALL TEXT ENTRIES.
endif
;
;   =====>> INITIALIZE POSITIONS: SECOND LOCATE BORDER
;
if !x.window(0) eq !x.window(1) then begin
  plot,/nodata,xstyle=4,ystyle=4,[0],/noerase
endif
;   next line takes care of weirdness with small windows
pos = [min(!x.window),min(!y.window),max(!x.window),max(!y.window)]
case n_elements(position) of
 0: begin
  if ltor then px = pos(0) else px = pos(2)
  if ttob then py = pos(3) else py = pos(1)
  if keyword_set(center) then begin
    if not keyword_set(right) and not keyword_set(left) then $
      px = (pos(0) + pos(2))/2. - xt
    if not keyword_set(top) and not keyword_set(bottom) then $
      py = (pos(1) + pos(3))/2. + n*yspacing
    endif
  position = [px,py] + [xspacing,-yspacing]
  end
 1: begin     ; interactive
  message,/inform,'Place mouse at upper left corner and click any mouse button.'
  cursor,x,y,/normal
  position = [x,y]
  end
 2: begin     ; convert upper left corner to normal coordinates
  if keyword_set(data) then $
    position = convert_coord(position,/to_norm) $
  else if keyword_set(device) then $
    position = convert_coord(position,/to_norm,/device) $
  else if not keyword_set(normal) then $
    position = convert_coord(position,/to_norm)
  end
 else: message,'Position keyword can have 0, 1, or 2 elements only. Try legend,/help.'
endcase
yoff = 0.25*yspacing*ysign             ; VERT. OFFSET FOR SYM/LINE.
if ltor then xend = 0 else xend = 1         ; SAVED WIDTH FOR DRAWING BOX
x0 = position(0) + (margin)*xspacing        ; INITIAL X & Y POSITIONS
y0 = position(1) - margin*yspacing + yalign*yspacing  ; WELL, THIS WORKS!
y = y0                            ; STARTING X & Y POSITIONS
x = x0
;
;   =====>> OUTPUT TEXT FOR LEGEND, ITEM BY ITEM.
;   =====>> FOR EACH ITEM, PLACE SYM/LINE, THEN DELIMITER,
;   =====>> THEN TEXT---UPDATING X & Y POSITIONS EACH TIME.
;   =====>> THERE ARE A NUMBER OF EXCEPTIONS DONE WITH IF STATEMENTS.
;
;stop
if ttob then ii = [0,n-1,1] else ii = [n-1,0,-1]
for i = ii(0),ii(1),ii(2) do begin
  if vertical then x = x0 else y = y0       ; RESET EITHER X OR Y
  x = x + xspacing                ; UPDATE X & Y POSITIONS
  y = y - yspacing
  if nlpv eq 0 then goto,TEXT_ONLY          ; FLAG FOR TEXT ONLY
  if (psym(i) eq 0) and (vectorfont(i) eq '') then num = (number + 1) > 3 else num = number
  if psym(i) lt 0 then num = number > 2          ; TO SHOW CONNECTING LINE
  if psym(i) eq 0 then expand = 1 else expand = 2
  xp = x + expand*pspacing*indgen(num)*xspacing
  if (psym(i) gt 0) and (num eq 1) and vertical then xp = x + xt/2.
  yp = y + intarr(num)
  if vectorfont(i) eq '' then yp = yp + yoff
  if psym(i) eq 0 then begin
    xp = [min(xp),max(xp)]             ; TO EXPOSE LINESTYLES
    yp = [min(yp),max(yp)]             ; DITTO
    endif
  if psym(i) eq 8 then usersym,usersym,fill=fill,color=colors(i)
  if (psym(i) eq 5) and (keyword_set(fill)) then begin
     PLOTSYM2, 4, /FILL
     psym(i) = 8
  endif
  if (psym(i) eq 6) and (keyword_set(fill)) then begin
     PLOTSYM2, 8, /FILL
     psym(i) = 8
  endif
  if (psym(i) eq 9) then begin
     PLOTSYM2, 0, FILL=Fill
     psym(i) = 8
  endif
  if vectorfont(i) ne '' then begin
;    if (num eq 1) and vertical then xp = x + xt/2    ; IF 1, CENTERED.
    xyouts,xp,yp,vectorfont(i),width=width,color=colors(i) $
      ,size=charsize,align=xalign,/norm
    xt = xt > width
    xp = xp + width/2.
  endif else begin
    if symline and (linestyle(i) ge 0) then plots,xp,yp,color=colors(i) $
      ,/normal,linestyle=linestyle(i),psym=psym(i),symsize=charsize
  endelse
;  if i eq ii(1) then stop
  if vertical then x = x + xt else if ltor then x = max(xp) else x = min(xp)
  if symline then x = x + xspacing
  TEXT_ONLY:
  xyouts,x,y,delimiter,width=width,/norm,color=textcolors(i),size=charsize,align=xalign
  x = x + width*xsign
  if width ne 0 then x = x + 0.5*xspacing
  xyouts,x,y,items(i),width=width,/norm,color=textcolors(i),size=charsize,align=xalign
  x = x + width*xsign
  if not vertical and (i lt (n-1)) then x = x+2*xspacing; ADD INTER-ITEM SPACE
  xfinal = (x + xspacing*margin)
  if ltor then xend = xfinal > xend else xend = xfinal < xend   ; UPDATE END X
;  print,i,xfinal,xend
  endfor
;stop
;
;   =====>> OUTPUT BORDER
;
x = position(0)
y = position(1)
if vertical then bottom = n else bottom = 1
ywidth = - (2*margin+bottom-0.5)*yspacing
corners = [x,y+ywidth,xend,y]
if box then plots,[x,xend,xend,x,x],y + [0,0,ywidth,ywidth,0],/norm
return
end