package nxm.sys.libg;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.ImageObserver;
import java.util.ArrayList;
import java.util.Arrays;

import nxm.sys.inc.Drawable;
import nxm.sys.inc.InternalUseOnly;
import nxm.sys.inc.PlotFile;
import nxm.sys.inc.UnicodeMap;
import nxm.sys.inc.Units;
import nxm.sys.lib.Args;
import nxm.sys.lib.BaseFile;
import nxm.sys.lib.Data;
import nxm.sys.lib.DataFile;
import nxm.sys.lib.MidasException;
import nxm.sys.lib.Parser;
import nxm.sys.lib.Position;
import nxm.sys.lib.Table;
import nxm.sys.lib.TimeLine;
import nxm.sys.lib.TuneAdjust;
import nxm.sys.libm.Tolerance;
import nxm.sys.prim.plot;

/** A class to hold and plot a 2 dimensional data layer.

  @author Jeff Schoen / Jay Hallam / J. Czechowski IV / Neon Ngo
*/
public class Layer2D extends Layer {

  /** available compression modes for raster images */
  public final String compressionList = "None,Max,Min,Avg,Mean,SWMax";
  /** disable raster image compression */
  public final int CMP_NONE=1;
  /** raster block windowed maximum compression */
  public final int CMP_MAX=2;
  /** raster block windowed minimum compression (Not Implemented) */
  public final int CMP_MIN=3;
  /** raster block windowed average compression (Not Implemented) */
  public final int CMP_AVG=4;
  /** raster block windowed mean compression (Not Implemented) */
  public final int CMP_MEAN=5;
  /** raster sliding windowed maximum compression */
  public final int CMP_SWMAX=6;

  private static final int DEFAULT_LPS = 128;

  // Overview of indexing in a raster:
  //
  //                              TOP OF FILE --------------
  //                                            ^         ^
  //                                            |         |
  //                                            |         |
  //                                          index1      |
  //                                            |         |
  //                                            |         |
  //                                            v         |
  //              +------------------------+   ---        |
  //              |   ^                    |    ^         |
  //              |<--|----- frame ------->|    |  (index + index1)
  //              |   |                    |  index       |
  //              |   |                    |    |         |
  // current      |   |                    |    v         v
  //  draw   ---> |~~~|~~~~~~~~~~~~~~~~~~~~|   -------------
  //  line        |   |                    |
  //              | size                   |
  //              |   |                    |       frame  = [frame size in file]
  //              |   v                    |       index1 = mindex * pass
  //              +------------------------+       mindex = frame * size
  //

  /** Data read in from file (converted to float) */  protected float[]fbuf;
  /** MImage used (handles color map)             */  protected MImage mimage;
  /** Scaled data (z = buf[i]/scl1 + scl0)        */  protected byte[] buf;
  /** Rotated version of buf when in xvy mode     */  private byte[]   bufv;
  /** buffer for doing y compression larger than LPI*/private byte[]   cmpbuf;
  /** book keeping for doing y compression when larger than LPI */ private int cmpLeft;
  /** Contour lines (used bu drawContour())       */  private Contour  contour;
  /** Data in RWC (used in drawPoly())            */  private double[] dbuf;
  /** Pixel position A (used in drawPoly())       */  private Pix      pa;
  /** Pixel position B (used in drawPoly())       */  private Pix      pb;
  /** Number of lines per buffer (LPB)            */  private int      lpb;
  /** Number of lines per image  (LPI), buffer    */  private int      lpi;
  /** Current index on screen (total = index+index1)*/private int      index;
  /** Pass number                                 */  private int      pass;
  /** Index at the top of screen (always equal to pass*mindex)*/ private int index1;
  /** Index multiplier (always equal to frame*size)*/ private int      mindex;
  /** Copy of ndata for use as index adjustment   */  private int      indexAdjust;
  /** Compression to use (see compressionList)    */  private int      compression;
  /** Draw as raster (2D)                         */  private boolean  useRaster;
  /** Draw as contour (2D)                        */  private boolean  useContour;
  /** Draw as poly (3D)                           */  private boolean  usePoly;
  /** Is the axis rotated (i.e. is this an X-vs-Y plot?)*/private boolean xvy;
  /** X-Axis is time (used in getTimeAt(..))      */  private boolean  xIsTime;
  /** Pixel xMin                                  */  private int      jx1;
  /** Pixel xMax                                  */  private int      jx2;
  /** Pixel yMin                                  */  private int      jy1;
  /** Pixel yMax                                  */  private int      jy2;
  /** Index of the next line to be drawn (when xvy is true) */  private int ixn;
  /** Index of the next line to be drawn (when xvy is false)*/  private int iyn;
  /** Lower level scale value                     */  protected float  scl0;
  /** Scale multiplier                            */  protected float  scl1;
  /** Color map before going to M_PHASE mode and restored when leaving that mode */
  private int      dcmap       = MColor.RAMP;
  //private boolean  forceReread = false;       // Option to force data to be reread in the draw(int) method. (FYI, we stopped using this variable in 2.5.0)
  /** Used to flag reScale of colors after colorMap change */
  private boolean  reScale     = false;
  /** Option to use Midas Type 2000 with SL/QB data as actual color for raster image*/
  protected boolean  directColor = false;
  /** image buffer containing actual colors w/ alpha values of raster (mirrors buf[]) */
  private int[]    imageBuf;
  /** chunk of image buffer that is copied to mimage.image  (mirrors bufv[]) */
  private int[]    imageBufv;
  /** buffer for doing y compression larger than LPI (mirrors cmpbuf[]) */
  private int[]    cmpbufv;
  /** alpha color component that will override those of the input when in directColor mode */
  private int      alpha       = -1;

  private boolean scaleZ = false;      // whether to allow
  private double  lrz1;                // Last rz1 = min z-axis range value (special case for 3D view)
  private double  lrz2;                // Last rz2 = max z-axis range value (special case for 3D view)
  private int     lcmp;                // Last compression setting ???
  private boolean yCompressionWarn = true;    // NTN: added to display warnings when Y-direction compression is larger than /LPB
                                              //      which may cause data to not seem to be rendered (reset to true in reset())
  private int     compressByteWarn = 0;       // NTN: added to display warning in compressByte() when icmp > nx (every i th time)
  private int     compressIntWarn  = 0;       // NTN: added to display warning in compressInt()  when icmp > nx (every i th time)

  /** @deprecated Since 3.5.1, this field is meant to be private. */
  @Deprecated
  public TimeLine timeLineSave     = null;    // JMM keep track of most recent TimeLine
  /** @deprecated Since 3.5.1, this field is meant to be private. */
  @Deprecated
  public double   indexSave        = 0;       // save index when TimeLine changes (index in bytes)

  private boolean awaiting_data    = true;   // waiting for new data
  private boolean force_proc_order = true;   // flag to disable changes for Bugs 1544, 2091, and 1254

  /** Default constructor. */
  public Layer2D () {
    super("2D");
    setEnableNonLocal(false, false, true);
    ndata   = index = index1 = pass = indexAdjust = 0;
    defmode = Drawable.DATAX;
    zaxis   = true;
  }

  /** package-private since NeXtMidas 3.7.0, was deprecated in 2.7.0: This is never used by PLOT. */
  Layer2D (int cm, int nc) { // TODO: remove in 3.9+ or 3.7.1+
    this();
    cmap = cm;
    ncolors = nc;
    colors = MColor.getColorMap(cm,nc);
  }

  @Override
  protected void init (plot p, String layerName, int layerIndex, Layer oldLayer) { // since 3.3.2
    super.init(p, layerName, layerIndex, oldLayer);
    switch (p.getPlotTypeID()) {
      case PlotFile.T_CONT:  setSurface(Surface.CONT); break;
      case PlotFile.T_MESH:  setSurface(Surface.MESH); break;
      case PlotFile.T_POINT: setSurface(Surface.DOTS); break;
    }

    // This code comes from the the old underlay(Layer2D) function. The "underlay"
    // feature makes it so that following a pipe-restart the data "appends" to the
    // previous raster rather than clearing the plot and restarting from the top.
    // This can be seen in the %TEST_RESTART_BLOCKING when the pipe input is
    // changed. There realy should be a flag to enable/disable this feature as
    // not all users will want it.
    if (oldLayer instanceof Layer2D) {
      Layer2D prev = (Layer2D)oldLayer;
      index=frame*Math.min(size-1,prev.index/prev.frame);

      // since 3.7.0 for BUG 2991
      // Note: The setFile method, which is called before this method (init) will
      // have set data.size to LPB.  Although LPB is always less that LPS, the index
      // (for the next position draw) may be toward the bottom of the screen.  The
      // data.size (number of frames to be drawn this time) should not be greater
      // than the number of frames left on the screen
      int framesLeft = (mindex-index)/frame;
      // Note: oldLayer.data.size can be greater than framesLeft
      // old - no longer valid - int oldFramesLeft = oldLayer.data.size;
      data.size = Math.min(data.size, framesLeft);

      pass = prev.pass;
      if (prev.mimage!=null) {
        if (buf==null) getBuffer();
        if (mimage==null) getImage();
        double factor = (double)prev.frame/(double)frame;
        int jmax=prev.mindex-1;
        for (int i=0; i<mindex; i++) {
          int j = Math.min(jmax,(int)(0.5+(double)i*factor));
          buf[i] = prev.buf[j];
        }
      }
      index1 = index;
    }
  }

  @Override
  public int getDefaultComplexMode () { // since 3.3.2
    return MPlot.M_MAG;
  }

  @Override
  public int getDefaultView () { // since 3.3.2
    if (pf == null) return super.getDefaultView();
    return (pf.getYUnits()==Units.TIME)? MPlot.V_IYX : super.getDefaultView();
  }

  @Override
  public boolean setFile (PlotFile pf, Table tab, Args MA) {
    setFile(pf);
    if (typeClass!=2) return false;
    if (MA.find("/FORCEORDER")) force_proc_order = MA.getState("/FORCEORDER", force_proc_order);   // switch to back out changes for Bugs 1544, 2091, and 1254
    // Set colormap
    ncolors = MA.getL("NC",32);
    cmap = MColor.RAMP;
    if (pf.getXUnits()==Units.LONGITUDE) cmap=MColor.TERRAIN;
    cmap = MA.getSelectionIndex("CMAP",MColor.colorMapList,cmap);
    cmap = MA.getSelectionIndex("/CMAP",MColor.colorMapList,cmap);
    if (MA.isPresent("/COLORS")) {
      String colorArray = MA.getS("/COLORS");
      colors = MColor.getColorMap(colorArray,ncolors);
    } else {
      colors = MColor.getColorMap(cmap,ncolors);
    }

    // if the user has done /SCALEZ=LAYER, scale the layer Z values on the layer
    String scaleBy = MA.getS("/SCALEZ", "PLOT"); // since 2.7.2 for BUG 1901
    if (scaleBy.equals("LAYER")) scaleZ = true;
    else                         scaleZ = false;

    // Set the axis
    setXAxis( pf.getXStart(), pf.getXDelta() );
    setYAxis( pf.getYStart(), pf.getYDelta() );
    setFrame( pf.getXFrame() );

    // Determine LinesPerScreen (lps) and see if paging needs to be turned on (this can be confusing)
    double fsize = pf.getSize();                                       // Number of frames in file
    if (pf instanceof DataFile) {                                      // make sure it is a DataFile before casting
      DataFile df = (DataFile)pf;
      if ((df.flags&BaseFile.WRAP)!=0) fsize = df.getOutByte(0)/df.dbpe;  // use expected size if WRAP
      if (useRaster && df.getRasterTuneAdjust()) {
        TuneAdjust tuneAdjustments = df.getTuneAdjustments();
        if ((tuneAdjustments!=null) && tuneAdjustments.isTuneAdjustAvailable()) {
          // switch to IF axis, even if originally given RF XStart
          setXAxis(tuneAdjustments.getXStartIF(), pf.getXDelta());
        }
      }
    }
    int ts = (int)Math.min(fsize,10e6);                                // total size of file
    int ss = Math.max(8,MP.iy21);                                      // screen size
    int _lps = Math.min(ts,ss);                                        // lines per screen
    if (realtime) _lps=ss;                                             // Realtime mode shows 1 frame-per-pixel
//    else if ((MP.options & MPlot.O_ALL) != 0) lps = ts;                // Always show everything when /ALL is used
//    else if (pf.getUnits()!=Units.TIME && ts*frame<1024*1024) lps=ts;  // Show all if page mode is not necessary for memory
    else if ((long)ts*frame<1024*1024) _lps=ts;                        // Show all if page mode is not necessary for memory
    int lpsSwitch =MA.getL("/LPS",0);                                  // User can override defaults (0 - do not override - base on screen size) Since 3.7.0 for Bug 2991
    if (lpsSwitch > 0) _lps=lpsSwitch;                                 // User set to > 0, so use specified value
    else if (lpsSwitch < 0) _lps=DEFAULT_LPS;                          // User set it to -1, use default
    isPaged = !realtime && _lps<ts;                                    // Page if lps is less than total
    // ORIG: lpi=_lpb=MA.getL("/LPB",16);                              // lines per image, buffer
    lpi=lpb=MA.getL("/LPB",Math.min(16,_lps));                         // lines per image, buffer
    if (lpb > _lps) {                                                  // If user set LPB > LPS ERROR out, as this is an invalid setting (Since 3.7.0 for Bug 2991)
      throw new MidasException("Layer2D.setFile invalid setting LPB:"+lpb+" > LPS:"+_lps);
    }
    if (isPaged) {
       _lps=MA.getL("/LPB",DEFAULT_LPS);                               // lines per buffer
       if ((lpsSwitch > 0) && (lpsSwitch != _lps)) {                   // warn user if /LPS value was overridden because in paging mode  (Since 3.7.0 for Bug 2991)
         warning("Layer2D.setFile in Paging mode user specified LPS is overrriden by /LPB or default LPS");
       }
    }
    if (!realtime) lpi=lpb=_lps;                                       // lines per image = lines per buffer since 3.7.2
    setSize(_lps);                                                     // Sets size=lpb, size is used when drawing
    // If the archivedFrames array is null, then this means it has not been initialized and therefore must be initialized
    // If the LPS changes and is different than the size of the archivedFrames array (number of rows), then
    // the array must be reinitialized. Otherwise, the array should not be reinitialized.
    if (isPipeWithCuts()) {
      if (MP.archivedFrames == null || MP.archivedFrames.length != size) {
        MP.archivedFrames = new float[size][];
        MP.archivedFramesWriteIdx = 0; // Reset circular buffer pointer to 0 since initializing/reinitializing the array
      }
    }
    setData (pf.getDataBuffer(lpb));                                   // Get a data buffer
    compression = CMP_MAX;
    xIsTime = (pf.getXUnits()==Units.TIME);
    mindex = frame*size;
    // System.out.println(" lpb="+lpb+" lps="+lps+" ts="+ts+" ss="+ss+" lpi="+lpi+" isPaged="+isPaged+" realtime="+realtime);
// JGS: the fast float buffer method does not work for non SF data (e.g. SB). see Bug 1293 for details -NTN
//    if (isPaged && Native.useCast) ffbuf = fbuf = new float[lpb*frame*data.spa];
    update();
    return true;
  }

  /** Get the tune adjustments associated with certain 2000 files
   *  for use by MPlot
   *  @return TuneAdjust object holding tune adjustments
   */
  @InternalUseOnly
  public TuneAdjust getTuneAdjustments () {
    if (useRaster && (pf!=null) && (pf instanceof DataFile)) {
      return ((DataFile)pf).getTuneAdjustments();
    }
    else return null;
  }

  @Override
  public void update () {
    setUse();
    if (MP.xvy!=xvy && mimage!=null && ndata>0) getImage();
    if (useRaster   && mimage==null && ndata>0) getImage();
    // auto set phase colormap
    if (MP.mode==0 || MP.mode==MP.modeLast || cmap==MColor.GRAYSCALE) { }
    else if (MP.mode==MPlot.M_PHASE) { dcmap=cmap; setColorMap(MColor.WHEEL); }
    else if (MP.modeLast==MPlot.M_PHASE) setColorMap(dcmap);
    if (ndata>0 && !realtime) { index=index1=pass=0; process(); }
  }

  @Override
  public void setSurface (int value) {
    super.setSurface(value);
    setUse();
  }

  @Override
  public void setColorMap (Color[] colors) {
    super.setColorMap(colors);

    if (mimage!=null) {
      getImage();        // This is reset to avoid issue "ghost" colors
      mimage.setColors (colors);
    }
    reScale = true;  // Flag to reload the raster buffer
  }

  @Override
  public boolean clear () {
    clearIndexValues();
    buf      = null;
    imageBuf = null;
    mimage   = null;
    return true;
  }

  /** Private method used by both clear() and reset(). */
  private void clearIndexValues () {
    ndata=index=index1=pass=indexAdjust=0;
    cmpLeft = 0;
  }

  @Override
  public boolean reset () {
    boolean inReset = isReset;
    boolean reset = super.reset();
    if (reset && !inReset) clearIndexValues();
    yCompressionWarn = true;
    cmpLeft = 0;
    return reset;
  }

  @Override
  public void process () {
    ndata = indexAdjust = 0;
    if (data==null) return;
    process (data.size);
  }

  @Override
  public void process (int ndo) {
    ndata = indexAdjust = 0;
    if (data==null) return;
    ndata = indexAdjust = ndo*data.ape;
    if (ndata<=0) return;

    if (directColor) {
      imageBuf = data2la(data,ndata,imageBuf,0);
      if (alpha >= 0) MColor.applyAlpha(imageBuf, alpha);
    } else {
      fbuf = data2fa(data,ndata,fbuf,0,MP.mode);
      if (isPipeWithCuts()) {
        for (int i = 0; i < ndata; i+=frame) {
          if (ndata-i < frame) MP.archivedFrames[MP.archivedFramesWriteIdx++] = Arrays.copyOfRange(fbuf, i, ndata);
          else                 MP.archivedFrames[MP.archivedFramesWriteIdx++] = Arrays.copyOfRange(fbuf, i, i+frame);
          MP.archivedFramesWriteIdx %= size;
        }
      }
    }

    if (pf instanceof DataFile) {
      DataFile df = (DataFile)pf;
      if (timeLineSave != df.timeLine) {
        timeLineSave = df.timeLine;
        indexSave    = (double)(index + mindex*pass)/(df.ape);
      }
    }
    adjustIndex();  // increment the index
    awaiting_data = false;  // indicates buffer needs to be drawn / plotted
  }

  /** Checks the data buffer to see if the data in it has been drawn. If not, calls
      {@link MPlot#refresh(int)} to draw the data if the plot is visible. If the plot
      is not visible, calls {@link #draw(int)} directly.
      @param mode The draw mode (always 0)
      @return {@link Layer#getData(int)}
      @since NeXtMidas 2.9.0 (See Bug 1254 and Bug 1544 and Bug 2091)
   */
  @Override
  public int getData (int mode) {
    if (force_proc_order && !(awaiting_data)) {   // data in buffer has not been drawn
      if (MP.isPanelVisible()&& isVisible()) MP.refresh(Drawable.DATA);  // have MPLot draw the data if visible and enabled
      else  draw(Drawable.DATA);  // for hidden panels, draw directly to avoid blocked pipes
    }
    return super.getData(mode);   // data in buffer has been drawn, so proceed
  }

  @Override
  public void findRange () {
    if (MP.view==MPlot.V_3DGRAPH) {
      findGraphRange();
      return;
    }

    boolean findZ1 = !getUseLocalZMin();  // Find z1 (true) or keep current (false)?
    boolean findZ2 = !getUseLocalZMax();  // Find z2 (true) or keep current (false)?

    x1 = xstart - xdelta/2;
    x2 = x1 + frame*xdelta;
    if (x1>x2) { double t=x1; x1=x2; x2=t; }
    y1 = ystart - ydelta/2;
    y2 = y1 + size*ydelta;
    if (y1>y2) { double t=y1; y1=y2; y2=t; }

    if (MP.mode==MPlot.M_PHASE) {
      a1 = -180; a2 = 180;
      if (findZ1) z1 = a1;
      if (findZ2) z2 = a2;
      return;
    }

    if (directColor || fbuf==null) {
      if (findZ1) z1 = 0;
      if (findZ2) z2 = 1;
      return;
    }

    if (findZ1) {
      z1 = fbuf[0];
      for (int i=1; i<ndata; i++) {
        if (fbuf[i]<z1) z1=fbuf[i];
      }
    }

    if (findZ2) {
      z2 = fbuf[0];
      for (int i=1; i<ndata; i++) {
        if (fbuf[i]>z2) z2=fbuf[i];
      }
    }

    autoScale4FindRange(z1, z2); // since 3.3.3 use helper method in super class
  }

  /** Get range centered on 0,0,0 if this is to be drawn on a graph
      @since NeXtMidas 3.9.3 for NXM-3500 */
  private void findGraphRange() {
    double xMaxAbs;
    double yMaxAbs;
    double zMaxAbs;
    x1 = xstart - xdelta/2;
    x2 = x1 + frame*xdelta;
    xMaxAbs = Math.max(Math.abs(x1), Math.abs(x2));
    x1 = -xMaxAbs;
    x2 = xMaxAbs;
    y1 = ystart - ydelta/2;
    y2 = y1 + size*ydelta;
    yMaxAbs = Math.max(Math.abs(x1), Math.abs(x2));
    y1 = -yMaxAbs;
    y2 = yMaxAbs;

    if (fbuf==null) {
      z1 = 0;
      z2 = 1;
      return;
    }

    zMaxAbs = Math.abs(fbuf[0]);
    for (int i=1; i<ndata; i++) {
      double absZ = Math.abs(fbuf[i]);
      if (absZ>zMaxAbs) zMaxAbs=absZ;
    }
    z1 = -zMaxAbs;
    z2 = zMaxAbs;
  }

  @Override
  public void findFullRange () { findFullRange(pf); }

  @Override
  public void findFullRange (PlotFile pf) {
    fx1 = x1; fx2 = x2;
    fy1 = y1; fy2 = y2;
    fz1 = z1; fz2 = z2;
    if (pf!=null && !realtime) {
      fy1 = pf.getYStart();
      double fsize = pf.getSize();
      if (pf instanceof DataFile) { // make sure it is a DataFile before casting
        DataFile df = (DataFile)pf;
        if ((df.flags&BaseFile.WRAP)!=0) fsize = df.getOutByte(0)/df.dbpe;  // use expected size if WRAP
      }
      fy2 = fy1 + (fsize-1)*pf.getYDelta();
      if (fy1>fy2) { double t=fy1; fy1=fy2; fy2=t; }
    }
  }

  /** Set raster image compression mode.
   *  @param value The compression mode, see {@link #compressionList} for available modes.
   */
  public void setCompression (String value) {
    compression = Parser.find(compressionList,value,compression,0,Parser.E_WARNING);  // warning added for Bug 760
    refresh();
  }

  /** Get current raster image compression mode.
   *  @return The compression mode, see {@link #compressionList} for available modes.
   */
  public String getCompression() {
    return Parser.get(compressionList,compression);
  }

  private void setUse() {
    useRaster  = (stype&Surface.FILL) != 0;
    useContour = (stype&Surface.CONT) != 0;
    usePoly    = (stype&(Surface.MESH|Surface.DOTS)) != 0 || !MP.is2D;
    if (usePoly) useRaster=false;
  }

  /** Recomputes the bounds of the layer. */
  private boolean recomputeBounds () {
    boolean outofrangeX = false;    // need separate booleans for X and Y
    boolean outofrangeY = false;    // recomputation is independent of tests
    if (MP.view == MPlot.V_LATLONCONTINUOUS) {
      // Supporting continuous Lat Lon since 3.9.0 for NXM-3284
      double effectiveRx1 = MP.rx1;
      double effectiveRx2 = MP.rx2;
      if (MP.rx2 > MPlot.LONGITUDE_DEFAULT_MAX) {
        effectiveRx2 -= MPlot.LONGITUDE_RANGE;
        effectiveRx1 -= MPlot.LONGITUDE_RANGE;
      }
      if (xdelta>0) {
        double xend = xstart+frame*xdelta; // ende of 2D figure
        outofrangeX = (xstart>MP.rx2 || xend<MP.rx1);
        // changed to use effectiveXStart rather than xstart as we do not want to permanently change the value since 3.9.2
        double continuousXstart = xstart;
        boolean outofrangeXC = (xstart>effectiveRx2 || (xend<effectiveRx1)); // extended range out-of-range
        if (outofrangeX && !outofrangeXC) continuousXstart+=MPlot.LONGITUDE_RANGE; // adjust xstart to put it in extended range - ?? not here?
        outofrangeX = outofrangeX & outofrangeXC;
        jx1 = Math.max(jx1,(int)((MP.rx1-continuousXstart)/xdelta+.49));
        jx2 = Math.min(jx2,(int)((MP.rx2-continuousXstart)/xdelta+1.51));
       } else {
        outofrangeX = (xstart<MP.rx1 || xstart+frame*xdelta>MP.rx2);
        jx1 = Math.max(jx1,(int)((MP.rx2-xstart)/xdelta+.49));
        jx2 = Math.min(jx2,(int)((MP.rx1-xstart)/xdelta+1.51));
       }
       if (ydelta>0) {
        outofrangeY = (ystart>MP.ry2 || ystart+size*ydelta<MP.ry1);
        jy1 = Math.max(jy1,(int)((MP.ry1-ystart)/ydelta+.49));
        jy2 = Math.min(jy2,(int)((MP.ry2-ystart)/ydelta+1.51));
       } else {
        outofrangeY = (ystart<MP.ry1 || ystart+size*ydelta>MP.ry2);
        jy1 = Math.max(jy1,(int)((MP.ry2-ystart)/ydelta+.49));
        jy2 = Math.min(jy2,(int)((MP.ry1-ystart)/ydelta+1.51));
       }
       // more efficient to plot full image and let clip
       // plus this gets around strange JDK1.5.02+ partial wide image plot hang
       if (jx1<4) jx1=0;
       if (frame-jx2<4) jx2=frame;
    }
    else if (MP.is2D) {
     if (xdelta>0) {
      outofrangeX = (xstart>MP.rx2 || xstart+frame*xdelta<MP.rx1);
      jx1 = Math.max(jx1,(int)((MP.rx1-xstart)/xdelta+.49));
      jx2 = Math.min(jx2,(int)((MP.rx2-xstart)/xdelta+1.51));
     } else {
      outofrangeX = (xstart<MP.rx1 || xstart+frame*xdelta>MP.rx2);
      jx1 = Math.max(jx1,(int)((MP.rx2-xstart)/xdelta+.49));
      jx2 = Math.min(jx2,(int)((MP.rx1-xstart)/xdelta+1.51));
     }
     if (ydelta>0) {
      outofrangeY = (ystart>MP.ry2 || ystart+size*ydelta<MP.ry1);
      jy1 = Math.max(jy1,(int)((MP.ry1-ystart)/ydelta+.49));
      jy2 = Math.min(jy2,(int)((MP.ry2-ystart)/ydelta+1.51));
     } else {
      outofrangeY = (ystart<MP.ry1 || ystart+size*ydelta>MP.ry2);
      jy1 = Math.max(jy1,(int)((MP.ry2-ystart)/ydelta+.49));
      jy2 = Math.min(jy2,(int)((MP.ry1-ystart)/ydelta+1.51));
     }
     // more efficient to plot full image and let clip
     // plus this gets around strange JDK1.5.02+ partial wide image plot hang
     if (jx1<4) jx1=0;
     if (frame-jx2<4) jx2=frame;
    }

    if (!MP.is2D) {
      // ignore
    } else if (MP.xvy) {
      ixn = (int)(MP.myy*(ystart+(index/frame-.5)*ydelta)+MP.myb);
      if (MP.myy*ydelta<0) ixn-=1;
    } else {
      iyn = (int)(MP.myy*(ystart+(index/frame-.5)*ydelta)+MP.myb);
      if (MP.myy*ydelta<0) iyn-=1; else iyn+=1;
    }
    return (outofrangeX||outofrangeY);
  }

  /** Increments the index, which is where to draw the next line.
      Moved to this method from process() for Bug 1544.
   */
  public void adjustIndex () {
    // setup for draw() to finish scaling the new data
    // draw processes from index1 to index
    if (index>=mindex) { index-=mindex; pass++; }
    if (isPaged)       { index=index1=0; }
    index += indexAdjust;
    indexAdjust = 0;  // to prevent further adjustments of index during zooms, unzooms, etc
    int framesLeft = (mindex-index)/frame;
    if (framesLeft<=0 || framesLeft>=lpb || isPaged) { data.size = lpb; }
    else { data.size = framesLeft; }
  }

  @SuppressWarnings("deprecation")
  @Override
  public void draw (int flag) {
    super.draw(flag);
    if (ndata==0 && index==0 && pass==0) return;
    double rz1 = (MP.is2D)? MP.rz1: MP.orz1; // Min z-axis range value (special case for 3D view)
    double rz2 = (MP.is2D)? MP.rz2: MP.orz2; // Max z-axis range value (special case for 3D view)
    boolean scaleZMin = scaleZ && ((MP.scale&MPlot.S_MIN)!=0 || getUseLocalZMin());
    boolean scaleZMax = scaleZ && ((MP.scale&MPlot.S_MAX)!=0 || getUseLocalZMax());
    if (MP.is2D && scaleZMin) rz1=z1;
    if (MP.is2D && scaleZMax) rz2=z2;
    boolean forceReread = (rz1!=lrz1 || rz2!=lrz2 || compression!=lcmp || reScale); // re-read if z-scale or compression changes
    jx1=0; jx2=frame; jy1=0; jy2=size;
    if ((index != index1) || (super.size == 1) || forceReread) {
      if (index1>=mindex) index1-=mindex;
      jy1 = index1/frame; jy2 = jy1 + (index-index1)/frame;
      if (cmap==MColor.TERRAIN) {
        // insure min = -max for alt=0 cmap split
        scl0 = (float)Math.min(rz1,-rz2);
        scl1 = ncolors/(-2*scl0);
      } else {
        scl0 = (float)rz1;
        scl1 = (float)(ncolors/(rz2-rz1));
      }
      if (index==0) // index might be zero if adjustIndex called while plot was hidden
        loadRasterBuffer (index1,mindex);
      else
        loadRasterBuffer (index1,index);
      index1 = index;
    }
    else if (ndata==0) {
      // no new raster data, so no lines need to be drawn
      // since 3.7.3 for NXM-3088 - fix updated in 3.9.4
      if (Tolerance.equalsWithTolerance(MP.fry2, MP.ory2)) jy2 = 0;
    }

    if (flag==Drawable.FULL) { jy1=0; jy2=size; }
    boolean outofrange = recomputeBounds() && MP.view!=MPlot.V_LATLONCONTINUOUS; // Added check for latlon continuous as it will never be out of range in that mode since 3.9.1
    if (directColor && usePoly) {
      warning("Layer2D: 3D not supported with USECOLOR=true");
      outofrange = true;
    }
    if (!outofrange && !MP.cancel) {
      if (useRaster) drawRaster();
      if (useContour) drawContour();
      if (usePoly) drawPoly();
      awaiting_data = true;  // indicates buffer has been drawn so we can accept new data
    }
    lrz1=rz1; lrz2=rz2; lcmp=compression;
    reScale = false;
  }

  protected void loadRasterBuffer (int i, int j) {
    int b, nc=ncolors;
    if (buf==null) getBuffer();
    if (directColor) return; // do not need to setup color map when using direct color
    if (j>buf.length) j=buf.length;
    if (fbuf==null) return;  // return b/c no data loaded/available (Bug 1663)
    for (int k=0; (i<j && i<buf.length && k<fbuf.length);) {
      b = (int)( (fbuf[k++]-scl0)*scl1 );
      if (b<0) b=0;
      if (++b>nc) b=nc;
      buf[i++]=(byte)b;
    }
  }

  private void drawPoly () {
    if (MP.gc==null) return;
    int i,j,k,kx,ky,nx,ny,kx1,kx2,kxd,ky1,ky2,kyd;
    Pix p0,p1; Graphics gc = MP.gc;
    Color mc = new Color(0x505050);
    int[] tx = new int[4],  ty = new int[4];
    boolean fill = (stype&Surface.FILL) != 0;
    boolean mesh = (stype&Surface.MESH) != 0;
    boolean dots = (stype&Surface.DOTS) != 0;
    int tileOrder = 1;
    if (pa==null) pa = new Pix(frame,Pix.CHECK);
    if (pb==null) pb = new Pix(frame,Pix.CHECK);
    if (dbuf==null) dbuf = new double[frame*3];
    if (MP.view==MPlot.V_XYZ) { i=0;  // set tiling order
      dbuf[i++]=xstart; dbuf[i++]=ystart; dbuf[i++]=0;
      dbuf[i++]=xstart+(frame-1)*xdelta; dbuf[i++]=ystart; dbuf[i++]=0;
      dbuf[i++]=xstart+(frame-1)*xdelta; dbuf[i++]=ystart+(size-1)*ydelta; dbuf[i++]=0;
      dbuf[i++]=xstart; dbuf[i++]=ystart+(size-1)*ydelta; dbuf[i++]=0;
      MP.viewer.rwc2pix(dbuf,0,3,4,pa); float[] z=pa.z;
      if (z[0]<=z[1] && z[0]<=z[2] && z[0]<=z[3]) tileOrder=1;
      if (z[1]<=z[0] && z[1]<=z[2] && z[1]<=z[3]) tileOrder=2;
      if (z[2]<=z[0] && z[2]<=z[1] && z[2]<=z[3]) tileOrder=3;
      if (z[3]<=z[0] && z[3]<=z[1] && z[2]<=z[3]) tileOrder=4;
    }
    nx=jx2-jx1; ny=jy2-jy1;
    if (tileOrder==1 || tileOrder==4) {
      kx1=jx1; kx2=jx2; kxd=1;
    } else {
      kx1=jx2-1; kx2=jx1-1; kxd=-1;
    }
    if (tileOrder==1 || tileOrder==2) {
      ky1=jy1; ky2=jy2; kyd=1;
    } else {
      ky1=jy2-1; ky2=jy1-1; kyd=-1;
    }
    double y = ystart + ky1*ydelta;
    for (kx=kx1,j=ky1*frame+kx1,i=0; kx!=kx2; kx+=kxd,j+=kxd) {
      dbuf[i++] = xstart + kx*xdelta;
      dbuf[i++] = y;
      dbuf[i++] = fbuf[j];
    }
    MP.viewer.rwc2pix(dbuf,0,3,frame,pa);
    gc.setColor(mc);
    if (mesh) gc.drawPolyline (pa.x, pa.y, nx);
    for (ky=ky1+kyd,k=0; ky!=ky2 && !MP.cancel; ky+=kyd,k=1-k) {
      y = ystart + ky*ydelta;
      for (kx=kx1,j=ky*frame+kx1,i=0; kx!=kx2; kx+=kxd,j+=kxd) {
        i++; dbuf[i++] = y; dbuf[i++] = fbuf[j];
      }
      if (k==0) { p0=pa; p1=pb; }
      else      { p0=pb; p1=pa; }
      MP.viewer.rwc2pix(dbuf,0,3,nx,p1);
      if (fill) for (j=ky*frame+kx1,i=0; i<nx-1; i++,j+=kxd) {
        tx[0]=p0.x[i]; tx[1]=p0.x[i+1]; tx[2]=p1.x[i+1]; tx[3]=p1.x[i];
        ty[0]=p0.y[i]; ty[1]=p0.y[i+1]; ty[2]=p1.y[i+1]; ty[3]=p1.y[i];
        if (MP.view==MPlot.V_GEODETIC && (tx[0]==0||tx[1]==0||tx[2]==0||tx[3]==0) ) continue;
        gc.setColor(colors[(int)buf[j]]);
        gc.fillPolygon(tx,ty,4);
      }
      if (mesh) {
        gc.setColor(mc);
        gc.drawPolyline (p1.x, p1.y, nx);
        for (i=0; i<nx; i++) {
          gc.drawLine(p0.x[i],p0.y[i],p1.x[i],p1.y[i]);
        }
      }
    }
  }

  private void drawContour () {
    if (ndata<=0) return;
    int i,j,k,npts,ncol,ic;
    float[] fout, ftmp = new float[4];
    double z;

    Line lin = line.copy();

    if (contour==null) {
      contour = new Contour();
      contour.setXAxis (xstart,xdelta,frame);
      contour.setYAxis (ystart,ydelta,size);
    }
    contour.setBounds (jx1,jy1,jx2,jy2);
    for (ic=0; ic<ncolors && !MP.cancel; ic++) {
      lin.setColor(colors[ic+1]);
      z = scl0 + ic/scl1;
      contour.setZAxis (z,z,1);
      contour.process (fbuf);
      fout = contour.out;
      for (j=0; j<contour.nout && !MP.cancel; j=k) {
        npts = (int)fout[j++];
        ncol = (int)fout[j++];
        k = j + (2+npts)*2;
        if (MP.is2D) {
          if (fout[j++]>=MP.rx2) continue;
          if (fout[j++]>=MP.ry2) continue;
          if (fout[j++]<=MP.rx1) continue;
          if (fout[j++]<=MP.ry1) continue;
        } else {
          i = MP.viewer.rwc2pix (fout,j,2,2,MP.tpix);
          if (i==0) { // try alternate corners
            ftmp[0]=fout[j]; ftmp[1]=fout[j+3];
            ftmp[2]=fout[j+2]; ftmp[3]=fout[j+1];
            i = MP.viewer.rwc2pix (ftmp,0,2,2,MP.tpix);
          }
          j += 2*2;
          if (i==0) continue;
        }
        MP.drawLine (fout,j,2,npts,lin);
      }
    }
  }

  private void getImage() {
    int width, height;
    if (MP.xvy) { width = lpi;   height = frame; }
    else        { width = frame; height = lpi;   }
    if (directColor) { mimage = new MImage(MP,width,height,imageBufv);   }
    else             { mimage = new MImage(MP,width,height,bufv,colors); }
    xvy = MP.xvy;
  }

  protected void getBuffer() {
    buf = new byte[frame*size];
    if (directColor) { imageBufv = new int[frame*lpi];  cmpbufv = new int[frame]; }
    else             { bufv      = new byte[frame*lpi]; cmpbuf = new byte[frame]; }
  }

  /** Check if LPI (which can be set via /LPB) is less than ycmp and warn user
      that data may not appear unless user zooms into the plot.
      Only display this message once per screen resize (e.g. after a reset).
      NTN: 2008-12-03: Disable warning for real-time rasters.
   */
  private void checkYCompression (int ycmp) {
    if ( yCompressionWarn && ycmp>lpi && !realtime) {
      yCompressionWarn = false; // hide this message until the next reset()
      warning("Y-compression ["+ycmp+"] > /LPB ["+lpi+"] which may cause data not to appear.");
    }
  }

  private void drawRaster () {
    if (MP.gc==null) return;
    int ix1,ix2,iy1,iy2,xcmp,ycmp;
    boolean cmp = (compression!=CMP_NONE);
    if (buf==null) getBuffer();

    if (mimage==null ) getImage();  // Note: uses files frame, bufv and imageBufv

    // make local copies for thread safety -  - Since 3.7.0 for Bug 2991
    int    l_frame     = frame;    // local copy - Frame size of the layer
    byte[] l_buf       = buf;      // local copy - Scaled data (z = buf[i]/scl1 + scl0)
    byte[] l_bufv      = bufv;     // local copy - Rotated version of buf when in xvy mode
    int[]  l_imageBuf  = imageBuf; // local copy - image buffer containing actual colors w/ alpha values of raster (mirrors buf[])
    int[]  l_imageBufv = imageBufv;// local copy - chunk of image buffer that is copied to mimage.image (mirrors bufv[])
    int l_jx1 = jx1;  // local copy - Pixel xMin
    int l_jx2 = jx2;  // local copy - Pixel xMax
    int l_jy1 = jy1;  // local copy - Pixel yMin
    int l_jy2 = jy2;  // local copy - Pixel xMax
    // save buffer lengths
    int l_bufLen       = (l_buf !=null) ? l_buf.length : 0;
    int l_bufvLen      = (l_bufv !=null) ? l_bufv.length : 0;
    int l_imageBufLen  = (l_imageBuf != null) ? l_imageBuf.length : 0;
    int l_imageBufvLen = (l_imageBufv != null) ? l_imageBufv.length : 0;

    MP.setColor(MP.theme.cfg);

    xcmp = (int)(Math.abs(1.0/(MP.mxx*xdelta)) + 0.95); // JGS: why 0.95?
    ycmp = (int)(Math.abs(1.0/(MP.myy*ydelta)) + 0.95); // JGS: why 0.95?
    checkYCompression(ycmp); // check to display warning message if ycmp>lpi  Note:uses field lpi

    int l_lpi = lpi;  // local copy - lines per image

    if (!MP.is2D) { } // ignore if not a 2D plot view
    else if (MP.xvy) {
      iy1 = (int)(MP.mxx*(xstart+(l_jx1-.5)*xdelta)+MP.mxb);
      iy2 = (int)(MP.mxx*(xstart+(l_jx2-.5)*xdelta)+MP.mxb);
      cmp  = cmp && (xcmp>1 || ycmp>1);
      for (int jyd,jy=l_jy1; jy<l_jy2; jy+=jyd) {
        jyd = l_jy2-jy; if (jyd>l_lpi) jyd=l_lpi;
        ix1 = (int)(MP.myy*(ystart+(jy-.5)*ydelta)+MP.myb);
        ix2 = (int)(MP.myy*(ystart+(jy+jyd-.5)*ydelta)+MP.myb);
        if (cmp) { // BUG 1048 - compression needed for X vs Y view
          if (directColor) { compressInt(l_imageBuf,jy*l_frame, l_imageBufv,0, l_frame,jyd, xcmp,ycmp, MP.xvy); } //Note: uses field lpi
          else             { compressByte(l_buf,jy*l_frame, l_bufv,0, l_frame,jyd, xcmp,ycmp, MP.xvy); } //Note: uses field lpi
        } else {
          transpose(jyd, jy);  // Note: alters fields buf, bufv,imageBuf, imageBufv
        }
        mimage.source.newPixels(0,0,jyd,l_frame);
        //MP.gc.drawImage (mimage.image, ix1,iy1,ix2,iy2, 0,jx1,jyd,jx2, mimage);  // changed to below for Bug 1918
        drawImage (mimage.image, ix1,iy1,ix2,iy2, 0,l_jx1,jyd,l_jx2,xcmp,ycmp, mimage);
      }
      if (realtime && index!=0 && ixn>MP.ix1 && ixn<MP.ix2-1) {
        int i1 = Math.max(MP.iy1,Math.min(iy1,iy2));
        int i2 = Math.min(MP.iy2,Math.max(iy1,iy2));
        MP.gc.drawLine (ixn,i1+1,ixn,i2-2);
      }
    }
    else {
      ix1 = (int)(MP.mxx*(xstart+(l_jx1-.5)*xdelta)+MP.mxb);
      ix2 = (int)(MP.mxx*(xstart+(l_jx2-.5)*xdelta)+MP.mxb);
      if (realtime) ycmp=1;
      cmp = cmp && (xcmp>1 || ycmp>1);
      for (int jyd,jy=l_jy1; jy<l_jy2; jy+=jyd) {
        jyd = l_jy2-jy; if (jyd>l_lpi) jyd=l_lpi;
        iy1 = (int)(MP.myy*(ystart+(jy-.5)*ydelta)+MP.myb);
        iy2 = (int)(MP.myy*(ystart+(jy+jyd-.5)*ydelta)+MP.myb);
        if (directColor) {
          if (cmp) compressInt(l_imageBuf,jy*l_frame, l_imageBufv,0, l_frame,jyd, xcmp,ycmp, MP.xvy); //Note: uses field lpi
          else     {
            int srcPos  = jy*l_frame;
            int maxSrcCopyLen  = l_imageBufLen - srcPos;
            int maxCopyLen = Math.min(maxSrcCopyLen, l_imageBufvLen);
            int copyLength = Math.min(jyd*l_frame, maxCopyLen);
            System.arraycopy(l_imageBuf,srcPos, l_imageBufv,0, copyLength);
            //System.arraycopy(l_imageBuf,jy*l_frame, l_imageBufv,0, jyd*l_frame);
          }
        } else {
          //System.out.println("Layer2D.drawRaster cmp"+cmp+" buf.length:"+buf.length+" srcPos:"+(jy*frame)+" length:"+(jyd*frame)+" bufv.length:"+bufv.length+" jy:"+jy+"jyd:"+jyd+" frame:"+frame+" xcmp:"+xcmp+" ycmp:"+ycmp);
          if (cmp) compressByte(l_buf,jy*l_frame, l_bufv,0, l_frame,jyd, xcmp,ycmp, MP.xvy);
          else     {
            int srcPos  = jy*l_frame;
            int maxSrcCopyLen  = l_bufLen - srcPos;
            int maxCopyLen = Math.min(maxSrcCopyLen, l_bufvLen);
            //int copyLength = Math.min(jyd*l_frame, maxCopyLen);
            int copyLength = jyd*l_frame;
            if (copyLength > maxCopyLen) {
              copyLength = maxCopyLen;
              warning("Avoiding ArrayIndexOutOfBounds in Layer2D.drawRaster bufLen:"+l_bufLen+" bufvLen:"+l_bufvLen+" l_frame:"+l_frame+" jy:"+jy+" jyd:"+jyd+" lpi:"+l_lpi+" jy1:"+l_jy1+" jy2:"+l_jy2+" index:"+index+" index1:"+index1+" mindex:"+mindex);
            }
            System.arraycopy(l_buf,srcPos, l_bufv,0, copyLength); // arraycopy(src, srcPos, dest, destPos, length)
            //System.arraycopy(l_buf,jy*l_frame, l_bufv,0, jyd*l_frame); // arraycopy(src, srcPos, dest, destPos, length)
          }
        }
        mimage.source.newPixels(0,0,l_frame,jyd);
        if (l_jx1>l_jx2 && MP.view != MPlot.V_LATLONCONTINUOUS)  continue; // Prevents exception if scroll bar is dragged out of range - except in lat lon continuous mode since 3.9.1
        //MP.gc.drawImage (mimage.image, ix1,iy1,ix2,iy2, jx1,0,jx2,jyd, mimage);  // changed to below for Bug 1918
        drawImage (mimage.image, ix1,iy1,ix2,iy2, l_jx1,0,l_jx2,jyd,xcmp,ycmp, mimage);
      }
      if (realtime && index!=0 && iyn>MP.iy1 && iyn<MP.iy2-1) {
        int i1 = Math.max(MP.ix1,Math.min(ix1,ix2));
        int i2 = Math.min(MP.ix2,Math.max(ix1,ix2));
        //System.out.println("Drawing Line. Index=" +index+ ", Thread= " +Thread.currentThread().getId());
        MP.gc.drawLine (i1+1,iyn,i2-2,iyn);
      }
    }
  }

  /** This method was added to allow for override via user's extended Layer2D.java. <br>
   *  <br>
   *  Without extending, just returns the MPlot Graphics Context method
   *  {@link Graphics#drawImage(Image,int,int,int,int,int,int,int,int,ImageObserver)} without
   *  regard to <code>xcomp</code> or <code>ycomp</code>.
   *  @param img      The specified image to be drawn.
   *  @param dx1      Destination x1.
   *  @param dy1      Destination y1.
   *  @param dx2      Destination x2.
   *  @param dy2      Destination y2.
   *  @param sx1      Source x1.
   *  @param sy1      Source y1.
   *  @param sx2      Source x2.
   *  @param sy2      Source y2.
   *  @param xcmp     Compression factor along x-axis.
   *  @param ycmp     Compression factor along y-axis.
   *  @param observer The observer to receive notifications.
   *  @return <code>false</code> if the image pixels are still changing; <code>true</code> otherwise.
   *  @since NeXtMidas 3.0.0
   */
  protected boolean drawImage (Image img, int dx1, int dy1, int dx2,
                               int dy2, int sx1, int sy1, int sx2, int sy2,
                               int xcmp, int ycmp, ImageObserver observer) {
    // Added in 3.9.1 to account for lat lon continuous - in that case just use existing code in MPlot.drawImage -
    // Can we replace previous functionality with the lat lon continuous functionality?
    if(MP.view==MPlot.V_LATLONCONTINUOUS) {
      MPoint point = new MPoint();
      MPoint point2 = new MPoint();
      double[] rwc1 = new double[3];
      double[] rwc2 = new double[3];

      point.x=dx1;
      point.y=dy1;
      point2.x=dx2;
      point2.y=dy2;

      MP.viewer.pix2rwc(point, rwc1);
      MP.viewer.pix2rwc(point2, rwc2);
      double xstartToUse = xstart > 180 ? xstart - MPlot.LONGITUDE_RANGE : xstart;
      MP.drawImage(GraphicsUtil.toBufferedImage(mimage.image), rwc1[1], rwc2[1], xstartToUse + xdelta*mimage.w, xstartToUse);
      return true;
    }
    else {
      return MP.gc.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, observer);

    }
  }

  /** Transpose buffer from Y vs X to X vs Y for image draw. Only used when
      in X vs Y view. Note: This method was extracted from drawRaster()
      @param jyd Number of y columns to transpose.
      @param jy  Current Y column to start transposing data from (zero-based)
      @since NeXtMidas 2.5.5
   */
  private void transpose(int jyd, int jy) {

    // make local copies for thread safety -  - Since 3.7.0 for Bug 2991
    int    l_lpi       = lpi;      // local copy - lines per image
    int    l_frame     = frame;    // local copy - Frame size of the layer
    byte[] l_buf       = buf;      // local copy - Scaled data (z = buf[i]/scl1 + scl0)
    byte[] l_bufv      = bufv;     // local copy - Rotated version of buf when in xvy mode
    int[]  l_imageBuf  = imageBuf; // local copy - image buffer containing actual colors w/ alpha values of raster (mirrors buf[])
    int[]  l_imageBufv = imageBufv;// local copy - chunk of image buffer that is copied to mimage.image (mirrors bufv[])

    int jyf = jy*l_frame;     // transpose buffer for image draw
    for (int i=0; i<l_frame; i++) {
      int ko = i*l_lpi;
      int ki = jyf+i;
      for (int j=0; j<jyd; j++,ki+=l_frame,ko++) {
        if (directColor) { l_imageBufv[ko] = l_imageBuf[ki]; }
        else             { l_bufv[ko]      = l_buf[ki];      }
      }
    }
  }

  /** Any changes/fix to this method should be mirrored in
      {@link #compressInt(int[], int, int[], int, int, int, int, int, boolean)}.
      Note: the transpose flag was added to fix BUG 1048 (when in view is X vs Y)
      @param buf   input buffer to compress data
      @param boff  offset into input buffer to start reading from
      @param bufv  output buffer for the compressed data
      @param bvoff offset into output buffer to start writing from? (JGS: this is not used?)
      @param nx    number of cells in x direction
      @param ny    number of cells in y direction to process
      @param icmp  number of bytes to compress in i (x) direction
      @param jcmp  number of bytes to compress in j (y) direction
      @param transpose set to true if we want to transpose the x & y on the output buffer
   */
  private void compressByte (byte[] buf, int boff, byte[] bufv, int bvoff,
                             int nx, int ny, int icmp, int jcmp, boolean transpose) {

    // make local copies for thread safety -  - Since 3.7.0 for Bug 2991
    int   l_lpi     = lpi;     // local copy - lines per image
    byte[] l_cmpbuf =  cmpbuf; // local copy - buffer for doing y compression larger than LPI

    byte b1,b2,bm;
    int off;      // offset into buf
    int ncells;   // number of cells (aka bins) in a block
    int maxIndex; // max index into buffers
    int idx;      // index into bufv
    int imid = icmp>>1;
    if (icmp<1) icmp = 1; // BUG 1330 prevent infinite loop (if we are doing zero compression in x direction)
    if (icmp>nx) {
      if ((compressByteWarn++ % 100) == 0) { // display warning every ith time
        warning("compressByte() icmp("+icmp+") > nx (" + nx + "), resetting to nx. count="+compressByteWarn);
      }
      icmp = nx; // fix for BUG 1684
    }

    // System.out.println("compressByte() boff="+boff+" bvoff="+bvoff+" nx="+nx+" ny="+ny+" icmp="+icmp+" jcmp="+jcmp+" transpose="+transpose);
    switch (compression) {
      case CMP_MAX:                    // compute block windowed maximum
        for (int iy=0; iy<ny; iy++) {  // compress in the x direction
          off      = iy*nx + boff;
          maxIndex = Math.min(off+nx, buf.length); // make sure maxIndex <= buf.length
          for (int ix=off; ix<maxIndex; ix+=icmp) {
            bm     = buf[ix];
            ncells = (ix+icmp > maxIndex)? maxIndex - ix : icmp;
            for (int i=ix+1; i<ix+ncells; i++) if (buf[i]>bm) bm=buf[i]; // calc max for block

            if (transpose) { idx = (ix-off)*l_lpi+iy; }
            else           { idx = ix-boff; }
            bufv[idx] = bm; // save compressed result into image buffer
          }
        }
        if (jcmp>1) {
          int y_inc;        // how many cells to increment one cell in y-direction
          int iy_block_inc; // how many cells to increment for whole block of cells in y-direction
          int ycmp = Math.min(ny,jcmp);
          boolean firstycmp = false;
          if (jcmp>lpi) {  // attempt y compression across paged buffers
            if (cmpLeft>0) { cmpLeft -= ycmp; }
            else           { cmpLeft = jcmp - ycmp; firstycmp = true; } // initial
          }

          if (transpose) { y_inc = 1;  iy_block_inc = ycmp;    }
          else           { y_inc = nx; iy_block_inc = nx*ycmp; }

          for (int ix=0; ix<nx; ix+=icmp) {  // compress in the y direction
            if (transpose) { off = ix*l_lpi; maxIndex = off+l_lpi;    }
            else           { off = ix;     maxIndex = nx*ny; }
            if (firstycmp) l_cmpbuf[ix] = bufv[off]; // load initial value
            for (int iy=off; iy<maxIndex; iy+=iy_block_inc) {
              bm = bufv[iy];
              ncells = 1;
              if (cmpLeft>0 && l_cmpbuf[ix]>bm) bm = l_cmpbuf[ix]; // compare against cmpbuf[]
              for (int ii=iy+y_inc; ii<iy+iy_block_inc && ii<maxIndex; ii+=y_inc,ncells++) if (bufv[ii]>bm) bm=bufv[ii];
              // NTN: we would not have to do the below assignment as a loop if bufv's height == display's screen height
              for (int ii=iy,jj=0; jj<ncells; ii+=y_inc,jj++) { bufv[ii]=bm; }
              // if block size to process is smaller than ycmp and block's bm > previous's bm
              // then extend current block backwards to fit full block size (otherwise it may not be visible)
              // NTN: this probably will not be needed if bufv's height == plot's screen height
              if (ncells<ycmp) {
                int moreCells = ycmp-ncells+1;
                if (bm>bufv[iy-y_inc]) for (int ii=iy-(y_inc*moreCells); ii<iy; ii+=y_inc) bufv[ii] = bm;
              }
              if (cmpLeft>0 && l_cmpbuf[ix]<bm) l_cmpbuf[ix] = bm; // save last max value for y compression across paged buffers
            }
          }
        }
        // NTN: the below loop will not be needed if bufv's width is the same as the display's screen width
        //      Currently this has to be done after the y compression so that those values are also picked up
        int x_inc = (transpose)? l_lpi : 1; // how many cells to increment one cell in x-direction
        for (int iy=0; iy<ny; iy++) { // this has to be done after y compression
          off = iy*nx;                // otherwise, we may highlight too many cells
          maxIndex = off+nx;
          for (int ix=off; ix<maxIndex; ix+=icmp) {
            if (transpose) idx = (ix-off)*l_lpi+iy;
            else           idx = ix;
            bm     = bufv[idx];
            ncells = (ix+icmp > maxIndex)? maxIndex - ix : icmp;
            for (int ii=idx+x_inc; ii<idx+(ncells*x_inc); ii+=x_inc) { bufv[ii] = bm; }

            // if block size to process is smaller than icmp and block's bm > previous's bm
            // then extend current block backwards to fit full block size (otherwise it may not be visible)
            // NTN: this probably will not be needed if bufv's width == plot's screen width
            if (ncells<icmp) {
              int moreCells = (icmp-ncells+1);
              if (bm>bufv[idx-x_inc]) for (int ii=idx-(moreCells*x_inc); ii<idx; ii+=x_inc) bufv[ii] = bm;
            }
          }
        }
        break;
      case CMP_SWMAX:     // compute sliding windowed maximum - TODO: add support for transpose mode?
        for (int iy=0; iy<ny; iy++) {
          off = iy*nx; bm = buf[off+boff];
          for (int ix=off; ix<off+imid; ix++) bufv[ix]=buf[ix+boff];
          for (int ix=off+imid+1; ix<off+nx-imid; ix++) {
            b1 = buf[boff+ix-imid-1];
            b2 = buf[boff+ix+imid];

            if (b2>=bm) {
              bm=b2;
            }
            else if (b1<bm) {
              // nothing to do
            }
            else {
              int i=boff+ix-imid; bm=buf[i];
              for (i++; i<=boff+ix+imid; i++) if (buf[i]>bm) bm=buf[i];
            }
            bufv[ix]=bm;
          }
          for (int ix=off+nx-imid; ix<off+nx; ix++) bufv[ix]=buf[ix+boff];
        }
        break;
      default:
    }
  }

  /** Any changes/fix to this method should be mirrored in
      {@link #compressByte(byte[], int, byte[], int, int, int, int, int, boolean)}.
      NOTE: the current compression for int (ARGB colors) is somewhat bogus, in
      that it uses the int value of the ARGB color, making the green and blue
      components insignificant. Maybe we should not do compression when in
      directColor (ARGB) mode.
   */
  private void compressInt (int[] buf, int boff, int[] bufv, int bvoff,
                            int nx, int ny, int icmp, int jcmp, boolean transpose) {

    // make local copies for thread safety -  - Since 3.7.0 for Bug 2991
    int   l_lpi     = lpi;     // local copy - lines per image
    int[] l_cmpbufv = cmpbufv; // local copy - buffer for doing y compression larger than LPI (mirrors cmpbuf[])

    int b1,b2,bm;
    int off;      // offset into buf
    int ncells;   // number of cells (aka bins) in a block
    int maxIndex; // max index into buffers
    int idx;      // index into bufv
    int imid = icmp>>1;
    if (icmp<1) icmp = 1; // BUG 1330 prevent infinite loop (if we are doing zero compression in x direction)
    if (icmp>nx) {
      if ((compressIntWarn++ % 100) == 0) { // display warning every ith time
        warning("compressInt() icmp("+icmp+") > nx (" + nx + "), resetting to nx. count="+compressIntWarn);
      }
      icmp = nx; // fix for BUG 1684
    }

    switch (compression) {
      case CMP_MAX:                    // compute block windowed maximum
        for (int iy=0; iy<ny; iy++) {  // compress in the x direction
          off         = iy*nx + boff;
          maxIndex = Math.min(off+nx, buf.length); // make sure maxIndex <= buf.length
          for (int ix=off; ix<maxIndex; ix+=icmp) {
            bm     = buf[ix];
            ncells = (ix+icmp > maxIndex)? maxIndex - ix : icmp;
            for (int i=ix+1; i<ix+ncells; i++) if (buf[i]>bm) bm=buf[i]; // calc max for block

            if (transpose) { idx = (ix-off)*l_lpi+iy; }
            else           { idx = ix-boff; }
            bufv[idx] = bm; // save compressed result into image buffer
          }
        }
        if (jcmp>1) {
          int y_inc;        // how many cells to increment one cell in y-direction
          int iy_block_inc; // how many cells to increment for whole block of cells in y-direction
          int ycmp = Math.min(ny,jcmp);
          boolean firstycmp = false;
          if (jcmp>l_lpi) {  // attempt y compression across paged buffers
            if (cmpLeft>0) { cmpLeft -= ycmp; }
            else           { cmpLeft = jcmp - ycmp; firstycmp = true; } // initial
          }

          if (transpose) { y_inc = 1;  iy_block_inc = ycmp;    }
          else           { y_inc = nx; iy_block_inc = nx*ycmp; }

          for (int ix=0; ix<nx; ix+=icmp) {  // compress in the y direction
            if (transpose) { off = ix*l_lpi; maxIndex = off+l_lpi;    }
            else           { off = ix;     maxIndex = nx*ny; }
            if (firstycmp) l_cmpbufv[ix] = bufv[off]; // load initial value
            for (int iy=off; iy<maxIndex; iy+=iy_block_inc) {
              bm = bufv[iy];
              ncells = 1;
              if (cmpLeft>0 && l_cmpbufv[ix]>bm) bm = l_cmpbufv[ix]; // compare against cmpbuf[]
              for (int ii=iy+y_inc; ii<iy+iy_block_inc && ii<maxIndex; ii+=y_inc,ncells++) if (bufv[ii]>bm) bm=bufv[ii];
              // NTN: we would not have to do the below assignment as a loop if bufv's height == display's screen height
              for (int ii=iy,jj=0; jj<ncells; ii+=y_inc,jj++) { bufv[ii]=bm; }
              // if block size to process is smaller than ycmp and block's bm > previous's bm
              // then extend current block backwards to fit full block size (otherwise it may not be visible)
              // NTN: this probably will not be needed if bufv's height == plot's screen height
              if (ncells<ycmp) {
                int moreCells = ycmp-ncells+1;
                if (bm>bufv[iy-y_inc]) for (int ii=iy-(y_inc*moreCells); ii<iy; ii+=y_inc) bufv[ii] = bm;
              }
              if (cmpLeft>0 && l_cmpbufv[ix]<bm) l_cmpbufv[ix] = bm; // save last max value for y compression across paged buffers
            }
          }
        }
        // NTN: the below loop will not be needed if bufv's width is the same as the display's screen width
        //      Currently this has to be done after the y compression so that those values are also picked up
        int x_inc = (transpose)? l_lpi : 1;
        for (int iy=0; iy<ny; iy++) { // this has to be done after y compression
          off = iy*nx;                // otherwise, we may highlight too many cells
          maxIndex = off+nx;
          for (int ix=off; ix<maxIndex; ix+=icmp) {
            if (transpose) idx = (ix-off)*l_lpi+iy;
            else           idx = ix;
            bm     = bufv[idx];
            ncells = (ix+icmp > maxIndex)? maxIndex - ix : icmp;
            for (int ii=idx+x_inc; ii<idx+(ncells*x_inc); ii+=x_inc) { bufv[ii] = bm; }

            // if block size to process is smaller than icmp and block's bm > previous's bm
            // then extend current block backwards to fit full block size (otherwise it may not be visible)
            // NTN: this probably will not be needed if bufv's width == plot's screen width
            if (ncells<icmp) {
              int moreCells = (icmp-ncells+1);
              if (bm>bufv[idx-x_inc]) for (int ii=idx-(moreCells*x_inc); ii<idx; ii+=x_inc) bufv[ii] = bm;
            }
          }
        }
        break;
      case CMP_SWMAX:     // compute sliding windowed maximum
        for (int iy=0; iy<ny; iy++) {
          off = iy*nx; bm = buf[off+boff];
          for (int ix=off; ix<off+imid; ix++) bufv[ix]=buf[ix+boff];
          for (int ix=off+imid+1; ix<off+nx-imid; ix++) {
            b1 = buf[boff+ix-imid-1];
            b2 = buf[boff+ix+imid];

            if (b2>=bm) {
              bm=b2;
            }
            else if (b1<bm) {
              // nothing to do
            }
            else {
              int i=boff+ix-imid; bm=buf[i];
              for (i++; i<=boff+ix+imid; i++) if (buf[i]>bm) bm=buf[i];
            }
            bufv[ix]=bm;
          }
          for (int ix=off+nx-imid; ix<off+nx; ix++) bufv[ix]=buf[ix+boff];
        }
        break;
      default:
    }
  }

  @Override
  public String getReadOut (Position mp) {
    // Break out in RealTime for now since we don't handle the situation where
    // the raster wraps under the cursor (i.e. the old Z will be shown not the
    // current one).
    if (realtime) return null;
    int jx = (int)Math.round((mp.x-xstart)/xdelta);
    if (jx<0 || jx>=frame) return null;
    int jy = (int)Math.round((mp.y-ystart)/ydelta);
    if (jy<0 || jy>=size) return null;
    StringBuilder sb = new StringBuilder(name);
    sb.append(" (z=");
    double z;
    double dz=1/scl1;
    if (useRaster && (buf!=null || (directColor && imageBuf!=null)) ) {
      int i = jy*frame + jx;
      if (directColor && i<imageBuf.length) {
        sb.append("0x").append(Integer.toHexString(imageBuf[i]));
      } else if (i<buf.length) {
        int b = buf[i]; if (b<0) b+=256;
        z = b/scl1 + scl0;
        sb.append(MP.formNumber(z,0,Units.NONE));
    }
    }
    sb.append(' ').append(UnicodeMap.PLUS_MINUS_SIGN).append(' '); // Adds the +/- sign
    sb.append(MP.formNumber(dz,0,Units.NONE)).append(")");
    return sb.toString();
  }

  @Override
  protected Position getPosition (int px, int py, Position p) {
    super.getPosition(px, py, p);
    p.z = getZAtPos(p);
    return p;
  }

  /** Attempt to get the Z value for a given position
      @param mp The position
      @return The Z value
      @since NeXtMidas 2.9.0 (Bug 1383)
   */
  public double getZAtPos (Position mp) {
    if (realtime) return Double.NaN;
    int jx = (int)Math.round((mp.x-xstart)/xdelta);
    if (jx<0 || jx>=frame) return Double.NaN;
    int jy = (int)Math.round((mp.y-ystart)/ydelta);
    if (jy<0 || jy>=size) return Double.NaN;
    double z=0;
    if (useRaster && (buf!=null || (directColor && imageBuf!=null)) ) {
      int i = jy*frame + jx;
      if (directColor && i<imageBuf.length) {
        z = imageBuf[i];
      } else if (i<buf.length) {
        int b = buf[i]; if (b<0) b+=256;
        z = b/scl1 + scl0;
      }
    }
    return z;
  }

  /** Get the raster height in pixels. */
  private int getPixHeight () {
    return (MP.xvy) ? MP.ix2 - MP.ix1 : MP.iy2 - MP.iy1;
  }

  /** Get the number of pixels per frame. */
  private double getPixPerFrame () {
    double min = (MP.xvy) ? MP.getry(MP.ix1,0) : MP.getry(0,MP.iy1);
    double max = (MP.xvy) ? MP.getry(MP.ix2,0) : MP.getry(0,MP.iy2);
    double pps = getPixHeight() / (max - min); // pixels-per-sec
    return pps * pf.getDelta();  // pixels-per-frame
  }

  @Override
  public double getTime (int px, int py) {
    if (!MP.is2D) return Double.NaN; // Not supported at this time.
    recomputeBounds();    // Need to recompute bounds (required for Bug 1052)
    double frames;
    double timeAt;
    if ((MP.xvy && (px>MP.ix2 || px<MP.ix1)) || (py>MP.iy2 || py<MP.iy1)) return 0;  // cursor in the axis area
    if (realtime) {
      frames = ((MP.xvy)? (ixn-px):(iyn - py)) / getPixPerFrame();
      if (frames < 0) { frames = frames + size; } // Below draw line
      timeAt = pf.getTimeAt(-frames);
    } else {
      frames = Math.round((( (MP.xvy)? px:py) - MP.myb  - ystart*MP.myy) / getPixPerFrame());
      if (frames < 0) frames = 0;
      timeAt = pf.getTimeAt(frames);
    }
    if (xIsTime) timeAt += MP.getrx(px,py);
//System.out.println("Lay2d getTime time="+(new Time(time))+" ppf="+getPixPerFrame()+" iyn="+iyn+" px="+px+" py="+py+" myb="+MP.myb+" mxx="+MP.mxx+" mxb="+MP.mxb+
//    " myy="+MP.myy+" ixn="+ixn+" frames="+frames+" rx="+MP.getrx(px, py)+" statTime="+(new Time(pf.getTimeAt(frames))));
    return timeAt;
  }

  @Deprecated @Override // TODO: Remove this function in NeXtMidas 3.7+
  public double getYPosAtTime (double time) { return getPosAtTime(time); }

  @Override
  public double getPosAtTime (double time) {
    DataFile df = (DataFile)this.pf;
    int myPass  = pass;
    int myIndex = index;
    if (index==0 && myPass>0) { myPass--; myIndex=mindex; }
    double thisIndex = df.getIndexAt(time) + indexSave;
    if (realtime) {
      thisIndex -= (mindex*myPass/df.ape);
      if (thisIndex < 0 && thisIndex+size > myIndex/df.ape) thisIndex += size;
    }
//System.out.println("Lay2d getYPos time="+(new Time(time))+" index="+index+" thisIndex="+thisIndex+" delta="+pf.getYDelta()+" returning="+(thisIndex*pf.getYDelta()));
    return (thisIndex*df.getYDelta());
  }

  /** @deprecated Since NeXtMidas 3.3.2: This undocumented function was for internal
      use only and its functionality has now moved into the init(..) function.
      @param prev previous
   */
  @Deprecated // TODO: Remove this function in NeXtMidas 3.7+
  public void underlay (Layer2D prev) {
    index=frame*Math.min(size-1,prev.index/prev.frame);
    pass = prev.pass;
    if (prev.mimage!=null) {
      if (buf==null) getBuffer();
      if (mimage==null) getImage();
      double factor = (double)prev.frame/(double)frame;
      int jmax=prev.mindex-1;
      for (int i=0; i<mindex; i++) {
        int j = Math.min(jmax,(int)(0.5+(double)i*factor));
        buf[i] = prev.buf[j];
      }
    }
    index1 = index;
  }

  /** Check is the specified Data format is supported for DirectColor mode.
   *  @param format The data format (e.g. "SL").
   *  @return true if DirectColor is supported, false otherwise.
   */
  protected static boolean isDirectColorSupported (String format) {
    return format.equals("SL") || format.equals("QB");
  }

  /** Option to use direct colors (ARGB) from the input data.
   *  @return true if DirectColor is in use, false otherwise.
   */
  public boolean isDirectColor () { return directColor; }

  /** Set option to use direct colors (ARGB) from the input data. If value has changed, the layer
   *  will be cleared and reread so that the plot reflects the change.
   *  @param useColor true if DirectColor is to be used, false otherwise.
   */
  public void setDirectColor (boolean useColor) {
    if (directColor != useColor) {
      if (!useColor || isDirectColorSupported(data.getFormat())) {
        directColor = useColor;
        clear();
        reread();
      } else {
        warning("Layer2D: format "+data.getFormat()+" NOT supported for DirectColor mode");
      }
    }
  }

  /** Get alpha color component that will override those of the input when in directColor mode.
   *  @return The alpha component. Valid values are from 0 (transparent) to 255 (opaque),
   *          and -1 for not set.
   */
  public int getAlpha () { return alpha; }

  /** Set alpha color component that will override those of the input when in directColor mode.
      In normal mode, this applies the specified alpha (transparency) value to the current
      color model of this layer. Note, that when the alpha value is not 255 (opaque), then
      alpha blending is done on the output graphics which will consume more CPU/GPU than when opaque.
      If the alpha is set (other than -1) any existing data will be updated.
      @param alpha The alpha value. Valid values are from 0 (transparent) to 255 (opaque) or -1 for not set.
   */
  public void setAlpha (int alpha) {
    if (this.alpha != alpha) {
      this.alpha = alpha;
      if (directColor && (alpha >= 0) && (imageBuf != null)) {
        MColor.applyAlpha(imageBuf, alpha);
        refresh();
      } else if (colors!=null) {
        // for Bug 1266, we apply the specified alpha value to the color map
        // and update the mimage's color model and force a refresh of the layer
        final int a = (alpha & 0xFF) << 24;
        for (int i=1; i<colors.length; i++) {
          int argb = a | (colors[i].getRGB() & 0xFFFFFF);
          colors[i] = new Color(argb, true);
        } // end for loop
        if (mimage!=null) mimage.setColors (colors); // use new color map with alpha values
        refresh();
      }
    }
  }

  /** Draw legend for 2D Layer
      @param pos position of legend
      @param g   graphics context for drawing the legend
   */
  public void drawLegend (MBox pos, Graphics g) {
    int nc = colors.length;
    double scale = (double)nc/pos.w;
    int iy1=pos.y, iy2=pos.y+pos.h-1;
    for (int i=0; i<pos.w; i++) {
      int j = (int)(scale*i);
      g.setColor(colors[j]);
      int ix = pos.x+i;
      g.drawLine(ix,iy1,ix,iy2);
    }
  }

  /** Returns whether or not pipe cuts are supported
   *  Pipe cuts are supported when the /CUTS switch is present, when not using direct color, and if the current Layer is a baselayer.
   *  NOTE: Checking if acting baselayer since archived frames array in MPlot
   *  @return Whether or not pipe cuts are supported
   */
  @InternalUseOnly
  public boolean isPipeWithCuts () {
    return MP.isActingBaseLayer(this) && MP.getCutsSwitchOnForPipedLayer2D() && !directColor && ((DataFile)pf).isPipe();
  }

  /** Gets the Data elements for a pipe cut
   *  @param cutType Cut type (X or Y)
   *  @param xOry    RWC location on the plot to get the cut
   *  @return The Data elements for the pipe cut
   */
  @InternalUseOnly
  public Data[] getDataElementsForPipeCut2D (char cutType, double xOry) {
    if (cutType == 'X') {
      double y = xOry;

      // get relative percentage down the plot in the y direction
      double relativeYPer = (y-MP.ory1)/(MP.ory2-MP.ory1);

      // the lines per screen is used to get the pixel width per line
      double pixelWidthPerLine = (double)MP.iy21/getSize();

      // the relative percentage down and line pixel width is used to calculate the row index
      int row = (int)((relativeYPer*MP.iy21)/pixelWidthPerLine);

      // covers the case if the outer edge of the row is clicked
      if (row == MP.archivedFrames.length) row--;

      // populate the Data elements from the desired row
      float[] archivedFrameRow = MP.archivedFrames[row];
      Data[] elements = new Data[archivedFrameRow.length];
      for (int col = 0; col < archivedFrameRow.length; col++) {
        elements[col] = new Data(archivedFrameRow[col]);
      }
      return elements;
    }
    else {
      double x = xOry;

      // get relative percentage across the plot in the x direction
      double relativeXPer = (x-MP.orx1)/(MP.orx2-MP.orx1);

      // using an ArrayList to handle the case where not all rows have been populated yet
      ArrayList<Data> arrListData = new ArrayList<>(MP.archivedFrames.length);

      // checking every single row since the frame size can change
      for (int row = 0; row < MP.archivedFrames.length; row++) {
        float[] archivedFrameRow = MP.archivedFrames[row];
        if (archivedFrameRow == null) break; // a null row means this row and the remaining rows have not been populated yet
        int col = (int)(relativeXPer*archivedFrameRow.length); // the relative percentage across is used to calculate the column index
        if (col == archivedFrameRow.length) col--; // covers the case if the outer edge of the column is clicked
        arrListData.add(new Data(archivedFrameRow[col]));
      }

      // convert the ArrayList to an array to handle the case where not all rows have been populated yet
      arrListData.trimToSize();
      Data[] elements = new Data[arrListData.size()];
      elements = arrListData.toArray(elements);
      return elements;
    }
  }
}
