;+
; NAME:
;	GetColors
; PURPOSE:
;	Convert array of function values to equivalent color indexes.
;	Optionally plot a legend.
; CALLING SEQUENCE:
	FUNCTION GetColors, Value, BreakVal, BreakName, BreakPos,	$
		open		= open		, $
		legend		= legend	, $
		rimlegend	= rimlegend	, $
		format		= format	, $
		ctable		= ctable	, $
		cpart		= cpart		, $
		usedcolors	= usedcolors, $
		badforeground=badforeground, $
		badbackground=badbackground, $
		charsize	= charsize	, $
		flip		= flip		, $
		noedge		= noedge	, $
		ncolors		= ncolors	, $
		strictlabels= strictlabels, $
		steplabels	= steplabels, $
		labelrange	= labelrange, $
		logscale	= logscale	, $
		_extra		= _extra
; INPUTS:
;	Value				array; type: any
;							function values
;	BreakVal			array[n]; type: any
;							function values between adjacent colors
;
;	Only used if /legend is set:
;
;	BreakName			array[m]; type: any
;							labels along legend; usually a subset
;							of BreakVal. See PROCEDURE.
;	BreakPos			array[m];
;							position for BreakName labels along legend
;							See PROCEDURE:
; OPTIONAL INPUT PARAMETERS:
;	/legend				if set, a legend is plotted at the left
;							edge of the screen
;	steplabels=steplabels
;						scalar; type: integer; default: 1+ncolors/8
;							SEE PROCEDURE
;	rimlegend=rimlegend scalar; type: any; default: 0
;							sets the thickness of the bounding box around
;							the legend
;
;	/strictlabels		
;	format=format		scalar; type: string
;							format used to label the legend (only used if
;							BreakVal is a float array)
;	ctable=ctable		scalar; type: integer
;							used to load a color table with LOADCT
;							(only if !d.n_colors=16)
;	cpart=cpart 		array[2]; type: float
;							fractions of one; limits the range of color indices
;							used to cpart*(!d.n_colors-1)
;	/noedge 			avoids using color indices 0 and !d.n_colors-1
;							this overrides the cpart setting.
;							(these sometimes are set to black and white and
;							do not fit in with the rest of the color table).
;
;	_extra=_extra
;	/badforeground		if set then bad values (detected with the
;							finite function) are set to the foreground
;							color (!p.color)
;	/badbackground		if set then bad values are set to the foreground
;							color (!p.background)
;	/flip				uses color table in reverse order, i.e. with increasing
;							fnc value corresponding to decreasing color index
;	labelrange=labelrange
;						array[2]; type: float; default: [0.0,1.0]
;							determines range in normal coordinates used to
;							plot the labels along the legend.
; OUTPUTS:
;	Result				array; type: byte
;							array with color indices (same dimensions as Value)
; INCLUDE:
	@compile_opt.pro						; On error, return to caller
; CALLS:
;	IsType, gridgen
; SIDE EFFECTS:
;	The BreakVal array is sorted
; RESTRICTIONS:
;	#break values = n_elements(BreakVal) must be less than
;	#colors = !d.n_colors
; PROCEDURE:
;	The BreakVal array with N = n_elements(BreakVal) elements
;	divides the data range into N+1 interval (the 1st interval
;	covers data values below BreakVal[0], the last interval
;	covers data value above BreakVal[N-1]. A data value is
;	matched to a color index depending on the interval it
;	belongs to.
;
;	The # colors needed is 1+N (N=n_elements(BreakVal).
;	The 1+N color indices are calculated as
;		Col = round( (!d.n_colors-1.)/nBreak*indgen(nCols) )
;	i.e. as nearly evenly spaced over the full range 0,!d.n_colors-1
;	[Col[0]=0, Col[nBreak]=!d.n_colors-1]
;
;	The legend is a vertical color bar (color 0 to nBreak
;	from bottom to top) at the left side of the screen.
;	The BreakVal values are plotted at the borders between
;	adjacent colors. A maximum of 8 evenly spaced numbers
;	is plotted.
;
;	By default the labeling of the legend is determined
;	internally from the BreakVal array. This works well if
;	the BreakVal array contains a large number of values
;	(usually !d.n_colors).
;	For short arrays of BreakVal the labelling is better
;	controlled using a couple of arguments
;
;	BreakName explicitly specifies how the legend needs to
;	be labelled. Usually these are numbers. The numbers then
;	also define the position of the label. If BreakName
;	contains custom labels (e.g. string labels instead of
;	numbers), then the position of the label can be
;	specified as BreakPos.
;
;	If /strictlabels is set then BreakName = BreakPos = BreakVal
;	is assumed, and labels are plotted every
;	'steplabels' value of BreakVal
;	If BreakName is set then /strictlabels is ignored;
;	This is how GetColors worked originally;
;	the drawback is that the values plotted along
;	the legend are usually not nicely rounded values.
; MODIFICATION HISTORY:
;	SEP-1992, Paul Hick (UCSD/CASS)
;	APR-1993, Paul Hick (UCSD/CASS)
;		removed restrictions to !d.n_colors=8 and 16
;	AUG-1999, Paul Hick (UCSD/CASS)
;		added a check for bad values using the 'finite' function.
;		Corresponding entries in the Color output array are now set
;		to -1 (these will be ignored by href=ColorSkybox=).
;	SEP-1999, Paul Hick (UCSD/CASS)
;		added badforeground and badbackground options
;		added /open keyword
;	SEP-2003, Paul Hick (UCSD/CASS)
;		added cpart keyword
;	OCT-2003, Paul Hick (UCSD/CASS)
;		added /flip and /noedge
;	MAY-2006, Paul Hick (UCSD/CASS)
;		Added /rimlegend
;	JUN-2008, Paul Hick (UCSD/CASS)
;		Added /strictlabel, BreakPos. Added code to improve
;		labeling of legend.
;	JAN-2009, Paul Hick (UCSD/CASS)
;		Added keyword labelrange. Improved positioning of labels
;		along legend.
;	DEC-2009, Paul Hick (UCSD/CASS; pphick@ucsd.edu)
;		Added keyword /logscale.
;		Removed restriction on equally spaced BreakVal array.
;-

InitVar, badforeground	, /key
InitVar, badbackground	, /key
InitVar, open			, /key
InitVar, legend 		, /key
InitVar, flip			, /key
InitVar, noedge 		, /key
InitVar, strictlabels	, /key
InitVar, ncolors		, !d.n_colors
InitVar, logscale		, /key

CASE noedge OF
0: InitVar, cpart, [0,1]
1: cpart = [1.0,ncolors-2.0]/(ncolors-1.0)
ENDCASE

BreakValSave = BreakVal

nBreak = n_elements(BreakVal)	; # break values between colors
IF logscale THEN BEGIN
	i = where(BreakVal GT 0, nBreak)
	IF nBreak GT 0 THEN BreakVal = BreakVal[i]
ENDIF

IF nBreak GE ncolors THEN message,	$
	'#breaks ('+strcompress(nBreak,/remove_all)+') must be less then #colors ('$
	+strcompress(ncolors,/remove_all)+')'

BreakVal = BreakVal[sort(BreakVal)]
nCols = nBreak+1							; # colors to be used
range = cpart*(ncolors-1.0)

Col = open ? (gridgen(nCols+2,range=range))[1:nCols] : gridgen(nCols,range=range)	; Color index array
Col = round(Col)
IF flip THEN Col = reverse(Col)

bad = where(1B-finite(Value))			; Locate bad values

IF bad[0] NE -1 THEN BEGIN
	badv = Value[bad]					; Save all bad values

	; Set bad values to minimum value. If everything is bad use the lowest break value
	
	ValueMin = min(Value, /nan)			; Pick up minimum value
	IF NOT finite(ValueMin) THEN ValueMin = BreakVal[0]

	Value[bad] = ValueMin				; Replace bad values by minimum value
ENDIF

; Set up the colors array by assigning colors to all elements in between
; successive break values.
 
Colors = Col[0]+long(0*Value)			; Set everything to first color

FOR I=1,nBreak-1 DO BEGIN
	A = where ( BreakVal[I-1] LT Value AND Value LE BreakVal[I] , N )
	IF N NE 0 THEN Colors[A] = Col[I]
ENDFOR
										; Set everything above the last break to last color
A = where ( Value GT BreakVal[nBreak-1] , N )
IF N NE 0 then Colors[A] = Col[nBreak]

IF bad[0] NE -1 THEN BEGIN
	Colors[bad] = badbackground ? !p.background : badforeground ? !p.color : -1
	Value [bad] = badv
ENDIF

; Load color table if specified

IF IsType(ctable, /defined) THEN loadct, ctable[0]

usedcolors = Colors[ uniq(Colors) ]

IF legend THEN BEGIN					; Draw vertical legend at left side of screen

	Dx     = 0.015						; Horizontal size of box for each color
	Xleft  = 0.01						; Left side of legend
	Xright = Xleft+Dx 					; Right side of legend
	DyOff   = 0.02						; Open space above and below legend

	LegendBot = DyOff					; Bottom of legend
	LegendTop = 1.0-DyOff				; Top of legend
	LegendLen = LegendTop-LegendBot		; Length of legend

	frst_pos = LegendBot+LegendLen/nCols; Corresponds to frst_break
	last_pos = LegendTop-LegendLen/nCols; Corresponds to last_break

	IF logscale THEN BreakVal = alog10(BreakVal)

	frst_break = BreakVal[0]
	last_break = BreakVal[nBreak-1]
										; Normal units per data units
	pos_scale = (last_pos-frst_pos)/float(last_break-frst_break)
										; Positions of breaks in normal coordinates
	pos_break = frst_pos+pos_scale*(BreakVal-frst_break)

	; Loop from bottom to top of screen

	; Use first color below frst_break

	polyfill, color=Col[0], /normal,				$
		[Xleft    , Xleft  ,Xright  ,Xright   ],	$
		[LegendBot,frst_pos,frst_pos,LegendBot]

	; Size of box for each color is proportional
	; to the data units in BreakVal

	FOR i=1,nBreak-1 DO	$
		polyfill, color=Col[i], /normal,		$
			[Xleft,Xleft,Xright,Xright],		$
			pos_break[ i+[-1,0,0,-1] ]

	; Use last color above last_break

	polyfill, color=Col[nBreak], /normal,			$
		[Xleft   ,Xleft    ,Xright   ,Xright  ],	$
		[last_pos,LegendTop,LegendTop,last_pos]

	; Plot rim conterclockwise around legend

	InitVar, rimlegend, 0
	IF rimlegend NE 0 THEN BEGIN
		plots, /normal, thick=rimlegend,			$
			[Xleft    ,Xright   ,Xright   ,Xleft    ,Xleft    ],	$
			[LegendBot,LegendBot,LegendTop,LegendTop,LegendBot],color=!d.n_colors-1
	ENDIF

	InitVar, steplabels, 1+nCols/8
	steplabels >= 1

	CASE 1 OF

	IsType(BreakName,/defined): BEGIN

		; Labelling with explicitly specified labels BreakName
		; The values also specify the location of the label,
		; BreakPos, unless these also are specified.

		; (if BreakPos is not specified, then BreakName must contain labels
		; specified in the same units as BreakVal)

		InitVar, BreakPos, BreakName
		IF logscale THEN BreakPos = alog10(BreakPos)

	END

	strictlabels: BEGIN

		BreakName = BreakVal
		BreakPos  = BreakVal
		IF steplabels GT 1 THEN BEGIN
			i = steplabels/2+steplabels*indgen(nCols/steplabels)
			i = i[where(i LT nBreak)]
			BreakName = BreakName[i]
			BreakPos  = BreakPos [i]
		ENDIF

		IF logscale THEN BreakName = 10^BreakName
	END

	logscale: BEGIN			; Auto-labeling for log scale

		nn_break = ceil(nCols/steplabels)	; # labels desired
		nn_dec   = (last_break-frst_break) > 1	; # decades
		nn_break = ceil(nn_break/nn_dec) > 1	; # labels per decade

		nfrst = floor(frst_break)
		nlast = ceil (last_break)
		CASE 1 OF
		nn_break LE 1: BreakName = 10L^gridgen(range=[nfrst,nlast])
		nn_break LE 2: BreakName = [1,5]#(10L^gridgen(range=[nfrst,nlast]))
		nn_break LE 3: BreakName = [1,2,5]#(10L^gridgen(range=[nfrst,nlast]))
		nn_break LE 5: BreakName = [1,2,4,7]#(10L^gridgen(range=[nfrst,nlast]))
		nn_break LE 8: BreakName = [1,2,4,6,8]#(10L^gridgen(range=[nfrst,nlast]))
		nn_break LE 9: BreakName = [1,2,3,4,5,6,7,8,9]#(10L^gridgen(range=[nfrst,nlast]))
		ELSE	     : BreakName = 10^BreakVal
		ENDCASE

		BreakName = reform(Breakname,n_elements(BreakName))
		BreakPos  = alog10(BreakName)

		i = where(frst_break LE BreakPos AND BreakPos LE last_break)
		IF i[0] NE -1 THEN BEGIN
			BreakName = BreakName[i]
			BreakPos  = BreakPos [i]
		ENDIF

	END

	ELSE: BEGIN			; Auto-labeling for linear scale

		nn_break = ceil(nCols/steplabels)
		dd_break = anicenum(float(last_break-frst_break)/nn_break)

		CASE frst_break*last_break LE 0 OF
		0: BEGIN
			upper = frst_break GT last_break
			lo = anicenum(frst_break,upper=  upper)
			hi = anicenum(last_break,upper=1-upper)
			BreakName = (2*(hi GT 0)-1)*(abs(lo) > abs(hi))
		END
		1: BreakName = 0.0
		ENDCASE

		nn_break *= 4
		BreakName += gridgen(2*nn_break+1,range=dd_break*nn_break*[-1,1])

		i = [frst_break,last_break]
		i = where(min(i) LE BreakName AND BreakName LE max(i))
		IF i[0] NE -1 THEN BEGIN
			BreakName = BreakName[i]
			IF last_break LT frst_break THEN BreakName = reverse(BreakName)
		ENDIF ELSE	$
			BreakName = BreakVal

		BreakPos = BreakName

	END

	ENDCASE

	; If the break values are integers explicitly convert
	; to integer type

	IF IsType(BreakName, /generic_float) THEN BEGIN
		i = round(BreakName)
		IF (where(BreakName NE i))[0] EQ -1 THEN BreakName = i
	ENDIF

	; Convert BreakName to string array. If BreakName is
	; of type float then the specified FORMAT is used in
	; the conversion

	CASE 1 OF
	IsType(BreakName, /generic_int  ): BreakLabel = strcompress(BreakName, /remove_all)
	IsType(BreakName, /generic_float): BEGIN
		InitVar, format, '(F10.3)'
		BreakLabel = strcompress(string(BreakName,format=format), /remove_all)
	END
	ELSE: BreakLabel = BreakName
	ENDCASE

	InitVar, charsize, 1.0
	InitVar, labelrange, [0.0,0.95]

	pos_break = frst_pos+pos_scale*(BreakPos-frst_break)
	FOR i=0,n_elements(BreakName)-1 DO BEGIN
		IF labelrange[0] LT pos_break[i] AND pos_break[i] LT labelrange[1] THEN	$
			xyouts, Xright+0.75*Dx, pos_break[i],/normal, BreakLabel[i],		$
			charsize=charsize, _extra=_extra
		plots, /normal, Xright+[0.0,0.4]*Dx, pos_break[i]*[1,1], _extra=_extra
		plots, /normal, Xleft -[0.0,0.4]*Dx, pos_break[i]*[1,1], _extra=_extra
	ENDFOR

ENDIF

BreakVal = BreakValSave

RETURN, Colors  &  END
