;+
;
; Project   : general display tools
;                   
; Name      : tv_multi
;               
; Purpose   : standalone, used as part of the s3drs GUI
;               
; Explanation: generic display widget for multiple data frames
;               
; Use       : see documentation in code
;    
; Inputs    : (many)
;               
; Outputs   : 
;
; Keywords  : (many)
;               
; Common    : 
;               
; Restrictions: none
;               
; Side effects: 
;               
; Category    : display
;               
; Prev. Hist. : None.
;
; Written     : Sandy Antunes, NRC, March-April 2009
;               
;-            

; simple routine to plot a typical oPixon 'data' render
; Why the name?  Well, we are calling a 2-dimensional image a
; 'data', but data is plural.  And we are often plotting several of
; these. So we are plotting a set of datum.
;
; 'linebreak' lets you specify a new line after the 'linebreak'th data
; element, e.g. if given 6 data frames, linebreak=3 means break after
; the third plotted item to create a new row.  Note that, as usual
; with IDL, new rows are plotted above the earlier elements.
; 'unique' means it should let each row have its own max/min scaling,
; the default alternative uses the same plotting dynamic range for all data.
;
; Warning, note that use of 'congrid' to shrink images will result
; in bad interpolation for large reductions.  The plotting limits
; are derived from the original data (to conserve memory).  Given
; that, e.g. an 128x128 data with dmin=0, dmax=2.5 binned down to
; 16x16 results in an effective dmin=0, dmax<1.0 (but plotted against
; our original dmax=2.5), excessive shrinking will reduce the dynamic
; range of the plots and obscure details.
;
; 'slice' tells this to plot the data values as a plot rather than as
; an image.  The type of slice defaults to 'plot the N/2th row along y',
; but the additional keywords of 'xcol' and/or 'yrow' can be set
; to define the plot.  e.g.  /slice means "plot,data,yrow=N/2",
; /slice,xcol=128 means "plot,data(128,*)", /slice,yrow=128 means
; "plot,data(*,128)". If 'yrow' is defined, 'xcol' is ignored.
; 'decouple' decouples the 'slice' scaling from the plots, so each
;  slice is plotted to its own relative axes.
;
; If the keyword 'histogram' is used, histograms are plotted.
; The histogram uses default binning.  You can override this with
; nbins=nbins.  If you give both 'slice' and 'histogram' then
; 'histogram' is ignored.
;
; If the keyword 'addextra' is used with 'slice' or 'histogram', both the
; image and its slice or histogram plot are plotted, with the value
; plot below each image.  You cannot do slices and histograms together
; because that makes for too crowded a page.
;
; If the keyword '/both' is used, both normal and log plots are
; given, with the normal on top and log on the bottom.  This
; overwrites any 'addextra' or 'histogram' or 'slice' options
; (due to the current design of max 2 plots/item), and also
; ignores (obviously) the /log option.
;
; Optional 'labelset' is a string array to add labels to each image.
; Only really usable if there is the same number of elements as the data.
;
; Optional 'localminmax' turns off global scaling and lets each plot
; use its own extrema
;
; Optional 'oom' lets you set an order of magnitude to plot, taken
; as "plot the data max down to 'oom' orders of magnitude".  For
; example, oom=2 for an image with min=0 max=1e7 would plot only
; the data from 1e5 to 1e7.
;
; Optional 'rsun', if given an rsun in radius-of-sun-in-pixels,
; will add a scale of Rsun units to image plots.
; Optional 'circle' will overlay a plot of the sun at the sun center.
;
; You can use fontsize=N to overrule the default sizing of 7, where
; our fontsize is a multiplier related to 1.0.
;
; Now accepts an array of pointers to 2D images as well as an
; array of 2D images.
;
; If 'tv_multi' creates its own plotting windows (as it does by
; default), it sets retain=2, which means IDL keeps a pixmap
; of the backing so that IDL can redraw the window as needed.
; This can lead to performance issues, especially under Windows,
; if the number of available pixmap windows is limited by the OS.
;
; You can pass a given colortable index as 'ct=', which sets the
; given colortable in the tv_multi window using 'loadct'.
;
; You can force a size either by giving a scaling factor 'rescale=N',
; which samples the image to *rescale, or by giving a fixed pixel
; size 'fixedsize=N', which forces that size.  'fixedsize' overrides
; 'rescale', if both are given.  The default for both is to do no
; resizing/rebinning at all. fixedsize>=0 or no value turns off fixedsizing.
;
; Optional header text and caption (footer) text can be provided
;
; xbuffer is just if you want extra whitespace to the right, such as
; for captions.
; ybuffer is just if you want extra whitespace below, such as
; for captions.
;
; 'los_utc=' breaks our usual generic standard, in this case by making
; a STEREO-specific plot of the fields of view in the lower caption area.
;
; /tellactual has it print the actual data min/max for each plot
; /tellplot has it print the displayed data min/max for each plot
; /showrange is a legacy value, defaults to /tellplot here
;
; /pretty is shorthand to set '/showrange, /inverse, /border, /ps,
; /zb, /land' for making decent postscript plots.
;
; /bitmap forces the data to be -1, 0 or 1
;
; Given a time in seconds in slideshow=, will erase the existing
; canvas, display, then pause for that many seconds.  Most useful
; with the /update flag to do impromptu slideshows.
;
; Add /reverseorder if you want this to resort the data from the IDL
; 'bottom=first' to a more reasonable 'top=first' plotting.
;
; /nocloseps means tv_multi will open the PS device but
; ends without closing it... useful for when tv_multi is
; wrapped by another program which wishes to append xyouts
; or other information to its plot.
;
; the /threeview tag adds little x/y/z arrows, for use only
; when importing Pixon 'threeview' plots.
;
; /raw turns off interpolation when rescaling the image sizes
;  (returning blockier but more accurate images)
;
; /autocap sets a dynamic range by reducing the brightest pixels
;  as per a smoothing function, in theory to remove bright outlyers
;
; /blankrow puts in a blank row (useful in rare cases when one sometimes plots
;  input/output and sometimes doesn't, and requires spacing for the latter.)
;
; /appendps (assuming /ps is given) skips opening a new file (and thus
;  assumes we already have a postscript file open) and simply appends
;  to that existing file using /pagebreak.  Use /nocloseps with all
;  but the last file to do this appending.
;
; /compact makes the plots smaller (typically, for publication), and
; /notitle removes the bottom title and whitespace, and /squish pushes
; the title closer to the plots.
;

; alt to 'num2str' returning proper number of significant figures
FUNCTION tvnum2str,num,sigfig

  if (n_elements(sigfig) eq 0) then sigfig=2; default to 2 decimal places

  str=strtrim(string(num),2)

  stra=strmid(str,0,strpos(str,'.')+sigfig+1)

  if (strpos(str,'e') gt 0) then stra=stra+strmid(str,strpos(str,'e'))

  return,stra

END


FUNCTION tvd2d,box,winxy,yx
  ; rescales my original 'device' scalings to a data frame,
  ; i.e. for [x,y,x,y] in device/pixels and given window size [x,y],
  ; returns a set [x',y',x',y'] in the data [0,1] scale.
  ; also works on an [x,y] pair, or
  ; works on a single 'x' or 'y' value if using yx=0 or 1
  ; in postscript mode, scale winxy has been adjusted to return
  ; inches instead of data frame, so all math is proper here for both cases.

  scale=1.0/float(winxy)
;  if (n_elements(ps) ne 0) then scale=scale*ps; adjust for page size

  ; scale=(scale*0.0)+1.0; use this if tv_multi normal=0

  if (n_elements(box) gt 2) then scale=[scale,scale]
  yx=KEYWORD_SET(yx); defaults to 0 aka x, can be 1 aka y via yx of 1

  
  if (n_elements(box) eq 1) then begin
      newbox=box*scale[yx]    ; singlet
  end else begin
      newbox=box*scale; set of 2 or 4
  end

;  print,box,' : ',winxy[yx],' is as ',newbox,' : ',1.0,' aka ',box/float(winxy[yx])

  return,newbox

END

PRO tv_multi,datain,rescale=rescale,title=title,axes=axes,border=border, $
             ps=ps,dieplot=dieplot,inverse=inverse,logplot=logplot, $
             ct=ct,update=update,winid=winid,fixedsize=fixedsize, $
             linebreak=linebreak, unique=unique, maxval=maxval,minval=minval,$
             slice=slice,addextra=addextra,xcol=xcol,yrow=yrow,wipe=wipe, $
             labelset=labelset,localminmax=localminmax,nocloseps=nocloseps,$
             histogram=histogram, nbins=nbins, both=both, $
             reverseorder=reverseorder, blankrow=blankrow, rowtitle=rowtitle, $
             oom=oom,xmargin=xmargin,ymargin=ymargin,decouple=decouple, $
             rsun=rsun,csun=csun,fontsize=fontsize,threeview=threeview,$
             header=header,caption=caption,xbuffer=xbuffer,ybuffer=ybuffer,$
             los_utc=los_utc,quiet=quiet,slideshow=slideshow,circle=circle,$
             showrange=showrange,tellactual=tellactual,tellplot=tellplot,$
             landscape=landscape,zbuffer=zbuffer,raw=raw,autocap=autocap,$
             pretty=pretty,filename=filename,inches=inches,bitmap=bitmap,$
             cm=cm,appendps=appendps,gif=gif,compact=compact,$
             notitle=notitle,squish=squish

  ; a little syntaxtical shorthand for a specific postscript plot look
  ; /pretty is shorthand to set '/showrange, /border, /ps, /land'
  ; for making decent postscript plots.
  pretty=KEYWORD_SET(pretty)

  ; this routine is way more complicated than it needs to be, since
  ; it supports both device and normal coordinate schemes due to tests
  ; of postscript plotting.  That said, keep normal=1
  ; normal=0; remember to also alter tvd2d if you use normal=0
  ;normal=0
  normal=1

  if (pretty) then begin
      showrange=1
;      inverse=1
      border=1
      ps=1
      landscape=1
      rescale=1
;      circle=circle
;      fontsize=0.6
      if (n_elements(log) eq 0) then log=1
  end

  if (n_elements(datain) eq 0) then begin
      print,"usage is 'tv_multi,datum,[rescale=rescale],[title='title'],[axes='yes/no'],[ps='yes/no']"
      return
  end

  if (n_elements(ct) eq 0) then ct=-1
  if (n_elements(title) eq 0) then ptitle='plot' else ptitle=title+','
  if (n_elements(winid) eq 0) then winid=-1
  if (n_elements(oom) eq 0) then oom=-1

  ; note we also have to turn off 'oom' if min value is given
  if (n_elements(minval) ne 0) then if (finite(minval)) then oom=-1

  if (n_elements(fontsize) eq 0) then fontsize=1.0
  if (n_elements(xmargin) eq 0) then xmargin=0
  if (n_elements(ymargin) eq 0) then ymargin=0

  if (n_elements(rsun) eq 0) then rsun=0

  zbuffer=KEYWORD_SET(zbuffer)
  raw=KEYWORD_SET(raw)
  reverseorder=KEYWORD_SET(reverseorder)
  inverse=KEYWORD_SET(inverse)
  if (n_elements(wipe) eq 0) then wipe=0
  border=KEYWORD_SET(border)
  dieplot=KEYWORD_SET(dieplot)
  logplot=KEYWORD_SET(logplot)
  blankrow=KEYWORD_SET(blankrow)
  axes=KEYWORD_SET(axes)
  cm=KEYWORD_SET(cm)
  ps=KEYWORD_SET(ps)
  gif=KEYWORD_SET(gif)
  quiet=KEYWORD_SET(quiet)
  noisy = ~ quiet
  landscape=KEYWORD_SET(landscape); only relevant if /ps is set
  update=KEYWORD_SET(update); if yes, use current window for plot
  slice=KEYWORD_SET(slice)
  addextra=KEYWORD_SET(addextra)
  bitmap=KEYWORD_SET(bitmap)
  decouple=KEYWORD_SET(decouple); decouples slice scales from other plots
  unique=KEYWORD_SET(unique)
  localminmax=KEYWORD_SET(localminmax)
  threeview=KEYWORD_SET(threeview)
  compact=KEYWORD_SET(compact)
  squish=KEYWORD_SET(squish)
  notitle=KEYWORD_SET(notitle)
  nocloseps=KEYWORD_SET(nocloseps)
  appendps=KEYWORD_SET(appendps)
  histogram=KEYWORD_SET(histogram)
  both=KEYWORD_SET(both)
  circle=KEYWORD_SET(circle)
  autocap=KEYWORD_SET(autocap)
  showrange=KEYWORD_SET(showrange)
  tellplot=KEYWORD_SET(tellplot)
  tellactual=KEYWORD_SET(tellactual)
  if (showrange) then tellplot=1; legacy name /showrange handled here
  if (tellplot) then tellactual=0
  ; slideshow= secs to sleep/pause after display
  if (n_elements(slideshow) eq 0) then slideshow=-1;
  if (n_elements(nbins) eq 0) then nbins=-1; defaults later to N
  if (n_elements(xcol) eq 0) then xcol=-1; -1 means ignored later
  if (n_elements(yrow) eq 0) then yrow=-1; ditto
  if (n_elements(yrow) gt 1) then yrow=yrow[0]; if array, take just first
  if (n_elements(xcol) gt 1) then xcol=xcol[0]; if array, take just first
  if (yrow ne -1 and xcol ne -1) then xcol=-1; yrow has precidence
  ; note if xrow and yrow not set, yrow defaults to N/2 later

  if (n_elements(caption) ne 0) then begin
;          ; first convert any perl-like \n linefields to IDL !C
;      caption=STRJOIN(STRSPLIT(caption,'\n',/EXTRACT),'!C')

      ignore=STRSPLIT(caption,'!C',count=numcaptionlines)
      captionbuffer=(numcaptionlines+2)*fontsize*9.1
  end else begin
      captionbuffer=0
  end

  ; xbuffer is just if you want pextra whitespace to the right or top
  if (n_elements(xbuffer) eq 0) then xbuffer=0
  if (n_elements(ybuffer) eq 0) then ybuffer=0

  ; default resizing is none, but can be specified by invocation
  if (n_elements(rescale) eq 0) then rescale=1
  ; note 'fixedsize' overrides and overwrites 'rescale' if given
  if (n_elements(fixedsize) ne 0) then begin
      if (fixedsize gt 0) then begin
          if (datatype(datain) eq 'PTR') then N = (size(*datain[0]))[1] else $
            N=n_elements(datain[*,0,0])
          rescale=float(fixedsize)/float(N)
      end
  end

  if (both) then begin
     ; asking for 'raw plus log' overrides plotting of slices, histo, loglabels
      histogram=0               
      slice=0
      logplot=0
      addextra=0
  end


  ; dice require closer spacing, ps more, screen middling
  if (dieplot) then spacing=10 else if (compact) then spacing=10 else $
    if (ps) then spacing=50 else spacing=40

  if (n_elements(filename) eq 0) then begin
      if (gif) then filename='tvmulti.gif' else filename='tvmulti.ps'
      if (zbuffer) then filename='z'+filename
      if (dieplot) then filename='die_'+filename
  end

  ; first set up our 'data' item based on input
  ; if we are passed pointers, we (inefficiently) expand them
  ; out into arrays.
  freeme=1; default for most cases is free our excess pointers
  if (datatype(datain) eq 'PTR') then begin
      ; input is an array of pointers to image arrays
      nframes = n_elements(datain)
      base = (size(*datain[0]))[1]
      basex = (size(*datain[0]))[1]
      basey = (size(*datain[0]))[2]
      data = datain
      freeme=0; do not free our ptr since it is the original data ptr
  end else begin
      ; input is either an array or an array of image arrays
      bases=size(datain)
      if (bases[0] eq 2) then begin
          nframes=1   ; special handling if you just pass it one frame
          base=bases[1]
          basex=bases[1]
          basey=bases[2]
          data=make_array(1,/ptr)
          data=ptr_new(datain)
      end else begin
          nframes=bases[3]
          base=bases[1]
          basex=bases[1]
          basey=bases[2]
          data = make_array(nframes,/ptr)
          for i=0,nframes-1 do data[i]=ptr_new(datain[*,*,i])
      end
      freeme=1; free our excess pointers later
  end

  if (reverseorder) then begin
      ; swap around out data and label order from bottom-to-top
      ; to instead be top-to-bottom
      istart=nframes-1
      iend=0
      iinc=-1
  end else begin
      ; normal 'as input' order
      istart=0
      iend=nframes-1
      iinc=1
  end
;      help,data
;      data=reverse(data)
;      help,labelset
;      if (n_elements(labelset) ne 0) then labelset=reverse(labelset)

  ; this next bit of awkward parsing is to ensure that we
  ; do not overwrite any user-provided labels, while also
  ; allowing for the /showrange option of showing datamin/max
  ; even if user-provided labels are not used
  if (n_elements(labelset) eq 0) then nlabels=0 else $
    nlabels=n_elements(labelset)
  if (nlabels eq 0) then labelset=strarr(nframes)
  rangelabels=strarr(nframes)
  if (tellplot or tellactual) then nlabels=nframes


  ; set some N-dependent defaults, if otherwise not specified by user
  if (xcol eq -1 and yrow eq -1) then yrow=basey/2
  if (nbins eq -1) then nbins=base

  if (n_elements(linebreak) eq 0) then begin
      ; default=no break
      lbreak=nframes+1       
      n_lines=1
      n_in_row=nframes
      ;unique=0
  end else if (linebreak eq 0 or linebreak ge nframes) then begin
      ; special weird numerical cases
      lbreak=nframes+1
      n_lines=1
      n_in_row=nframes
      ;unique=0
  end else begin
      n_lines = long(nframes/linebreak); e.g. 6 frames, break at each 3 = 2
      if ( (nframes mod linebreak) ne 0) then n_lines = n_lines+1
;      print,nframes,linebreak,n_lines
      lbreak=linebreak
      n_in_row=lbreak
  end


  newsize=rescale*base
  newsizex=rescale*basex
  newsizey=rescale*basey

  xsize=spacing+n_in_row*(newsizex+spacing)+xmargin*2; size + optional margin
  if (dieplot) then xsize=newsizex*3+40+xmargin*2

  ysize=(newsizey+spacing*3) * (n_lines + blankrow)
  ysize = ysize - spacing*(n_lines-1+blankrow); correction for larger plots
  if (dieplot) then ysize=newsizey*4+40
  if (addextra or both) then ysize=ysize*2; adds room for plot under tv
  ysize=ysize+ymargin*2; adds optional margin


  if (compact and n_elements(header) ne 0) then ysize += 30
  if (squish) then cmargin=40 else cmargin=0

  ; for display-to-postscript use, converted /device to data units [0,1]
  ; winxy is a handy reference of total win size
  winxy=[xsize+xbuffer,ysize+captionbuffer+ybuffer];
  ;print,'winxy is ',winxy
  ; prototype code for fixing postscript problems, likely delete this
  ;if (ps eq 2) then begin
      ; postscript, 'fit to page' incorrect approach, left here but
      ; commented out as a cautionary on how not to do this.
      ; width in pixels/1000.0 pixels per cm = current width in cm
      ; want width 28cm, so 28cm = current size * X
      ; so final rescale = current rescale * X, thus X=current size/28.0
      ; and final rescale = rescale * current size / 28.0
      ; thus final rescale = rescale * (newsize/1000.0) / 28.0
      ; width_in_cm = winxy[0]/1000.0
      ; desiredscale = 28.0 / width_in_cm
      ; rescale = rescale * desiredscale
      ; then redo all the newsize, xsize, etc.
  ;end


  ;rsun=rsun*rescale; actually we rescale this later
  if (n_elements(csun) eq 0) then csun=[basex/2,basey/2]; assume sun in center
  csun=csun*rescale

  ;start of making PTitle
  ptitle=string(ptitle,basex,basey,format='(a," base res ",i4,"x",i-4)')
;    ptitle=string(title,base,base,rescale,format='(a," base res ",i4,"x",i-4,", scaled by ",f-3.1)')
  if (rescale eq 0) then begin
      scalestr = '0'
  end else if (rescale ge 1) then begin
      scalestr = string(rescale,format='(" rescaled by ",f-4.1)')
  end else begin
      scalestr = string(rescale,format='(" rescaled by ",f-5.2)')
  end
  ptitle = ptitle + scalestr

  if (both) then ptitle = ptitle +', raw+log (log is for data>0)'
  if (logplot) then ptitle = ptitle+', log (data>0)'
  if (oom gt 0) then ptitle = ptitle+', '+tvnum2str(oom)+' oom'
  if (slice or histogram) then begin
      if (slice) then stem='slice plot' else stem='histogram'
      if (addextra) then stem = ' also '+stem
      if (slice and xcol ne -1) then begin
          ptitle = string(ptitle,stem,xcol,format='(a,a,"of xcol=",i-4)')
      end else if (slice and yrow ne -1) then begin
          ptitle = string(ptitle,stem,yrow,format='(a,a,"of yrow",i-4)')
      end else begin
          ptitle = ptitle+stem
      end
  end
; finished making PTitle

  current_device=!d.name

  fc=0; default background, may be changed below depending on device

  ; first derive some postscript paper sizes, if needed
  if (ps) then begin

;      print,current_device

      paperborder=0.5; suggested border

      winaspect=float(winxy[0])/float(winxy[1])

      if (landscape) then paperdim=[10.0,8.5] else paperdim=[8.5,11.0]

      xpaperarea=paperdim[0] - 2.0*paperborder; paper size minus border,inches
      ypaperarea=paperdim[1] - 2.0*paperborder; paper size minus border,inches

      paperaspect=xpaperarea/ypaperarea

      xpaperfinal=xpaperarea
      ypaperfinal=ypaperarea

      if (winaspect gt paperaspect) then ypaperfinal = xpaperarea/winaspect $
      else xpaperfinal = ypaperarea * winaspect
;      print,xpaperfinal,ypaperfinal

      ; now adjust our ratio for translation to inches in tvd2d
      inchxy=winxy/[xpaperfinal,ypaperfinal]

      ; quirk of landscape-- yoffset is from right not left,and before rotation
      xoffset=paperborder
      yoffset=paperborder
      if (landscape) then yoffset=paperdim[0]-paperborder

;      print,'to fit graphics window ',winxy[0],winxy[1],' aspect = ',winaspect
;      print,' onto paper ',paperdim[0],paperdim[1],' aspect = ',$
;        paperdim[0]/paperdim[1]
;      print,' that has border offsets = ',xoffset,yoffset,$
;        ' (border=',paperborder,')'
;      print,' resulting in active area ',xpaperarea,ypaperarea,$
;        ' aspect = ',paperaspect
;      print,' means rescaling active area to ',xpaperfinal,ypaperfinal,$
;        xpaperfinal/ypaperfinal

;      pfname='tvmulti.ps'

;      device,filename='tvmulti.ps',/color, bits_per_pixel=8,$
;        xsize=28.0,ysize=20.0,/landscape 
;        xsize=(28.0*winxy[0])/1000.0,ysize=(20.0*winxy[1])/1000.0;

  end

  ; now do & save one last conversion of our data/normal/postscript options
  newx=tvd2d(newsizex,winxy)
  newy=tvd2d(newsizey,winxy,1)
  if (ps) then begin
      ; extra conversion values needed later for tvscl/image use
      inchx=tvd2d(newsizex,inchxy)
      inchy=tvd2d(newsizey,inchxy,1)
  end

  ; now actually create the necessary window

  if (zbuffer) then begin

      set_plot,'z'
      device, set_resolution=[winxy[0],winxy[1]]
      erase,!D.N_COLORS - 1; a white fill

  end else if (ps eq 1) then begin

      if (appendps) then begin
          ERASE; starts a new page on a presumed open postscript device
      end else begin
          set_plot,'PS'
         ; postscript
      
          device,filename=filename,/color, bits_per_pixel=8, $
            xsize=xpaperfinal,ysize=ypaperfinal,/inches, $
            xoffset=xoffset,yoffset=yoffset,landscape=landscape, $
            font_size=9*fontsize;,/encapsulate; ,scale_factor=0.8

      ;print,'page size: ',xpaperfinal,ypaperfinal
          if (noisy) then print,'Writing ',filename
      end
;      winid = !D.WINDOW

  end else if (winid ne -1) then begin

      ; use a specific window, if it exists
      cwin = !D.WINDOW
      clean_wset,winid

      winid = !D.WINDOW

  end else if (update) then begin
      ; use current window

      winid = !D.WINDOW

  end else begin
      ; force background to white
      oldbg=!P.BACKGROUND
      !P.BACKGROUND=!D.N_COLORS-1
      window,/free,xsize=winxy[0],ysize=winxy[1],retain=2
      !P.BACKGROUND=oldbg
      ; now restore original backgrounds as per user needs
;      print,xsize+xbuffer,ysize+captionbuffer,!D.X_SIZE,!D.Y_SIZE

      winid = !D.WINDOW

  end

  if (ct ne -1) then loadct,ct

  if (slideshow gt 0) then erase,'FFFFFF'x

;  winid = !D.WINDOW

  ; first grab local mins
  dmax=make_array(nframes,/double,value=0)
  for i=0,nframes-1 do dmax[i]=max(*data[i])

  if (autocap) then begin
      ; find outliers as 'brightest 1%' and set threshold appropriately
      dcap=dmax*0.0
      for i=0,nframes-1 do dcap[i]=percentiles(*data[i],value=[0.99])
      for i=0,nframes-1 do dmax[i]=min([dmax[i],dcap[i]])
  end


  dmin=make_array(nframes,/double,value=0)
  for i=0,nframes-1 do dmin[i]=min(*data[i])
  ; allow forcing of max and min
  if (n_elements(maxval) ne 0) then $
    if (finite(maxval)) then dmax=make_array(/float,nframes,value=maxval)
  if (n_elements(minval) ne 0) then $
    if (finite(minval)) then dmin=make_array(/float,nframes,value=minval)

  ; Note that we first save the actual data limits for the labels,
  ; even though the plotting will be on a unified scale
  ; These labels are used if /tellactual is given
  if (tellactual) then for i=0,nframes-1 do $
    rangelabels[i]=' ['+tvnum2str(dmin[i])+','+tvnum2str(dmax[i])+']'

  if (unique) then begin
      ; wants plotting range by row, not for entire data.
      for i=0,n_lines-1 do begin
          j=i*lbreak
          k=j+lbreak-1
          if (k gt nframes-1) then k=nframes-1
          dmin[j:k]=min(dmin[j:k])
          dmax[j:k]=max(dmax[j:k])
      end
  end else if (localminmax eq 0) then begin
      ; set a global max
      dmin[0:nframes-1]=min(dmin[0:nframes-1])
      dmax[0:nframes-1]=max(dmax[0:nframes-1])
  end

  ; set new limits based on requested order of magnitude, if any
  ; note we apply a cutoff of the datamin so that we do not
  ; put in too many orders of magnitude for data that does not span
  ; it, e.g. date from 9-10 will not be forced to an 0.01-10 scale.
  ;

  if (bitmap) then begin
      for i=0,nframes-1 do dmin[i]=-1.0
      for i=0,nframes-1 do dmax[i]=1.0
  end

  if (oom gt 0) then for i=0,nframes-1 do $
    dmin[i]=max([dmin[i],dmax[i]/(10.0^oom)])


  ; special handling for log plots where dmin cannot be artificially set
  ; we instead set a 'global non-zero minimum' in place of 0
  if (logplot and min(dmin) le 0) then begin
      minmin=min(dmin > 0)
      ; if there is not a global 'not zero min', force one at 10-8 from dmax
      if (minmin eq 0) then minmin=max(dmax)*1e-8
      for i=0,nframes-1 do if (dmin[i] le 0) then dmin[i]=minmin
  end

  ; now do /tellplot, if asked
  if (tellplot) then for i=0,nframes-1 do $
    rangelabels[i]=' ['+tvnum2str(dmin[i])+','+tvnum2str(dmax[i])+']'

  ; update with how plot scaling is done
  if (unique) then begin
      dynamics = ' (each row has own scale)'
  end else if (localminmax) then begin
      dynamics = ' (each figure has own scale)'
  end else begin
      dynamics = ' plots forced to ['+tvnum2str(dmin[0])+','+tvnum2str(dmax[0])+']'
  end
  if (tellactual) then dynamics = dynamics+' (unscaled data ranges indicated)'
  if (tellplot) then dynamics = dynamics+' (plotted ranges indicated)'
  ptitle = ptitle + dynamics

  ; this section is commented out because it is now obsolete, as
  ; we track and scale images instead of original data
;  dsave=fltarr(nframes,2)
  ; force unified scaling of data, then fix restore afterwards
  ; we do this by altering 2 pixel values to set them to the global
  ; min and max, hoping that 2 'fake' values will not ruin your
  ; data viewing pleasure.
;  for i=0,nframes-1 do begin
;      dsave[i,0]=(*data[i])[0,0]; save original data value
;      dsave[i,1]=(*data[i])[1,0]; save original data value
;      omax = max(*data[i])
;      omin = min(*data[i])
;      if (omax ne dmax[i]) then (*data[i])[0,0]=dmax[i]
;      if (omin ne dmin[i]) then (*data[i])[1,0]=dmin[i]
;  end

;  loadct,0
  ix=0; tracks data across

  for iframe=istart,iend,iinc do begin

; note how we force data to our range, which already includes
; any relevant 'oom'
      mydata=*data[iframe] < dmax[iframe] > dmin[iframe]

      ; get centroid from our unaltered data
      if (cm) then ctr=centroid(*data[iframe])*rescale else $
        ctr=[newsizex/2,newsizey/2]

      if (bitmap) then begin
          i0=where(mydata eq 0.0)
          il=where(mydata lt 0.0)
          ih=where(mydata gt 0.0)
          mydata=long(mydata)
          if (il[0] ne -1) then mydata[il]=-1
          if (i0[0] ne -1) then mydata[i0]=0
          if (ih[0] ne -1) then mydata[ih]=1
      end

      if (raw) then begin
          im=congrid(mydata,newsizex,newsizey) ;,/interp)
      end else begin
          im=congrid(mydata,newsizex,newsizey,/interp)
      end

;      if (oom gt 0) then begin
;          ; limit data to given orders of magnitude
;          ; After some debate over whether it is more proper to
;          ; provide the 2 OOM of the data, versus 2 OOM from the
;          ; maximum amount being plotted for a set of data (unless
;          ; the /unique flag is set), we went with ensuring that
;          ; each plot covers the same dynamic range
;          im = (im > dmin[iframe])
;      end


      ; extract slices before additional optional border/axes
      if (slice eq 1) then begin
          if (xcol ne -1) then begin
              slicedata=im[xcol*rescale,*]
          end else if (yrow ne -1) then begin
              slicedata=im[*,yrow*rescale]
          end else begin
              ; unused option of 'plot all data values'
              slicedata=im
          end
      end

      if (histogram) then begin
          if (nbins ne -1) then $
            histodata = histogram(im,nbins=nbins) else $
            histodata = histogram(im)
      end
;        histogram(im[(where (im gt 0))],nbins=nbins)

      if (border) then begin
         ; add a border, overwriting any bordering data
          borderval=dmin[iframe]
          im[0,*]=borderval
          im[newsizex-1,*]=borderval
          im[*,0]=borderval
          im[*,newsizey-1]=borderval

          if (both) then begin
              borderval=max(imorig)
              imorig[0,*]=borderval
              imorig[newsizex-1,*]=borderval
              imorig[*,0]=borderval
              imorig[*,newsizey-1]=borderval
          end
      end

      if (axes or cm) then begin
        ; add a crosshair through the plot, overwriting any central data
        ; note that axes go through the nominal middle unless given /cm,  
        ;   which will instead make the axes through the centroid of it
          borderval=dmin[iframe]
          im[ctr[0],*]=borderval
          im[*,ctr[1]]=borderval
          im[ctr[0]-1,*]=borderval
          im[*,ctr[1]-1]=borderval

          if (both) then begin
              borderval=max(imorig)
              imorig[ctr[0],*]=borderval
              imorig[*,ctr[1]]=borderval

              imorig[ctr[0]-1,*]=borderval
              imorig[*,ctr[1]-1]=borderval
          end
      end

      ; force unified scaling of data
      ; we do this by altering 2 pixel values to set them to the global
      ; min and max, hoping that 2 'fake' values will not ruin your
      ; data viewing pleasure.
      ; Note we have to do this after setting the border etc, or we
      ;   lose this when we add borders and axes!
      lmax=max(im)
      lmin=min(im)
;      print,'lmax, lmin: ',lmax,lmin,dmax[iframe],dmin[iframe]
      if (lmax ne dmax[iframe]) then im[0,0]=dmax[iframe]
      if (lmin ne dmin[iframe]) then im[1,0]=dmin[iframe]

      ; set min for log based on first image plotted only, so
      ; all log plots are on the same scale

      if (logplot or both) then begin
          if (both) then imorig=im
          if (max(im) eq 0 and min(im) ne 0) then begin
              ; force inversion
              im=abs(im)
          end
          if (max(im) > 0) then begin
              imset=(im > min(im(where (im > 0))))
              if (max(imset) > 0) then im=alog10(imset)
;              print,minmax(im),dmin[iframe],dmax[iframe],lmin,lmax
          end
      end

      if (inverse) then im=0-im ; last operation before plotting can be this
      if (inverse and both) then imorig=0-imorig ; ditto

      if (dieplot) then begin
        ; plots a foldable 6-sided die, assuming sufficient data

        ; x goes from left to right
          if (iframe lt 4) then x=newsizex
          if (iframe eq 4) then x=0
          if (iframe eq 5) then x=newsizex*2

        ; y goes from top to bottom
          if (iframe eq 0) then y=3*newsizey
          if (iframe eq 1) then y=2*newsizey
          if (iframe eq 2) then y=1*newsizey
          if (iframe eq 3) then y=0
          if (iframe eq 4 or iframe eq 5) then y=newsizey*1

          x=x+spacing+xmargin           ; adds a little border to the image
          y=y+2*spacing+ymargin+captionbuffer; adds a little border to image

;          tvscl,im,x,y
          if (ps) then xp=tvd2d(x,inchxy)
          if (ps) then yp=tvd2d(y,inchxy,1)
          xw=tvd2d(x,winxy)
          yw=tvd2d(y,winxy,1)
          if (ps) then $
            tvscl,im,xp,yp,xsize=inchx,ysize=inchy,/inches else $
            tvscl,im,xw,yw,xsize=newx,ysize=newy,normal=normal


      end else begin
          ; normal side-by-side plotting

          ; get initial y
          if (ix eq 0) then $
            y=spacing*2+ymargin+captionbuffer+ybuffer+(newsizey*blankrow)
          if (ix eq lbreak) then begin
              ; new row, update it
              if (addextra or both) then numy=2 else numy=1
              y = y + ((2*spacing+ymargin) + newsizey)*numy
              ix=0; line return, reset our data horizonal position to start
          end
          x=spacing+xmargin+(spacing+newsizex)*ix

          if (slice eq 1 or histogram eq 1) then begin

              ; an extra functionality tossed in here, the ability
              ; to do structured plots of data values across a slice,
              ; rather than actually plotting the data
              ; print,'plotting at ',x,y,x+newsize,y+newsize,newsize
              ;x0=x+(newsize*0.1) ; shrink to fit to allow labels
              ;y0=y+(newsize*0.1) ; shrink to fit to allow labels
              ;x1=x+(newsize*0.9) ; shrink to fit to allow labels
              ;y1=y+(newsize*0.9) ; shrink to fit to allow labels
              x0=x
              y0=y
              x1=x+newsizex-1
              y1=y+newsizey-1

              if (slice) then begin
                  if (decouple) then yr=[min(slicedata),max(slicedata)] $
                  else yr=[dmin[iframe],dmax[iframe]]
                  if (yr[0] eq yr[1]) then yr[1]=yr[0]+1; arbitrary
                  plot,slicedata,pos=tvd2d([x0,y0,x1,y1],winxy),/noerase,$
                    max_value=yr[1],min_value=yr[0],yrange=yr,$
                    yticks=1,xticks=1,ytickname=[' ',' '],xtickname=[' ',' '],$
                    xstyle=1,ystyle=1,xrange=[0,n_elements(slicedata)],$
                    charsize=fontsize,normal=normal
              end else begin
                  plot,histodata,pos=tvd2d([x0,y0,x1,y1],winxy),/noerase,$
                    yticks=1,xticks=1,ytickname=[' ',' '],xtickname=[' ',' '],$
                    xstyle=1,ystyle=1,charsize=fontsize,normal=normal
                  hlabel = 'histogram,' + strtrim(string(nbins),2) + ' bins'
                  xyouts,$
                    tvd2d(x0+newsizex/4,winxy),tvd2d(y0+3*newsizey/4,winxy,1),$
                    hlabel,color=fc,charsize=fontsize,normal=normal

              end

          end

          ; next bit is needed for labels but pulled out of loop to avoid dups
          if (n_elements(csun) gt 2) then begin
              ; could be a long 1-D array or a set of 2-D elements
              if (size(csun,/n_dim) eq 2) then begin
                  msun=csun[*,iframe]
              end else begin
                  msun=csun[iframe*2:(iframe*2)+1]
              end
          end else begin
              msun=csun
          end

          ; likewise, extracting rsun from array or scalar as needed
          if (n_elements(rsun) gt 1) then dsun=rsun[iframe] else dsun=rsun
          dsun=dsun*rescale

          if ((slice eq 0 and histogram eq 0) or addextra eq 1 or dsun gt 0) then begin
              ; usual image plots
              ;print,'showing image at ',x,y
              if (addextra) then y2=y+newsizey else y2=y
              ; tvscl,im,x,y2
;              print,'here B',normal,inches
              if (ps) then begin
                  xp=tvd2d(x,inchxy)
                  yp=tvd2d(y2,inchxy,1)
                  if (n_elements(inches) ne 0) then begin
                      ; postscript way to force a given image size, iffy
                      inchx=inches
                      inchy=inches
                  end
;                  print,'ps ',xp,yp,inchx,inchy
                  tvscl,im,xp,yp,xsize=inchx,ysize=inchy,/inches
              end else begin
                  xw=tvd2d(x,winxy)
                  yw=tvd2d(y2,winxy,1)
;                  print,'window ',xw,yw,newx,newy
                  ;print,'x,y: ',xw,yw,newx,newy
                  tvscl,im,xw,yw,xsize=newx,ysize=newy,normal=normal
              end


;              if (dsun gt 0) then $
;                rsunscale,newsize,newsize,x,y2,dsun,msun,spacing,font=fontsize
              if (dsun gt 0) then $
                rsunscale,tvd2d(newsizex,winxy),tvd2d(newsizey,winxy,1),tvd2d(x,winxy),tvd2d(y2,winxy,1),tvd2d(dsun,winxy),tvd2d(msun,winxy),tvd2d(spacing,winxy),font=fontsize,normal=normal

              if (circle) then draw_circle,x+csun[0],y2+csun[1],dsun,/device
          end

          if (both) then begin
              ; adding log plot under image plot
              y2=y+newsizey;+spacing
              ; tvscl,imorig,x,y2
              if (ps) then begin
                  xp=tvd2d(x,inchxy)
                  yp=tvd2d(y2,inchxy,1)
                  tvscl,imorig,xp,yp,xsize=inchx,ysize=inchy,/inches
              end else begin
                  xw=tvd2d(x,winxy)
                  yw=tvd2d(y2,winxy,1)
                  tvscl,imorig,xw,yw,xsize=newx,ysize=newy,normal=normal
              end


;              if (dsun gt 0) then $
;                rsunscale,newsize,newsize,x,y2,dsun,msun,spacing,font=fontsize
              if (dsun gt 0) then $
                rsunscale,tvd2d(newsizex,winxy),tvd2d(newsizey,winxy,1),tvd2d(x,winxy),tvd2d(y2,winxy,1),tvd2d(dsun,winxy),tvd2d(msun,winxy),tvd2d(spacing,winxy),font=fontsize,normal=normal
              if (circle) then draw_circle,x+csun[0],y2+csun[1],dsun,/device
          end

          if (nlabels gt 0) then begin
              if (iframe lt nlabels) then begin
                xyouts,tvd2d(x+newsizex/2,winxy),tvd2d(y+newsizey+4,winxy,1),$
                  labelset[iframe]+rangelabels[iframe],$
                  align=0.5,color=fc,charsize=fontsize,normal=normal
              end else if (ix lt nlabels) then begin
                xyouts,tvd2d(x+newsizex/2,winxy),tvd2d(y+newsizey+4,winxy,1),$
                  labelset[ix]+rangelabels[iframe],$
                  align=0.5,color=fc,charsize=fontsize,normal=normal
              end
          end
          ++ix
      end

      ;print,iframe,min(im),max(im)

  end

  if (update eq 0) then begin
      ; all the extra pretty static labels

      if (notitle eq 0) then $
        xyouts,tvd2d(spacing+xmargin,winxy),$
        tvd2d(ymargin+spacing/2+captionbuffer+ybuffer+cmargin,winxy,1),$
        ptitle,alignment=0,color=fc,charsize=fontsize,normal=normal

      ; note we always do header larger than other text
      headerbuff=ysize-spacing/2+captionbuffer+ybuffer-4*fontsize
      if (compact and n_elements(header) ne 0) then headerbuff -= 10
      if (n_elements(header) ne 0) then xyouts,$
        tvd2d(xsize/2,winxy),$
        tvd2d(headerbuff,winxy,1),$
        header,alignment=0.5,color=fc,charsize=fontsize*2.0,normal=normal

      captionhack=16
;      captionhack=0
      if (n_elements(caption) ne 0) then xyouts,$
        tvd2d(spacing+xmargin,winxy),$
        tvd2d(ymargin/2+captionbuffer+captionhack,winxy,1),$
        caption,alignment=0,color=fc,charsize=fontsize,normal=normal

      if (n_elements(rowtitle) ne 0) then begin
          for i=0,n_elements(rowtitle)-1 do begin
              ytemp= captionbuffer+ybuffer + $
                ( (i+1) * (newsizey + 2*spacing+ymargin)) - newsizey-spacing
              xyouts,tvd2d(xsize/2,winxy),tvd2d(ytemp,winxy,1),$
                rowtitle[i],alignment=0.5,color=fc,$
                charsize=0.6*fontsize*2.0,normal=normal
          end
      end

      satsize=max([128,min([newsize,196])]); good range is 128 to 196

      ; sat_los_view not yet working in postscript mode
      if (ps eq 0) then begin
          if (n_elements(los_utc) ne 0) then sat_los_view,utc=los_utc,$
            embed=4,parent=winid,wsize=satsize,/inverse
      end

  end

  if (wipe gt 0) then begin
      wait,wipe
      xyouts,tvd2d(xsize/2,winxy),$
        tvd2d(ysize-spacing/2+captionbuffer+ybuffer-4*fontsize,winxy,1),$
        header,alignment=0.5,color=!D.N_COLORS-1,charsize=fontsize*2.0,$
        normal=normal
  end

  if (threeview) then begin

  ; 0 = black, !D.N_COLORS-1 = white
      x0ref=spacing+xmargin
      y0ref=spacing*2+ymargin+captionbuffer+ybuffer
      gap=spacing

      x1=tvd2d(x0ref+0.5*newsizex,winxy); center x of plot 1
      x2a=tvd2d(x0ref+0.3*gap+1.0*newsizex,winxy); between plots 1 and 2
      x2b=tvd2d(x0ref+0.6*gap+1.0*newsizex,winxy); between plots 1 and 2
      x3=tvd2d(x0ref+gap+1.5*newsizex,winxy); center x of plot 2
      x4=tvd2d(x0ref+2*gap+2.5*newsizex,winxy); center x of plot 3
      x5=tvd2d(x0ref+2.3*gap+3.0*newsizex,winxy); after plot 3
      y3=tvd2d(y0ref-0.3*gap,winxy,1); just below plot
      y2a=tvd2d(y0ref+0.3*gap+0.5*newsizey,winxy,1); middle of plot
      y2=tvd2d(y0ref+0.5*newsizey,winxy,1); middle of plot
      y2b=tvd2d(y0ref-0.3*gap+0.5*newsizey,winxy,1); middle of plot
      y1=tvd2d(y0ref+newsizey+0.3*gap,winxy,1); just above plot

      xyouts,x1,y1,"z!M7",alignment=0.5,normal=normal,color=0
      xyouts,x2a,y2a,"!M6y",alignment=0.5,normal=normal,color=0
      xyouts,x2b,y2b,"x!M4",alignment=0.5,normal=normal,color=0
      xyouts,x3,y1,"z!M7",alignment=0.5,normal=normal,color=0
      xyouts,x4,y3,"x!M5",alignment=0.5,normal=normal,color=0
      xyouts,x5,y2,"!M6y",alignment=0.5,normal=normal,color=0

;      xyouts,0.18,0.88,"z!M7",alignment=0.5,/normal,color=!D.N_COLORS-1
;      xyouts,0.33,0.55,"!M6y",alignment=0.5,/normal,color=!D.N_COLORS-1
;      xyouts,0.50,0.88,"z!M7",alignment=0.5,/normal,color=!D.N_COLORS-1
;      xyouts,0.36,0.55,'x!M4',alignment=0.5,/normal,color=!D.N_COLORS-1
;      xyouts,0.81,0.25,'x!M5',alignment=0.5,/normal,color=!D.N_COLORS-1
;      xyouts,0.95,0.55,'!M6y',alignment=0.5,/normal,color=!D.N_COLORS-1
  end


  ; restore original data values in scaling pixels
  ; now obsolete since we track and scale images, not data.
;  for i=0,nframes-1 do begin
;      (*data[i])[0,0]=dsave[i,0]
;      (*data[i])[1,0]=dsave[i,1]
;  end

  if (gif) then begin

      write_gif,filename,tvrd()

  end else if (zbuffer) then begin

      m_im = tvrd()

      if (ps) then begin

;          pfname='z'+pfname

          set_plot,'ps'
          device,filename=filename,/color, bits_per_pixel=8, $
            xsize=xpaperfinal,ysize=ypaperfinal,/inches, $
            xoffset=xoffset,yoffset=yoffset,landscape=landscape;,/encapsulate

          TV, m_im
          device,/close

          set_plot,current_device

      end else begin

          if (noisy) then print,'plotting to x'
          set_plot,'x'
          window,/free,xsize=winxy[0],ysize=winxy[1],retain=2
          tv,m_im

      end

  end else if (ps eq 1 and nocloseps eq 0) then begin

      device,/CLOSE_FILE
      ;print,current_device
      set_plot,current_device

  end

  ; if we altered the current window intentionally with 'winid', set it back
  if (n_elements(cwin) ne 0) then clean_wset,cwin

  if (freeme) then ptr_free,data

  if (slideshow gt 0) then wait,slideshow

  ; set these to what was used, if they were originally unspecified
  ; (basically, a hidden callback variable)
  if (n_elements(maxval) eq 0) then maxval=dmax
  if (n_elements(minval) eq 0) then minval=dmin

END
