net.sf.maltcms.chromaui.charts.FastHeatMapPlot.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.maltcms.chromaui.charts.FastHeatMapPlot.java

Source

/* 
 * Maui, Maltcms User Interface. 
 * Copyright (C) 2008-2014, The authors of Maui. All rights reserved.
 *
 * Project website: http://maltcms.sf.net
 *
 * Maui may be used under the terms of either the
 *
 * GNU Lesser General Public License (LGPL)
 * http://www.gnu.org/licenses/lgpl.html
 *
 * or the
 *
 * Eclipse Public License (EPL)
 * http://www.eclipse.org/org/documents/epl-v10.php
 *
 * As a user/recipient of Maui, you may choose which license to receive the code 
 * under. Certain files or entire directories may not be covered by this 
 * dual license, but are subject to licenses compatible to both LGPL and EPL.
 * License exceptions are explicitly declared in all relevant files or in a 
 * LICENSE file in the relevant directories.
 *
 * Maui is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. Please consult the relevant license documentation
 * for details.
 */
package net.sf.maltcms.chromaui.charts;

import net.sf.maltcms.chromaui.charts.renderer.XYNoBlockRenderer;
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.Transparency;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.VolatileImage;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Iterator;
import java.util.List;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;

import org.jfree.chart.axis.AxisSpace;
import org.jfree.chart.axis.AxisState;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.axis.ValueTick;
import org.jfree.chart.event.PlotChangeEvent;
import org.jfree.chart.event.RendererChangeEvent;
import org.jfree.chart.plot.CrosshairState;
import org.jfree.chart.plot.Pannable;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.PlotRenderingInfo;
import org.jfree.chart.plot.PlotState;
import org.jfree.chart.plot.ValueAxisPlot;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.plot.Zoomable;
import org.jfree.chart.renderer.PaintScale;
import org.jfree.chart.renderer.xy.XYBlockRenderer;
import org.jfree.chart.renderer.xy.XYItemRendererState;
import org.jfree.chart.util.ResourceBundleWrapper;
import org.jfree.data.Range;
import org.jfree.data.xy.XYZDataset;
import org.jfree.io.SerialUtilities;
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.RectangleInsets;
import org.jfree.util.ObjectUtilities;
import org.jfree.util.PaintUtilities;

/**
 *
 * @author Nils Hoffmann
 */
public class FastHeatMapPlot extends XYPlot implements ValueAxisPlot, Pannable, Zoomable, Cloneable, Serializable {

    /**
     * For serialization.
     */
    private static final long serialVersionUID = 7871545897358563521L;
    /**
     * The default grid line stroke.
     */
    public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke(0.5f, BasicStroke.CAP_BUTT,
            BasicStroke.JOIN_BEVEL, 0.0f, new float[] { 2.0f, 2.0f }, 0.0f);
    /**
     * The default grid line paint.
     */
    public static final Paint DEFAULT_GRIDLINE_PAINT = Color.lightGray;
    /**
     * The x data range.
     */
    private Range xDataRange;
    /**
     * The y data range.
     */
    private Range yDataRange;
    /**
     * The domain axis (used for the x-values).
     */
    private ValueAxis domainAxis;
    /**
     * The range axis (used for the y-values).
     */
    private ValueAxis rangeAxis;
    /**
     * The paint used to plot data points.
     */
    private transient Paint paint;
    /**
     * A flag that controls whether the domain grid-lines are visible.
     */
    private boolean domainGridlinesVisible;
    /**
     * The stroke used to draw the domain grid-lines.
     */
    private transient Stroke domainGridlineStroke;
    /**
     * The paint used to draw the domain grid-lines.
     */
    private transient Paint domainGridlinePaint;
    /**
     * A flag that controls whether the range grid-lines are visible.
     */
    private boolean rangeGridlinesVisible;
    /**
     * The stroke used to draw the range grid-lines.
     */
    private transient Stroke rangeGridlineStroke;
    /**
     * The paint used to draw the range grid-lines.
     */
    private transient Paint rangeGridlinePaint;
    private transient BufferedImage dataImage;
    private transient VolatileImage offscreenBuffer;
    private transient int width = 0, height = 0;
    private int threshholdCutOff = 0;
    /**
     * A flag that controls whether or not panning is enabled for the domain
     * axis.
     *
     * @since 1.0.13
     */
    private boolean domainPannable;
    /**
     * A flag that controls whether or not panning is enabled for the range
     * axis.
     *
     * @since 1.0.13
     */
    private boolean rangePannable;
    /**
     * The resourceBundle for the localization.
     */
    protected static ResourceBundle localizationResources = ResourceBundleWrapper
            .getBundle("org.jfree.chart.plot.LocalizationBundle");

    /**
     * Creates a new instance of <code>FastHeatmapPlot</code> with default axes.
     * @param xyz
     * @param width
     * @param height
     * @param domain
     * @param range
     * @param xybr
     */
    public FastHeatMapPlot(XYZDataset xyz, int width, int height, ValueAxis domain, ValueAxis range,
            XYBlockRenderer xybr) {
        this(domain, range);
        setDataset(xyz);
        setRenderer(xybr);
        // BufferedImage bi = prepareData(xyz,width,height,xybr);
        this.width = width;
        this.height = height;
        setDomainPannable(true);
        setRangePannable(true);
        // setDataImage(bi,new Range(0,width),new Range(0,height));
    }

    /**
     *
     * @param xyz
     * @param sl
     * @param spm
     * @param xybr
     * @param activeGraphics
     * @param dataArea
     * @param info
     * @param crosshairState
     * @return
     */
    public BufferedImage prepareData(final XYZDataset xyz, final int sl, final int spm, final XYBlockRenderer xybr,
            Graphics2D activeGraphics, Rectangle2D dataArea, PlotRenderingInfo info,
            CrosshairState crosshairState) {
        long start = System.currentTimeMillis();
        final PaintScale ps = xybr.getPaintScale();
        double minz = Double.POSITIVE_INFINITY, maxz = Double.NEGATIVE_INFINITY;

        for (int i = 0; i < xyz.getSeriesCount(); i++) {
            final int items = xyz.getItemCount(i);
            for (int j = 0; j < items; j++) {
                minz = Math.min(xyz.getZValue(i, j), minz);
                maxz = Math.max(xyz.getZValue(i, j), maxz);
            }
        }
        if (ps instanceof GradientPaintScale) {
            ((GradientPaintScale) ps).setUpperBound(maxz);
            ((GradientPaintScale) ps).setLowerBound(minz);
        }
        Logger.getLogger(getClass().getName()).log(Level.INFO, "Finding min and max data took{0}ms",
                (System.currentTimeMillis() - start));

        //        VolatileImage bi = null;
        //        if (bi == null) {
        //        if (this.getOrientation() == PlotOrientation.VERTICAL) {
        BufferedImage bi = createCompatibleImage(sl, spm, BufferedImage.TRANSLUCENT);
        //        } else {
        //            bi = createCompatibleImage(spm, sl);
        //        }
        //        }else{
        //            img.validate(g.getDeviceConfiguration())
        //        }

        Graphics2D g2 = (Graphics2D) bi.getGraphics();
        g2.setColor((Color) ps.getPaint(ps.getLowerBound()));
        g2.fillRect(0, 0, sl, spm);
        // System.out.println("Using Threshold: " + threshold);
        int height = bi.getHeight();
        //final WritableRaster wr = bi.getRaster();
        XYItemRendererState xyrs = xybr.initialise(g2, dataArea, this, xyz, info);
        for (int i = 0; i < xyz.getSeriesCount(); i++) {
            final int items = xyz.getItemCount(i);
            for (int j = 0; j < items; j++) {
                final double tmp = xyz.getZValue(i, j);
                if (tmp > this.threshholdCutOff) {
                    //if(j%50==0)System.out.println("Value > threshold: "+tmp);
                    final Paint p = ps.getPaint(tmp);
                    //                    final Paint tp = ps.getPaint(this.threshholdCutOff);
                    //                    if (!tp.equals(p)) {
                    if (p instanceof Color) {
                        final Color c = (Color) p;
                        g2.setColor(c);
                        //                    xybr.drawItem(g2, xyrs, dataArea, info, this, domainAxis, rangeAxis, xyz, i, j, crosshairState, 0);
                        //                        if (this.getOrientation() == PlotOrientation.VERTICAL) {

                        g2.fillRect((int) xyz.getXValue(i, j), height - (int) xyz.getYValue(i, j), 1, 1);
                        //                            wr.setPixel(, , new int[]{c.getRed(),
                        //                                        c.getGreen(), c.getBlue(), c.getAlpha()});
                        //                        } else {
                        //                            wr.setPixel((int) xyz.getYValue(i, j), (int) xyz.getXValue(i, j), new int[]{c.getRed(),
                        //                                        c.getGreen(), c.getBlue(), c.getAlpha()});
                        //                        }
                        //                }
                        //                    }
                    }
                }
            }
        }

        Logger.getLogger(getClass().getName()).log(Level.INFO, "Creating image and drawing items took {0}ms",
                (System.currentTimeMillis() - start));

        return bi;
    }

    private void drawOffscreenImage(Image sourceImage) {
        // image creation
        if (offscreenBuffer == null) {
            offscreenBuffer = createCompatibleVolatileImage(sourceImage.getWidth(null), sourceImage.getHeight(null),
                    Transparency.TRANSLUCENT);
        }
        do {
            if (offscreenBuffer.validate(getGraphicsConfiguration()) == VolatileImage.IMAGE_INCOMPATIBLE) {
                // old vImg doesn't work with new GraphicsConfig; re-create it
                offscreenBuffer = createCompatibleVolatileImage(sourceImage.getWidth(null),
                        sourceImage.getHeight(null), Transparency.TRANSLUCENT);
            }
            Graphics2D g = offscreenBuffer.createGraphics();
            //
            // miscellaneous rendering commands...
            //
            g.drawImage(sourceImage, 0, 0, null);
            g.dispose();
        } while (offscreenBuffer.contentsLost());
    }

    private void drawFromOffscreenBuffer(Graphics2D g, Image sourceImage, Rectangle sourceArea,
            Rectangle targetArea) {
        // copying from the image (here, gScreen is the Graphics
        // object for the onscreen window)
        do {
            int returnCode = offscreenBuffer.validate(getGraphicsConfiguration());
            if (returnCode == VolatileImage.IMAGE_RESTORED) {
                // Contents need to be restored
                drawOffscreenImage(sourceImage); // restore contents
            } else if (returnCode == VolatileImage.IMAGE_INCOMPATIBLE) {
                // old vImg doesn't work with new GraphicsConfig; re-create it
                offscreenBuffer = null;
                drawOffscreenImage(sourceImage);

            }
            int sx0, sx1, sy0, sy1, tx0, tx1, ty0, ty1;
            sx0 = sourceArea.x;
            //            sx1 = sourceArea.x + sourceArea.width;
            sy0 = sourceArea.y;
            //            sy1 = sourceArea.y + sourceArea.height;
            tx0 = targetArea.x;
            //            tx1 = targetArea.x + targetArea.width;
            ty0 = targetArea.y;
            //            ty1 = targetArea.y + targetArea.height;

            g.drawImage(offscreenBuffer.getSnapshot().getSubimage(sx0, sy0, sourceArea.width, sourceArea.height),
                    tx0, ty0, targetArea.width, targetArea.height, null);
        } while (offscreenBuffer.contentsLost());

    }

    /**
     *
     * @param width
     * @param height
     * @param transparency
     * @return
     */
    public BufferedImage createCompatibleImage(int width, int height, int transparency) {
        GraphicsConfiguration gc = getGraphicsConfiguration();
        gc.getImageCapabilities().isAccelerated();
        return gc.createCompatibleImage(width, height, transparency);

    }

    /**
     *
     * @param width
     * @param height
     * @param transparency
     * @return
     */
    public VolatileImage createCompatibleVolatileImage(int width, int height, int transparency) {
        GraphicsConfiguration gc = getGraphicsConfiguration();
        VolatileImage img = gc.createCompatibleVolatileImage(width, height, transparency);
        Logger.getLogger(getClass().getName()).log(Level.INFO, "Using accelerated images: {0}",
                gc.getImageCapabilities().isAccelerated());
        Logger.getLogger(getClass().getName()).log(Level.INFO, "Using true volatile images: {0}",
                gc.getImageCapabilities().isTrueVolatile());
        return img;
    }

    /**
     *
     * @return
     */
    public GraphicsConfiguration getGraphicsConfiguration() {
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice gs = ge.getDefaultScreenDevice();
        return gs.getDefaultConfiguration();
    }

    /**
     *
     * @param bi
     * @param xrange
     * @param yrange
     */
    public void setDataImage(BufferedImage bi, Range xrange, Range yrange) {
        // AffineTransformOp at = new
        // AffineTransformOp(AffineTransform.getScaleInstance(1,
        // -1),AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
        // System.out.println("Transforming image");
        // BufferedImage fi = at.filter(bi, null);
        this.dataImage = bi;
        drawOffscreenImage(dataImage);
        this.xDataRange = xrange;
        this.yDataRange = yrange;
        // System.out.println("Firing change event");
        fireChangeEvent();
    }

    /**
     *
     */
    public void resetDataImage() {
        this.dataImage = null;
        this.offscreenBuffer = null;
        fireChangeEvent();
    }

    /**
     * Creates a new fast scatter plot.
     * <p>
     * The data is an array of x, y values: data[0][i] = x, data[1][i] = y.
     *
     * @param data the data (<code>null</code> permitted).
     * @param domainAxis the domain (x) axis (<code>null</code> not permitted).
     * @param rangeAxis the range (y) axis (<code>null</code> not permitted).
     */
    public FastHeatMapPlot(ValueAxis domainAxis, ValueAxis rangeAxis) {

        super();
        if (domainAxis == null) {
            throw new IllegalArgumentException("Null 'domainAxis' argument.");
        }
        if (rangeAxis == null) {
            throw new IllegalArgumentException("Null 'rangeAxis' argument.");
        }
        Logger.getLogger(getClass().getName()).info("Setting up axes");
        this.domainAxis = domainAxis;
        this.domainAxis.setPlot(this);
        this.domainAxis.addChangeListener(this);
        this.rangeAxis = rangeAxis;
        this.rangeAxis.setPlot(this);
        this.rangeAxis.addChangeListener(this);

        this.paint = Color.red;

        this.domainGridlinesVisible = false;
        this.domainGridlinePaint = FastHeatMapPlot.DEFAULT_GRIDLINE_PAINT;
        this.domainGridlineStroke = FastHeatMapPlot.DEFAULT_GRIDLINE_STROKE;

        this.rangeGridlinesVisible = false;
        this.rangeGridlinePaint = FastHeatMapPlot.DEFAULT_GRIDLINE_PAINT;
        this.rangeGridlineStroke = FastHeatMapPlot.DEFAULT_GRIDLINE_STROKE;
        this.mapDatasetToDomainAxis(0, 0);
        this.mapDatasetToRangeAxis(0, 0);
        configureDomainAxes();
        configureRangeAxes();
    }

    /**
     * Returns a short string describing the plot type.
     *
     * @return A short string describing the plot type.
     */
    @Override
    public String getPlotType() {
        return localizationResources.getString("Fast_Scatter_Plot");
    }

    /**
     * Returns the orientation of the plot.
     *
     * @return The orientation (always {@link PlotOrientation#VERTICAL}).
     */
    @Override
    public PlotOrientation getOrientation() {
        return super.getOrientation();
    }

    /**
     *
     * @param or
     */
    @Override
    public void setOrientation(PlotOrientation or) {
        super.setOrientation(or);
    }

    /**
     * Returns the domain axis for the plot.
     *
     * @return The domain axis (never <code>null</code>).
     *
     * @see #setDomainAxis(ValueAxis)
     */
    @Override
    public ValueAxis getDomainAxis() {
        return this.domainAxis;
    }

    /**
     * Sets the domain axis and sends a {@link PlotChangeEvent} to all
     * registered listeners.
     *
     * @param axis the axis (<code>null</code> not permitted).
     *
     * @since 1.0.3
     *
     * @see #getDomainAxis()
     */
    @Override
    public void setDomainAxis(ValueAxis axis) {
        // if (axis == null) {
        // throw new IllegalArgumentException("Null 'axis' argument.");
        // }
        this.domainAxis = axis;
        fireChangeEvent();
    }

    /**
     * Returns the range axis for the plot.
     *
     * @return The range axis (never <code>null</code>).
     *
     * @see #setRangeAxis(ValueAxis)
     */
    @Override
    public ValueAxis getRangeAxis() {
        return this.rangeAxis;
    }

    /**
     * Sets the range axis and sends a {@link PlotChangeEvent} to all registered
     * listeners.
     *
     * @param axis the axis (<code>null</code> not permitted).
     *
     * @since 1.0.3
     *
     * @see #getRangeAxis()
     */
    @Override
    public void setRangeAxis(ValueAxis axis) {
        // if (axis == null) {
        // throw new IllegalArgumentException("Null 'axis' argument.");
        // }
        this.rangeAxis = axis;
        fireChangeEvent();
    }

    /**
     * Returns the paint used to plot data points. The default is
     * <code>Color.red</code>.
     *
     * @return The paint.
     *
     * @see #setPaint(Paint)
     */
    public Paint getPaint() {
        return this.paint;
    }

    /**
     * Sets the color for the data points and sends a {@link PlotChangeEvent} to
     * all registered listeners.
     *
     * @param paint the paint (<code>null</code> not permitted).
     *
     * @see #getPaint()
     */
    public void setPaint(Paint paint) {
        if (paint == null) {
            throw new IllegalArgumentException("Null 'paint' argument.");
        }
        this.paint = paint;
        fireChangeEvent();
    }

    /**
     * Returns <code>true</code> if the domain gridlines are visible, and      <code>false<code> otherwise.
     *
     * @return <code>true</code> or <code>false</code>.
     *
     * @see #setDomainGridlinesVisible(boolean)
     * @see #setDomainGridlinePaint(Paint)
     */
    @Override
    public boolean isDomainGridlinesVisible() {
        return this.domainGridlinesVisible;
    }

    /**
     * Sets the flag that controls whether or not the domain grid-lines are
     * visible. If the flag value is changed, a {@link PlotChangeEvent} is sent
     * to all registered listeners.
     *
     * @param visible the new value of the flag.
     *
     * @see #getDomainGridlinePaint()
     */
    @Override
    public void setDomainGridlinesVisible(boolean visible) {
        if (this.domainGridlinesVisible != visible) {
            this.domainGridlinesVisible = visible;
            fireChangeEvent();
        }
    }

    /**
     * Returns the stroke for the grid-lines (if any) plotted against the domain
     * axis.
     *
     * @return The stroke (never <code>null</code>).
     *
     * @see #setDomainGridlineStroke(Stroke)
     */
    @Override
    public Stroke getDomainGridlineStroke() {
        return this.domainGridlineStroke;
    }

    /**
     * Sets the stroke for the grid lines plotted against the domain axis and
     * sends a {@link PlotChangeEvent} to all registered listeners.
     *
     * @param stroke the stroke (<code>null</code> not permitted).
     *
     * @see #getDomainGridlineStroke()
     */
    @Override
    public void setDomainGridlineStroke(Stroke stroke) {
        if (stroke == null) {
            throw new IllegalArgumentException("Null 'stroke' argument.");
        }
        this.domainGridlineStroke = stroke;
        fireChangeEvent();
    }

    /**
     * Returns the paint for the grid lines (if any) plotted against the domain
     * axis.
     *
     * @return The paint (never <code>null</code>).
     *
     * @see #setDomainGridlinePaint(Paint)
     */
    @Override
    public Paint getDomainGridlinePaint() {
        return this.domainGridlinePaint;
    }

    /**
     * Sets the paint for the grid lines plotted against the domain axis and
     * sends a {@link PlotChangeEvent} to all registered listeners.
     *
     * @param paint the paint (<code>null</code> not permitted).
     *
     * @see #getDomainGridlinePaint()
     */
    @Override
    public void setDomainGridlinePaint(Paint paint) {
        if (paint == null) {
            throw new IllegalArgumentException("Null 'paint' argument.");
        }
        this.domainGridlinePaint = paint;
        fireChangeEvent();
    }

    /**
     * Returns <code>true</code> if the range axis grid is visible, and      <code>false<code> otherwise.
     *
     * @return <code>true</code> or <code>false</code>.
     *
     * @see #setRangeGridlinesVisible(boolean)
     */
    @Override
    public boolean isRangeGridlinesVisible() {
        return this.rangeGridlinesVisible;
    }

    /**
     * Sets the flag that controls whether or not the range axis grid lines are
     * visible. If the flag value is changed, a {@link PlotChangeEvent} is sent
     * to all registered listeners.
     *
     * @param visible the new value of the flag.
     *
     * @see #isRangeGridlinesVisible()
     */
    @Override
    public void setRangeGridlinesVisible(boolean visible) {
        if (this.rangeGridlinesVisible != visible) {
            this.rangeGridlinesVisible = visible;
            fireChangeEvent();
        }
    }

    /**
     * Returns the stroke for the grid lines (if any) plotted against the range
     * axis.
     *
     * @return The stroke (never <code>null</code>).
     *
     * @see #setRangeGridlineStroke(Stroke)
     */
    @Override
    public Stroke getRangeGridlineStroke() {
        return this.rangeGridlineStroke;
    }

    /**
     * Sets the stroke for the grid lines plotted against the range axis and
     * sends a {@link PlotChangeEvent} to all registered listeners.
     *
     * @param stroke the stroke (<code>null</code> permitted).
     *
     * @see #getRangeGridlineStroke()
     */
    @Override
    public void setRangeGridlineStroke(Stroke stroke) {
        if (stroke == null) {
            throw new IllegalArgumentException("Null 'stroke' argument.");
        }
        this.rangeGridlineStroke = stroke;
        fireChangeEvent();
    }

    /**
     * Returns the paint for the grid lines (if any) plotted against the range
     * axis.
     *
     * @return The paint (never <code>null</code>).
     *
     * @see #setRangeGridlinePaint(Paint)
     */
    @Override
    public Paint getRangeGridlinePaint() {
        return this.rangeGridlinePaint;
    }

    /**
     * Sets the paint for the grid lines plotted against the range axis and
     * sends a {@link PlotChangeEvent} to all registered listeners.
     *
     * @param paint the paint (<code>null</code> not permitted).
     *
     * @see #getRangeGridlinePaint()
     */
    @Override
    public void setRangeGridlinePaint(Paint paint) {
        if (paint == null) {
            throw new IllegalArgumentException("Null 'paint' argument.");
        }
        this.rangeGridlinePaint = paint;
        fireChangeEvent();
    }

    /**
     * Draws the fast scatter plot on a Java 2D graphics device (such as the
     * screen or a printer).
     *
     * @param g2 the graphics device.
     * @param area the area within which the plot (including axis labels) should
     * be drawn.
     * @param anchor the anchor point (<code>null</code> permitted).
     * @param parentState the state from the parent plot (ignored).
     * @param info collects chart drawing information (<code>null</code>
     * permitted).
     */
    @Override
    public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, PlotState parentState,
            PlotRenderingInfo info) {

        // set up info collection...
        if (info != null) {
            info.setPlotArea(area);
        }

        // adjust the drawing area for plot insets (if any)...
        RectangleInsets insets = getInsets();
        insets.trim(area);

        AxisSpace space = new AxisSpace();
        space = this.domainAxis.reserveSpace(g2, this, area, RectangleEdge.BOTTOM, space);
        space = this.rangeAxis.reserveSpace(g2, this, area, RectangleEdge.LEFT, space);
        Rectangle2D dataArea = space.shrink(area, null);

        if (info != null) {
            info.setDataArea(dataArea);
        }

        // draw the plot background and axes...
        drawBackground(g2, dataArea);

        AxisState domainAxisState = this.domainAxis.draw(g2, dataArea.getMaxY(), area, dataArea,
                RectangleEdge.BOTTOM, info);
        AxisState rangeAxisState = this.rangeAxis.draw(g2, dataArea.getMinX(), area, dataArea, RectangleEdge.LEFT,
                info);
        drawDomainGridlines(g2, dataArea, domainAxisState.getTicks());
        drawRangeGridlines(g2, dataArea, rangeAxisState.getTicks());

        Shape originalClip = g2.getClip();
        Composite originalComposite = g2.getComposite();

        g2.clip(dataArea);
        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, getForegroundAlpha()));
        render(g2, dataArea, info, null);

        g2.setClip(originalClip);
        g2.setComposite(originalComposite);
        drawOutline(g2, dataArea);

    }

    //    private void refreshEntities(Graphics2D g2, Rectangle2D dataArea,
    //            PlotRenderingInfo info, CrosshairState crosshairState) {
    //        for (int i = 0; i < getDatasetCount(); i++) {
    //            XYBlockRenderer xybr = (XYBlockRenderer) getRenderer(i);
    //            XYZDataset xyzd = (XYZDataset) getDataset(i);
    //            XYItemRendererState state = xybr.initialise(g2, dataArea, this,
    //                    xyzd, info);
    //            state.setProcessVisibleItemsOnly(true);
    //            int passCount = xybr.getPassCount();
    //            SeriesRenderingOrder seriesOrder = getSeriesRenderingOrder();
    //            Graphics2D g3 = createCompatibleImage(1, 1, BufferedImage.TRANSLUCENT).createGraphics();
    //            for (int j = 0; j < xyzd.getItemCount(0); j++) {
    //                xybr.drawItem(g3, state, dataArea, info, this, domainAxis,
    //                        rangeAxis, xyzd, i, j, crosshairState, 0);
    //
    //            }
    //        }
    // for(int i = 0; i<getDatasetCount(); i++) {
    // XYBlockRenderer xybr = (XYBlockRenderer)getRenderer(i);
    // XYZDataset xyzd = (XYZDataset)getDataset(i);
    // XYItemRendererState state = xybr.initialise(g2, dataArea, this,
    // xyzd, info);
    // state.setProcessVisibleItemsOnly(false);
    // EntityCollection entities = state.getEntityCollection();
    // if (entities != null) {
    // for(XYItemEntity xyie:entityCollection) {
    // entities.add(xyie);
    // }
    // }
    // }
    //    }
    /**
     * Draws a representation of the data within the dataArea region. The
     * <code>info</code> and <code>crosshairState</code> arguments may be
     * <code>null</code>.
     *
     * @param g2 the graphics device.
     * @param dataArea the region in which the data is to be drawn.
     * @param info an optional object for collection dimension information.
     * @param crosshairState collects crosshair information (<code>null</code>
     * permitted).
     */
    public void render(Graphics2D g2, Rectangle2D dataArea, PlotRenderingInfo info, CrosshairState crosshairState) {
        // long start = System.currentTimeMillis();
        // System.out.println("Start: " + start);
        //System.out.println("Data area: " + dataArea);
        // g2.setPaint(this.paint);

        // if the axes use a linear scale, you can uncomment the code below and
        // switch to the alternative transX/transY calculation inside the loop
        // that follows - it is a little bit faster then.
        //
        // int xx = (int) dataArea.getMinX();
        // int ww = (int) dataArea.getWidth();
        // int yy = (int) dataArea.getMaxY();
        // int hh = (int) dataArea.getHeight();
        // double domainMin = this.domainAxis.getLowerBound();
        // double domainLength = this.domainAxis.getUpperBound() - domainMin;
        // double rangeMin = this.rangeAxis.getLowerBound();
        // double rangeLength = this.rangeAxis.getUpperBound() - rangeMin;
        if (this.dataImage != null) {
            Logger.getLogger(getClass().getName()).info("Drawing data image");

            //DATA SPACE
            // System.out.println("Domain axis lower bound: "+this.domainAxis.getLowerBound());
            // define ROI in data coordinates
            double xlb = this.domainAxis.getLowerBound();
            double ylb = this.rangeAxis.getLowerBound();
            double xub = this.domainAxis.getUpperBound();
            double yub = this.rangeAxis.getUpperBound();
            //construct region of interest for data in view
            Rectangle2D.Double roi = new Rectangle2D.Double(xlb, ylb, xub - xlb, yub - ylb);
            Logger.getLogger(getClass().getName()).log(Level.INFO, "ROI: {0}", roi);
            //construct bounding rectangle of data
            Rectangle2D.Double imgSpace = new Rectangle2D.Double(0, 0, this.dataImage.getWidth(),
                    this.dataImage.getHeight());
            Logger.getLogger(getClass().getName()).log(Level.INFO, "IMG Space: {0}", imgSpace);
            //construct intersection of both, this is the area of the data, we need to render
            Rectangle2D sourceCoordinates = imgSpace.createIntersection(roi);
            Logger.getLogger(getClass().getName()).log(Level.INFO, "Projecting from: {0}", sourceCoordinates);
            //VIEW SPACE
            // define mapping coordinates from data domain to view domain
            double xlow = this.domainAxis.valueToJava2D(this.domainAxis.getLowerBound(), dataArea,
                    RectangleEdge.BOTTOM);
            double ylow = this.rangeAxis.valueToJava2D(this.rangeAxis.getLowerBound(), dataArea,
                    RectangleEdge.LEFT);
            double xhigh = this.domainAxis.valueToJava2D(this.domainAxis.getUpperBound(), dataArea,
                    RectangleEdge.TOP);
            double yhigh = this.rangeAxis.valueToJava2D(this.rangeAxis.getUpperBound(), dataArea,
                    RectangleEdge.RIGHT);

            drawFromOffscreenBuffer(g2, this.dataImage, sourceCoordinates.getBounds(), dataArea.getBounds());
            renderCrosshairs(dataArea, g2);
        } else {
            createImage(g2, dataArea, info, crosshairState);
            render(g2, dataArea, info, crosshairState);
        }
    }

    private void renderCrosshairs(Rectangle2D dataArea, Graphics2D g2) {
        int r = 5;
        Color fill = new Color(255, 255, 255, 192);
        Color outline = new Color(0, 0, 0, 128);
        double rx = this.domainAxis.valueToJava2D(this.getDomainCrosshairValue(), dataArea, RectangleEdge.BOTTOM);
        double minY = this.rangeAxis.valueToJava2D(dataArea.getMinY(), dataArea, RectangleEdge.LEFT);
        double maxY = this.rangeAxis.valueToJava2D(dataArea.getMaxY(), dataArea, RectangleEdge.RIGHT);
        double ry = this.rangeAxis.valueToJava2D(this.getRangeCrosshairValue(), dataArea, RectangleEdge.LEFT);
        double minX = this.domainAxis.valueToJava2D(dataArea.getMinX(), dataArea, RectangleEdge.BOTTOM);
        double maxX = this.domainAxis.valueToJava2D(dataArea.getMaxX(), dataArea, RectangleEdge.TOP);
        g2.setColor(fill);
        //        Rectangle2D.Double domainCrossHair = new Rectangle2D.Double(rx - 1, minY, 3, maxY - minY);
        //        Rectangle2D.Double rangeCrossHair = new Rectangle2D.Double(minX, ry - 1, maxX - minX, 3);
        //        g2.fill(domainCrossHair);
        //        g2.fill(rangeCrossHair);
        g2.setColor(outline);
        //        g2.draw(domainCrossHair);
        //        g2.draw(rangeCrossHair);
        //System.out.println("CH: " + rx + "," + ry);
        Ellipse2D.Double el2 = new Ellipse2D.Double(rx - r, ry - r, 2 * r, 2 * r);
        //            g2.drawOval(rx - r, ry - r, 2 * r, 2 * r);
        g2.fill(el2);
        g2.draw(el2);
    }

    private void createImage(Graphics2D g2, Rectangle2D dataArea, PlotRenderingInfo info,
            CrosshairState crosshairState) {
        //System.out.println("Creating image! - new");
        BufferedImage bi = createCompatibleImage(this.width, this.height, BufferedImage.TRANSLUCENT);
        float alpha = getDatasetCount() == 1 ? 1.0f : 1.0f / (float) getDatasetCount();
        for (int i = 0; i < getDatasetCount(); i++) {
            XYBlockRenderer xybr = (XYBlockRenderer) getRenderer(i);
            //            System.out.println("alpha in plot " + ((GradientPaintScale) xybr.getPaintScale()).getAlpha());
            //            System.out.println("beta in plot " + ((GradientPaintScale) xybr.getPaintScale()).getBeta());
            //((GradientPaintScale) xybr.getPaintScale()).setAlphaBeta(0, 1);
            //            System.out.println("ramp in plot " + Arrays.deepToString(((GradientPaintScale) xybr.getPaintScale()).getRamp()));
            XYZDataset xyzd = (XYZDataset) getDataset(i);
            BufferedImage bi2 = prepareData(xyzd, this.width, this.height, xybr, g2, dataArea, info, null);
            Graphics2D gg2 = bi.createGraphics();
            gg2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
            gg2.drawImage(bi2, 0, 0, null);
        }
        setDataImage(bi, new Range(0, this.width - 1), new Range(0, this.height - 1));
    }

    /**
     * Draws the gridlines for the plot, if they are visible.
     *
     * @param g2 the graphics device.
     * @param dataArea the data area.
     * @param ticks the ticks.
     */
    @Override
    protected void drawDomainGridlines(Graphics2D g2, Rectangle2D dataArea, List ticks) {

        // draw the domain grid lines, if the flag says they're visible...
        if (isDomainGridlinesVisible()) {
            Iterator iterator = ticks.iterator();
            while (iterator.hasNext()) {
                ValueTick tick = (ValueTick) iterator.next();
                double v = this.domainAxis.valueToJava2D(tick.getValue(), dataArea, RectangleEdge.BOTTOM);
                Line2D line = new Line2D.Double(v, dataArea.getMinY(), v, dataArea.getMaxY());
                g2.setPaint(getDomainGridlinePaint());
                g2.setStroke(getDomainGridlineStroke());
                g2.draw(line);
            }
        }
    }

    /**
     * Draws the gridlines for the plot, if they are visible.
     *
     * @param g2 the graphics device.
     * @param dataArea the data area.
     * @param ticks the ticks.
     */
    @Override
    protected void drawRangeGridlines(Graphics2D g2, Rectangle2D dataArea, List ticks) {

        // draw the range grid lines, if the flag says they're visible...
        if (isRangeGridlinesVisible()) {
            Iterator iterator = ticks.iterator();
            while (iterator.hasNext()) {
                ValueTick tick = (ValueTick) iterator.next();
                double v = this.rangeAxis.valueToJava2D(tick.getValue(), dataArea, RectangleEdge.LEFT);
                Line2D line = new Line2D.Double(dataArea.getMinX(), v, dataArea.getMaxX(), v);
                g2.setPaint(getRangeGridlinePaint());
                g2.setStroke(getRangeGridlineStroke());
                g2.draw(line);
            }
        }

    }

    /**
     * Returns the range of data values to be plotted along the axis, or
     * <code>null</code> if the specified axis isn't the domain axis or the
     * range axis for the plot.
     *
     * @param axis the axis (<code>null</code> permitted).
     *
     * @return The range (possibly <code>null</code>).
     */
    @Override
    public Range getDataRange(ValueAxis axis) {
        Range result = null;
        if (axis == this.domainAxis) {
            result = this.xDataRange;
        } else if (axis == this.rangeAxis) {
            result = this.yDataRange;
        }
        return result;
    }

    /**
     * Calculates the X data range.
     *
     * @param data the data (<code>null</code> permitted).
     *
     * @return The range.
     */
    private Range calculateXDataRange(float[][] data) {

        Range result = null;

        if (data != null) {
            float lowest = Float.POSITIVE_INFINITY;
            float highest = Float.NEGATIVE_INFINITY;
            for (int i = 0; i < data[0].length; i++) {
                float v = data[0][i];
                if (v < lowest) {
                    lowest = v;
                }
                if (v > highest) {
                    highest = v;
                }
            }
            if (lowest <= highest) {
                result = new Range(lowest, highest);
            }
        }

        return result;

    }

    /**
     * Calculates the Y data range.
     *
     * @param data the data (<code>null</code> permitted).
     *
     * @return The range.
     */
    private Range calculateYDataRange(float[][] data) {

        Range result = null;
        if (data != null) {
            float lowest = Float.POSITIVE_INFINITY;
            float highest = Float.NEGATIVE_INFINITY;
            for (int i = 0; i < data[0].length; i++) {
                float v = data[1][i];
                if (v < lowest) {
                    lowest = v;
                }
                if (v > highest) {
                    highest = v;
                }
            }
            if (lowest <= highest) {
                result = new Range(lowest, highest);
            }
        }
        return result;

    }

    /**
     * Multiplies the range on the domain axis by the specified factor.
     *
     * @param factor the zoom factor.
     * @param info the plot rendering info.
     * @param source the source point.
     */
    @Override
    public void zoomDomainAxes(double factor, PlotRenderingInfo info, Point2D source) {
        this.domainAxis.resizeRange(factor);
    }

    /**
     * Multiplies the range on the domain axis by the specified factor.
     *
     * @param factor the zoom factor.
     * @param info the plot rendering info.
     * @param source the source point (in Java2D space).
     * @param useAnchor use source point as zoom anchor?
     *
     * @see #zoomRangeAxes(double, PlotRenderingInfo, Point2D, boolean)
     *
     * @since 1.0.7
     */
    @Override
    public void zoomDomainAxes(double factor, PlotRenderingInfo info, Point2D source, boolean useAnchor) {

        if (useAnchor) {
            // get the source coordinate - this plot has always a VERTICAL
            // orientation
            double sourceX = source.getX();
            double anchorX = this.domainAxis.java2DToValue(sourceX, info.getDataArea(), RectangleEdge.BOTTOM);
            this.domainAxis.resizeRange2(factor, anchorX);
        } else {
            this.domainAxis.resizeRange(factor);
        }
    }

    /**
     * Zooms in on the domain axes.
     *
     * @param lowerPercent the new lower bound as a percentage of the current
     * range.
     * @param upperPercent the new upper bound as a percentage of the current
     * range.
     * @param info the plot rendering info.
     * @param source the source point.
     */
    @Override
    public void zoomDomainAxes(double lowerPercent, double upperPercent, PlotRenderingInfo info, Point2D source) {
        this.domainAxis.zoomRange(lowerPercent, upperPercent);
    }

    /**
     * Multiplies the range on the range axis/axes by the specified factor.
     *
     * @param factor the zoom factor.
     * @param info the plot rendering info.
     * @param source the source point.
     */
    @Override
    public void zoomRangeAxes(double factor, PlotRenderingInfo info, Point2D source) {
        this.rangeAxis.resizeRange(factor);
    }

    /**
     * Multiplies the range on the range axis by the specified factor.
     *
     * @param factor the zoom factor.
     * @param info the plot rendering info.
     * @param source the source point (in Java2D space).
     * @param useAnchor use source point as zoom anchor?
     *
     * @see #zoomDomainAxes(double, PlotRenderingInfo, Point2D, boolean)
     *
     * @since 1.0.7
     */
    @Override
    public void zoomRangeAxes(double factor, PlotRenderingInfo info, Point2D source, boolean useAnchor) {

        if (useAnchor) {
            // get the source coordinate - this plot has always a VERTICAL
            // orientation
            double sourceY = source.getY();
            double anchorY = this.rangeAxis.java2DToValue(sourceY, info.getDataArea(), RectangleEdge.LEFT);
            this.rangeAxis.resizeRange2(factor, anchorY);
        } else {
            this.rangeAxis.resizeRange(factor);
        }
    }

    /**
     * Zooms in on the range axes.
     *
     * @param lowerPercent the new lower bound as a percentage of the current
     * range.
     * @param upperPercent the new upper bound as a percentage of the current
     * range.
     * @param info the plot rendering info.
     * @param source the source point.
     */
    @Override
    public void zoomRangeAxes(double lowerPercent, double upperPercent, PlotRenderingInfo info, Point2D source) {
        this.rangeAxis.zoomRange(lowerPercent, upperPercent);
    }

    /**
     * Returns <code>true</code>.
     *
     * @return A boolean.
     */
    @Override
    public boolean isDomainZoomable() {
        return true;
    }

    /**
     * Returns <code>true</code>.
     *
     * @return A boolean.
     */
    @Override
    public boolean isRangeZoomable() {
        return true;
    }

    /**
     * Returns <code>true</code> if panning is enabled for the domain axes, and
     * <code>false</code> otherwise.
     *
     * @return A boolean.
     *
     * @since 1.0.13
     */
    @Override
    public boolean isDomainPannable() {
        return this.domainPannable;
    }

    /**
     * Sets the flag that enables or disables panning of the plot along the
     * domain axes.
     *
     * @param pannable the new flag value.
     *
     * @since 1.0.13
     */
    @Override
    public void setDomainPannable(boolean pannable) {
        this.domainPannable = pannable;
    }

    /**
     * Returns <code>true</code> if panning is enabled for the range axes, and
     * <code>false</code> otherwise.
     *
     * @return A boolean.
     *
     * @since 1.0.13
     */
    @Override
    public boolean isRangePannable() {
        return this.rangePannable;
    }

    /**
     * Sets the flag that enables or disables panning of the plot along the
     * range axes.
     *
     * @param pannable the new flag value.
     *
     * @since 1.0.13
     */
    @Override
    public void setRangePannable(boolean pannable) {
        this.rangePannable = pannable;
    }

    /**
     *
     * @param event
     */
    @Override
    public void rendererChanged(RendererChangeEvent event) {
        super.rendererChanged(event);
        this.dataImage = null;
        fireChangeEvent();
    }

    /**
     * Pans the domain axes by the specified percentage.
     *
     * @param percent the distance to pan (as a percentage of the axis length).
     * @param info the plot info
     * @param source the source point where the pan action started.
     *
     * @since 1.0.13
     */
    @Override
    public void panDomainAxes(double percent, PlotRenderingInfo info, Point2D source) {
        if (!isDomainPannable() || this.domainAxis == null) {
            return;
        }
        double length = this.domainAxis.getRange().getLength();
        double adj = -percent * length;
        if (this.domainAxis.isInverted()) {
            adj = -adj;
        }
        this.domainAxis.setRange(this.domainAxis.getLowerBound() + adj, this.domainAxis.getUpperBound() + adj);
    }

    /**
     * Pans the range axes by the specified percentage.
     *
     * @param percent the distance to pan (as a percentage of the axis length).
     * @param info the plot info
     * @param source the source point where the pan action started.
     *
     * @since 1.0.13
     */
    @Override
    public void panRangeAxes(double percent, PlotRenderingInfo info, Point2D source) {
        if (!isRangePannable() || this.rangeAxis == null) {
            return;
        }
        double length = this.rangeAxis.getRange().getLength();
        double adj = percent * length;
        if (this.rangeAxis.isInverted()) {
            adj = -adj;
        }
        this.rangeAxis.setRange(this.rangeAxis.getLowerBound() + adj, this.rangeAxis.getUpperBound() + adj);
    }

    /**
     * Tests an arbitrary object for equality with this plot. Note that
     * <code>FastHeatmapPlot</code> carries its data around with it (rather than
     * referencing a dataset), and the data is included in the equality test.
     *
     * @param obj the object (<code>null</code> permitted).
     *
     * @return A boolean.
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }
        if (!super.equals(obj)) {
            return false;
        }
        if (!(obj instanceof FastHeatMapPlot)) {
            return false;
        }
        FastHeatMapPlot that = (FastHeatMapPlot) obj;
        if (this.domainPannable != that.domainPannable) {
            return false;
        }
        if (this.rangePannable != that.rangePannable) {
            return false;
        }
        if (!ObjectUtilities.equal(this.domainAxis, that.domainAxis)) {
            return false;
        }
        if (!ObjectUtilities.equal(this.rangeAxis, that.rangeAxis)) {
            return false;
        }
        if (!PaintUtilities.equal(this.paint, that.paint)) {
            return false;
        }
        if (this.domainGridlinesVisible != that.domainGridlinesVisible) {
            return false;
        }
        if (!PaintUtilities.equal(this.domainGridlinePaint, that.domainGridlinePaint)) {
            return false;
        }
        if (!ObjectUtilities.equal(this.domainGridlineStroke, that.domainGridlineStroke)) {
            return false;
        }
        if (!this.rangeGridlinesVisible == that.rangeGridlinesVisible) {
            return false;
        }
        if (!PaintUtilities.equal(this.rangeGridlinePaint, that.rangeGridlinePaint)) {
            return false;
        }
        if (!ObjectUtilities.equal(this.rangeGridlineStroke, that.rangeGridlineStroke)) {
            return false;
        }
        if (!this.dataImage.equals(that.dataImage)) {
            return false;
        }
        return true;
    }

    /**
     * Returns a clone of the plot.
     *
     * @return A clone.
     *
     * @throws CloneNotSupportedException if some component of the plot does not
     * support cloning.
     */
    @Override
    public Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
        // FastHeatmapPlot clone = (FastHeatmapPlot) super.clone();
        // if (this.dataImage != null) {
        // // clone.dataImage = this.dataImage.
        // }
        // if (this.domainAxis != null) {
        // clone.domainAxis = (ValueAxis) this.domainAxis.clone();
        // clone.domainAxis.setPlot(clone);
        // clone.domainAxis.addChangeListener(clone);
        // }
        // if (this.rangeAxis != null) {
        // clone.rangeAxis = (ValueAxis) this.rangeAxis.clone();
        // clone.rangeAxis.setPlot(clone);
        // clone.rangeAxis.addChangeListener(clone);
        // }
        // return clone;

    }

    /**
     * Provides serialization support.
     *
     * @param stream the output stream.
     *
     * @throws IOException if there is an I/O error.
     */
    private void writeObject(ObjectOutputStream stream) throws IOException {
        stream.defaultWriteObject();
        SerialUtilities.writePaint(this.paint, stream);
        SerialUtilities.writeStroke(this.domainGridlineStroke, stream);
        SerialUtilities.writePaint(this.domainGridlinePaint, stream);
        SerialUtilities.writeStroke(this.rangeGridlineStroke, stream);
        SerialUtilities.writePaint(this.rangeGridlinePaint, stream);
        stream.writeObject(ImageIO.write(this.dataImage, "PNG", stream));
        stream.flush();
    }

    /**
     * Provides serialization support.
     *
     * @param stream the input stream.
     *
     * @throws IOException if there is an I/O error.
     * @throws ClassNotFoundException if there is a classpath problem.
     */
    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
        stream.defaultReadObject();

        this.paint = SerialUtilities.readPaint(stream);
        this.domainGridlineStroke = SerialUtilities.readStroke(stream);
        this.domainGridlinePaint = SerialUtilities.readPaint(stream);

        this.rangeGridlineStroke = SerialUtilities.readStroke(stream);
        this.rangeGridlinePaint = SerialUtilities.readPaint(stream);

        if (this.domainAxis != null) {
            this.domainAxis.addChangeListener(this);
        }

        if (this.rangeAxis != null) {
            this.rangeAxis.addChangeListener(this);
        }

        this.dataImage = ImageIO.read(stream);
    }

    /**
     *
     * @param t
     */
    public void setThresholdCutOff(int t) {
        this.threshholdCutOff = t;
        if (getRenderer() instanceof XYBlockRenderer) {
            XYBlockRenderer xybr = (XYBlockRenderer) getRenderer();
            PaintScale ps = xybr.getPaintScale();
            if (ps instanceof GradientPaintScale) {
                GradientPaintScale gps = (GradientPaintScale) ps;
                if (xybr instanceof XYNoBlockRenderer) {
                    XYNoBlockRenderer xybrn = (XYNoBlockRenderer) xybr;
                    xybrn.setEntityThreshold(gps.getValueForIndex(this.threshholdCutOff));
                }
            }
            resetDataImage();
        }
    }

    /**
     *
     * @return
     */
    public int getThresholdCutOff() {
        return this.threshholdCutOff;
    }
}