Package org.timepedia.chronoscope.client.plot

Source Code of org.timepedia.chronoscope.client.plot.DefaultXYPlot$RedrawTimer

package org.timepedia.chronoscope.client.plot;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

import org.timepedia.chronoscope.client.*;
import org.timepedia.chronoscope.client.axis.RangeAxis;
import org.timepedia.chronoscope.client.axis.ValueAxis;
import org.timepedia.chronoscope.client.canvas.*;
import org.timepedia.chronoscope.client.data.DatasetListener;
import org.timepedia.chronoscope.client.data.MipMap;
import org.timepedia.chronoscope.client.data.RenderedPoint;
import org.timepedia.chronoscope.client.data.tuple.Tuple2D;
import org.timepedia.chronoscope.client.event.ChartClickEvent;
import org.timepedia.chronoscope.client.event.ChartClickHandler;
import org.timepedia.chronoscope.client.event.PlotChangedEvent;
import org.timepedia.chronoscope.client.event.PlotChangedHandler;
import org.timepedia.chronoscope.client.event.PlotContextMenuEvent;
import org.timepedia.chronoscope.client.event.PlotFocusEvent;
import org.timepedia.chronoscope.client.event.PlotFocusHandler;
import org.timepedia.chronoscope.client.event.PlotHoverEvent;
import org.timepedia.chronoscope.client.event.PlotHoverHandler;
import org.timepedia.chronoscope.client.event.PlotMovedEvent;
import org.timepedia.chronoscope.client.event.PlotMovedHandler;
import org.timepedia.chronoscope.client.gss.GssElement;
import org.timepedia.chronoscope.client.gss.GssProperties;
import org.timepedia.chronoscope.client.overlays.Marker;
import org.timepedia.chronoscope.client.render.*;
import org.timepedia.chronoscope.client.render.domain.TickFormatter;
import org.timepedia.chronoscope.client.render.domain.TickFormatterFactory;
import org.timepedia.chronoscope.client.util.ArgChecker;
import org.timepedia.chronoscope.client.util.Array1D;
import org.timepedia.chronoscope.client.util.DateFormatter;
import org.timepedia.chronoscope.client.util.Interval;
import org.timepedia.chronoscope.client.util.PortableTimer;
import org.timepedia.chronoscope.client.util.PortableTimerTask;
import org.timepedia.chronoscope.client.util.Util;
import org.timepedia.chronoscope.client.util.date.ChronoDate;
import org.timepedia.chronoscope.client.util.date.DateFormatterFactory;
import org.timepedia.chronoscope.client.util.date.FastChronoDate;
import org.timepedia.exporter.client.Export;
import org.timepedia.exporter.client.ExportPackage;
import org.timepedia.exporter.client.Exportable;

import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Timer;

/**
* A DefaultXYPlot is responsible for drawing the main chart area (excluding
* axes), mapping one or more datasets from (domain,range) space to (x,y) screen
* space by delegating to one or more ValueAxis implementations. Drawing for
* each dataset is delegated to Renderers. A plot also maintains state like the
* current selection and focus point.
*
* @author Ray Cromwell <ray@timepedia.org>
*/
@ExportPackage("chronoscope")
public class DefaultXYPlot<T extends Tuple2D>
    implements XYPlot<T>, Exportable, DatasetListener<T>, ZoomListener {

  public boolean SHOW_BOXES = false;

  protected static double MIN_WIDTH_FACTOR = 1.5;
  protected static double MAX_WIDTH_FACTOR = 1.1;

  private boolean legendOverridden;
 
  private double lastZoomDomain = 0;

  private class ExportableHandlerManager extends HandlerManager {

    public ExportableHandlerManager(DefaultXYPlot<T> xyPlot) {
      super(xyPlot);
    }

    public ExportableHandlerRegistration addExportableHandler(
        GwtEvent.Type type, EventHandler handlerType) {
      super.addHandler(type, handlerType);
      return new ExportableHandlerRegistration(this, type, handlerType);
    }
  }

  // Indicator that nothing is selected (e.g. a data point or a data set).

  public static final int NO_SELECTION = -1;

  // private static int globalPlotNumber = 0;
  // private int plotNumber = 0;

  private static final String LAYER_HOVER = "hoverLayer";
  private static final String LAYER_OVERLAY = "overlayLayer";
  private static final String LAYER_PLOT = "plotLayer";

  // The maximum distance that the mouse pointer can stray from a candidate
  // data point and still be considered as referring to that point.

  private static final int MAX_FOCUS_DIST = 8;

  // The maximum distance (only considers x-axis) that the mouse pointer can
  // stray from a data point and still cause that point to be "hovered".

  private static final int MAX_HOVER_DIST = 8;

  private static final double MIN_PLOT_HEIGHT = 20;
  private static final double MIN_BOTTOM_HEIGHT = 16;

  private static final double ZOOM_FACTOR = 1.50d;

  /**
   * Returns the greatest domain value across all datasets for the specified
   * <tt>maxDrawableDataPoints</tt> value.  For each dataset, the max domain
   * value is obtained from the lowest mip level (i.e. highest resolution) whose
   * corresponding datapoint cardinality is not greater than
   * <tt>maxDrawableDataPoints</tt>.
   */
  private static <T extends Tuple2D> double calcVisibleDomainMax(
      int maxDrawableDataPoints, Datasets<T> dataSets) {

    double end = Double.MIN_VALUE;

    for (Dataset<T> ds : dataSets) {
      // find the lowest mip level whose # of data points is not greater
      // than maxDrawableDataPoints
      MipMap mipMap = ds.getMipMapChain()
          .findHighestResolution(maxDrawableDataPoints);
      end = Math.max(end, mipMap.getDomain().getLast());
    }
    return end;
  }

  private static boolean pointExists(int pointIndex) {
    return pointIndex > NO_SELECTION;
  }

  public Interval visDomain, lastVisDomain, widestDomain;

  protected View view;


  private int hoverX, hoverY;

  private boolean firstDraw = true;
  private boolean multiaxis = ChronoscopeOptions.getDefaultMultiaxisMode();

  private GssProperties crosshairProperties, crosshairLabelsProperties;

  private PortableTimer changeTimer = null;
  private PortableTimerTask animationContinuation;
  private PortableTimer animationTimer;

  private Background background;

  private double beginHighlight = Double.MIN_VALUE, endHighlight = Double.MIN_VALUE;

  private Datasets<T> datasets;

  private boolean highlightDrawn;

  private Focus focus = null;

  private BottomPanel bottomPanel;
  private TopPanel topPanel;
  private RangePanel rangePanel;

  private Layer plotLayer, hoverLayer, overlayLayer, backgroundLayer, plotRangeLayer, plotDomainLayer;

  private int[] hoverPoints;

  private Bounds innerBounds;

  private boolean isAnimating = false;

  private int maxDrawableDatapoints = 400;

  private final NearestPoint nearestSingleton = new NearestPoint();

  private ArrayList<Overlay> overlays;

  private Bounds plotBounds, hoverBounds;

  private XYPlotRenderer<T> plotRenderer;

  private double visibleDomainMax;

  private ExportableHandlerManager handlerManager = new ExportableHandlerManager(this);

  public DefaultXYPlot() {
    overlays = new ArrayList<Overlay>();
    // plotNumber = globalPlotNumber++;

    bottomPanel = new BottomPanel();
    topPanel = new TopPanel();
    rangePanel = new RangePanel();
  }

  public <S extends GwtEvent.Type<T>, T extends EventHandler> HandlerRegistration addHandler(
      S type, T handler) {
    return handlerManager.addHandler(type, handler);
  }

  @Export
  public void addOverlay(Overlay overlay) {
    if (overlay != null) {
        overlays.add(overlay);
        overlay.setPlot(this);
    }
    //TODO: should we really redraw here? Kinda expensive if you want to bulk
    // add hundreds of overlays
    redraw(true);
  }

  @Export("addClickHandler")
  public ExportableHandlerRegistration addChartClickHandler(
      ChartClickHandler handler) {
    return handlerManager.addExportableHandler(ChartClickEvent.TYPE, handler);
  }

  @Export("addChangeHandler")
  public ExportableHandlerRegistration addPlotChangedHandler(
      PlotChangedHandler handler) {
    return handlerManager.addExportableHandler(PlotChangedEvent.TYPE, handler);
  }

  @Export("addFocusHandler")
  public ExportableHandlerRegistration addPlotFocusHandler(
      PlotFocusHandler handler) {
    return handlerManager.addExportableHandler(PlotFocusEvent.TYPE, handler);
  }

  @Export("addHoverHandler")
  public ExportableHandlerRegistration addPlotHoverHandler(
      PlotHoverHandler handler) {
    return handlerManager.addExportableHandler(PlotHoverEvent.TYPE, handler);
  }

  @Export("addMoveHandler")
  public ExportableHandlerRegistration addPlotMovedHandler(
      PlotMovedHandler handler) {
    return handlerManager.addExportableHandler(PlotMovedEvent.TYPE, handler);
  }

 
  @Export
  public void setTimeZoneOffsetUTC(int offsetHours) {
      if ((offsetHours > -14) && (offsetHours < 14)) {
        ChronoDate.setTimeZoneOffsetInMilliseconds(offsetHours*60*60*1000);
      }// else { // == 0
          // now 0 should really set to 0 offset from UTC, if you want local use OffsetBrowserLocal
          // ChronoDate.setTimeZoneOffsetInMilliseconds(ChronoDate.getLocalTimeZoneOffsetInMilliseconds());
      // }
     
      topPanel.draw();
      redraw(true);
      bottomPanel.draw();
  }

  @Export
  public void setTimeZoneOffsetBrowserLocal(int offsetHours) {
    // if ((offsetHours >= -12 && offsetHours < 0) || (offsetHours > 0 && offsetHours <= 13)) {
     if ((offsetHours > -14) && (offsetHours < 14)) {
        ChronoDate.setTimeZoneOffsetBrowserLocal(offsetHours*60*60*1000);
     }
     topPanel.draw();
     redraw(true);
     bottomPanel.draw();
  }

  public void animateTo(final double destDomainOrigin,
      final double destCurrentDomain, final PlotMovedEvent.MoveType eventType) {

    animateTo(destDomainOrigin, destCurrentDomain, eventType, null);
  }

  public void animateTo(final double destDomainOrigin,
      final double destCurrentDomain, final PlotMovedEvent.MoveType eventType,
      final PortableTimerTask continuation) {

    animateTo(destDomainOrigin, destCurrentDomain, eventType, continuation, true);
  }

  public double calcDisplayY(int datasetIdx, int pointIdx, int dimension) {
    DrawableDataset dds = plotRenderer.getDrawableDataset(datasetIdx);
    RangeAxis ra = getRangeAxis(datasetIdx);
    double y = dds.getRenderer().getRangeValue(dds.currMipMap.getTuple(pointIdx), dimension);
    if (ra.isCalcRangeAsPercent()) {
      double refY = plotRenderer.calcReferenceY(ra, dds);
      // Interval rangeExtrema = dds.currMipMap.getRangeExtrema(dimension);
      Interval rangeExtrema = ra.getExtrema();
      return rangeExtrema.getPercentChange(refY, y);
    }

    return y;
  }

  public boolean click(int x, int y) {
    if (setFocusXY(x, y)) {
      return true;
    }

    for (Overlay o : overlays) {
      boolean wasOverlayHit = visDomain.contains(o.getDomainX()) && o.isHit(x, y);

      if (wasOverlayHit) {
        o.click(x, y);
        return true;
      }
    }

    if (topPanel.isEnabled()) {
      if (topPanel.click(x, y)) {
        return true;
      }
    }
    if (bottomPanel.isEnabled()) {
      if (bottomPanel.click(x, y)) {
        return true;
      }
    }
    handlerManager.fireEvent(new ChartClickEvent(this, x, y));
    return false;
  }

  @Export
  public void damageAxes() {
    rangePanel.clearDrawCaches();
    bottomPanel.clearDrawCaches();
    topPanel.clearDrawCaches();
    firstDraw = true; // "fresh" draw ...
  }

  public double domainToScreenX(double dataX, int datasetIndex) {
    ValueAxis valueAxis = bottomPanel.getDomainAxisPanel().getValueAxis();
    double userX = valueAxis.dataToUser(dataX);
    return userX * plotBounds.width;
  }

  public double domainToWindowX(double dataX, int datasetIndex) {
    return plotBounds.x + domainToScreenX(dataX, datasetIndex);
  }

  public void drawBackground() {
    background.paint(this, backgroundLayer, visDomain.getStart(), visDomain.length());
  }

  public void drawOverviewPlot(Layer overviewLayer) {
    // save original endpoints so they can be restored later
    Interval origVisPlotDomain = getDomain().copy();
    getWidestDomain().copyTo(getDomain());
    // Canvas backingCanvas = view.getCanvas();
    // backingCanvas.beginFrame();

    overviewLayer.save();

    overviewLayer.clear();
    // overviewLayer.setVisibility(false);
    overviewLayer.setFillColor(Color.TRANSPARENT);
    overviewLayer.fillRect(0, 0, overviewLayer.getWidth(), overviewLayer.getHeight());

    Bounds oldBounds = plotBounds;
    Layer oldLayer = plotLayer;
    // plotBounds = new Bounds(
    //    oldBounds.x, view.getHeight() - OverviewAxisPanel.OVERVIEW_HEIGHT,
    //    oldBounds.width, OverviewAxisPanel.OVERVIEW_HEIGHT);
    plotBounds = getOverviewAxisPanel().getBounds();
    plotLayer = overviewLayer;
    plotRenderer.drawDatasets(true);
    plotBounds = oldBounds;
    plotLayer = oldLayer;

    overviewLayer.restore();

    // backingCanvas.endFrame();
    // restore original endpoints
    origVisPlotDomain.copyTo(getDomain());
  }

  @Export
  public boolean ensureVisible(final double domainX, final double rangeY,
      PortableTimerTask callback) {
    view.ensureViewVisible();
    if (!visDomain.containsOpen(domainX)) {
      scrollAndCenter(domainX, callback);
      return true;
    }
    return false;
  }

  public void fireContextMenuEvent(int x, int y) {
    handlerManager.fireEvent(new PlotContextMenuEvent(this, x, y));
  }

  public void fireEvent(GwtEvent event) {
    handlerManager.fireEvent(event);
  }

  public Bounds getBounds() {
    return plotBounds;
  }

  @Export
  public Chart getChart() {
    return view.getChart();
  }

  public int getCurrentMipLevel(int datasetIndex) {
    return plotRenderer.getDrawableDataset(datasetIndex).currMipMap.getLevel();
  }

  public double getDataCoord(int datasetIndex, int pointIndex, int dim) {
    DrawableDataset<T> dds = plotRenderer.getDrawableDataset(datasetIndex);
    return dds.currMipMap.getTuple(pointIndex).getRange(dim);
  }

  public Tuple2D getDataTuple(int datasetIndex, int pointIndex) {
    DrawableDataset<T> dds = plotRenderer.getDrawableDataset(datasetIndex);
    return dds.currMipMap.getTuple(pointIndex);
  }

  public DatasetRenderer<T> getDatasetRenderer(int datasetIndex) {
    return plotRenderer.getDrawableDataset(datasetIndex).getRenderer();
  }

  @Override
  @Export
  public GssProperties getComputedStyle(String gssSelector) {
    return view.getGssPropertiesBySelector(gssSelector);
  }

  /**
   * Returns the datasets associated with this plot.
   */
  @Export
  public Datasets<T> getDatasets() {
    return this.datasets;
  }

  public double getDataX(int datasetIndex, int pointIndex) {
    DrawableDataset<T> dds = plotRenderer.getDrawableDataset(datasetIndex);
    return dds.currMipMap.getDomain().get(pointIndex);
  }

  public double getDataY(int datasetIndex, int pointIndex) {
    DrawableDataset<T> dds = plotRenderer.getDrawableDataset(datasetIndex);
    return dds.currMipMap.getTuple(pointIndex).getRange0();
  }

  @Export
  public Interval getDomain() {
    return this.visDomain;
  }

  @Export
  public DomainAxisPanel getDomainAxisPanel() {
    return bottomPanel.getDomainAxisPanel();
  }

  public Focus getFocus() {
    return this.focus;
  }

  public String getHistoryToken() {
    return getChart().getChartId() + "(O" + visDomain.getStart() + ",D"
        + visDomain.length() + ")";
  }

  public Layer getPlotRangeLayer() {
    return plotRangeLayer;
  }

  public Layer getPlotDomainLayer() {
    return plotDomainLayer;
  }

  public Layer getHoverLayer() {
    return hoverLayer;
    // return initLayer(hoverLayer, LAYER_HOVER, plotBounds);
  }

  public int[] getHoverPoints() {
    return this.hoverPoints;
  }

  public Layer getOverlayLayer() {
    return overlayLayer;
  }

  public Bounds getInnerBounds() {
    return innerBounds;
  }

  public int getMaxDrawableDataPoints() {
    return (int) (isAnimating && ChronoscopeOptions.isAnimationPreview() ? maxDrawableDatapoints
        : ChronoscopeOptions.getMaxStaticDatapoints());
  }

  public int getNearestVisiblePoint(double domainX, int datasetIndex) {
    DrawableDataset<T> dds = plotRenderer.getDrawableDataset(datasetIndex);
    return Util.binarySearch(dds.currMipMap.getDomain(), domainX);
  }

  public Overlay getOverlayAt(int x, int y) {
    if ((null != overlays) && overlays.size() > 0) {
        for (Overlay o : overlays) {
            boolean wasOverlayHit = visDomain.contains(o.getDomainX()) && o
                    .isHit(x, y);

            if (wasOverlayHit) {
                return o;
            }
        }
    }
    return null;
  }

  public OverviewAxisPanel getOverviewAxisPanel() {
    return bottomPanel.getOverviewAxisPanel();
  }

  public Layer getPlotLayer() {
    return plotLayer;
  }

  /**
   * Return the axis-y referenced by datasetIndex.
   * It could cause an outOfBoundException if the index is greater
   * to the number of axis-y in the plot.
   */
  @Export("getAxis")
  public RangeAxis getRangeAxis(int datasetIndex) {
    return rangePanel.getRangeAxes()[datasetIndex];
  }
  /**
    * Return the axis-y referenced by axisId (usually the units)
    */
  @Export("getRangeAxis")
  public RangeAxis getRangeAxis(String units) {
    return rangePanel.getRangeAxis(units);
  }

  @Export
  public int getRangeAxisCount() {
    return rangePanel.getRangeAxes().length;
  }

  public double getSelectionBegin() {
    return beginHighlight;
  }

  public double getSelectionEnd() {
    return endHighlight;
  }

  public double getVisibleDomainMax() {
    return visibleDomainMax;
  }

  public Interval getWidestDomain() {
    return this.widestDomain;
  }

  public boolean isAnimating() {
    return isAnimating;
  }

  public boolean isMultiaxis() {
    return multiaxis;
  }

  @Export
  public void maxZoomOut() {
    pushHistory();
    animateTo(widestDomain.getStart(), widestDomain.length(), PlotMovedEvent.MoveType.ZOOMED);
  }

  public boolean maxZoomTo(int x, int y) {
    int nearPointIndex = NO_SELECTION;
    int nearDataSetIndex = 0;
    double minNearestDist = MAX_FOCUS_DIST;
    for (int i = 0; i < datasets.size(); i++) {
      double domainX = windowXtoDomain(x);
      double rangeY = windowYtoRange(y, i);
      NearestPoint nearest = this.nearestSingleton;
      findNearestPt(domainX, rangeY, i, DistanceFormula.XY, nearest);
      if (nearest.dist < minNearestDist) {
        nearPointIndex = nearest.pointIndex;
        nearDataSetIndex = i;
        minNearestDist = nearest.dist;
      }
    }

    if (pointExists(nearPointIndex)) {
      maxZoomToPoint(nearPointIndex, nearDataSetIndex);
      return true;
    } else {
      return false;
    }
  }

  @Export
  public void maxZoomToFocus() {
    if (focus != null) {
      maxZoomToPoint(focus.getPointIndex(), focus.getDatasetIndex());
    }
  }

  @Export
  public void moveTo(double domainX) {
    movePlotDomain(domainX);
    fireMoveEvent(PlotMovedEvent.MoveType.DRAGGED);
    this.redraw();
  }

  @Export
  public void nextFocus() {
    shiftFocus(+1);
  }
 
  public void nextZoom() {
    pushHistory();
    double nDomain = fixDomainWidth(visDomain.length() / ZOOM_FACTOR);
    if (lastZoomDomain != nDomain) {
      lastZoomDomain = nDomain;
      animateTo(visDomain.midpoint() - nDomain / 2, nDomain, PlotMovedEvent.MoveType.ZOOMED);
    }
  }
 
  public void onDatasetsReplaced(Datasets<T> datasets){
    this.plotRenderer.init();
  }

  public void onDatasetAdded(Dataset<T> dataset) {
    // Range panel needs to be set back to an uninitialized state (in
    // particular so that it calls its autoAssignDatasetAxes() method).
    //
    // TODO: auxiliary panels should listen to dataset events directly
    // and respond accordingly, rather than forcing this class to manage
    // everything.
    //this.initAuxiliaryPanel(this.rangePanel, this.view);
    this.plotRenderer.addDataset(this.datasets.size()-1, dataset);
    //this.rangePanel = new RangePanel();
    fixDomainDisjoint();
    this.reloadStyles();
  }

  public void onDatasetChanged(final Dataset<T> dataset, final double domainStart, final double domainEnd) {
      view.createTimer(new PortableTimerTask() {
          @Override
          public void run(PortableTimer timer) {
              visibleDomainMax = calcVisibleDomainMax(getMaxDrawableDataPoints(),
                      datasets);
              int datasetIndex = DefaultXYPlot.this.datasets.indexOf(dataset);
              if (datasetIndex == -1) {
                  datasetIndex = 0;
              }
              plotRenderer.invalidate(dataset);
              fixDomainDisjoint();
              damageAxes();
              getRangeAxis(datasets.indexOf(dataset)).adjustAbsRange(dataset);
              redraw(true);
          }
      }).schedule(15);
  }

  public void onDatasetRemoved(Dataset<T> dataset, int datasetIndex) {
    if (datasets.isEmpty()) {
      throw new IllegalStateException(
          "Datasets container is empty -- can't render plot.");
    }

    // Remove any marker overlays bound to the removed dataset.
    List<Overlay> tmpOverlays = overlays;
    overlays = new ArrayList<Overlay>();
    for (int i = 0; i < tmpOverlays.size(); i++) {
      Overlay o = tmpOverlays.get(i);
      if (o instanceof Marker) {
        Marker m = (Marker) o;
        int markerDatasetIdx = m.getDatasetIndex();
        boolean doRemoveMarker = false;
        if (markerDatasetIdx == datasetIndex) {
          m.setDatasetIndex(-1);
          doRemoveMarker = true;
        } else if (markerDatasetIdx > datasetIndex) {
          // HACKITY-HACK!
          // Since Marker objects currently store the
          // ordinal position of the dataset to which they are bound,
          // we need to decrement all of the indices that are >=
          // the index of the dataset being removed.
          m.setDatasetIndex(markerDatasetIdx - 1);
        }

        if (!doRemoveMarker) {
          overlays.add(o);
        }
      } else {
        overlays.add(o);
      }
    }

    this.plotRenderer.removeDataset(dataset);
    fixDomainDisjoint();
    this.reloadStyles();
  }

  public void onZoom(double intervalInMillis) {
    if (intervalInMillis == Double.MAX_VALUE) {
      maxZoomOut();
    } else {
      double domainStart = getDomain().midpoint() - (intervalInMillis / 2);
      animateTo(domainStart, intervalInMillis, PlotMovedEvent.MoveType.ZOOMED,
          null);
    }
  }

  @Export
  public InfoWindow openInfoWindow(final String html, final double domainX,
      final double rangeY, final int datasetIndex) {

    final InfoWindow window = view
        .createInfoWindow(html, domainToWindowX(domainX, datasetIndex),
            rangeToWindowY(rangeY, datasetIndex) + 5);

    final PortableTimerTask timerTask = new PortableTimerTask() {
      public void run(PortableTimer timer) {
        window.open();
      }
    };

    if (!ensureVisible(domainX, rangeY, timerTask)) {
      window.open();
    }

    return window;
  }

  @Export
  public void pageLeft(double pageSize) {
    page(-pageSize);
  }

  @Export
  public void pageRight(double pageSize) {
    page(pageSize);
  }

  @Export
  public void prevFocus() {
    shiftFocus(-1);
  }

  @Export
  public void prevZoom() {
    pushHistory();
    double nDomain = fixDomainWidth(visDomain.length() * ZOOM_FACTOR);
    if (lastZoomDomain != nDomain) {
      lastZoomDomain = nDomain;
      animateTo(visDomain.midpoint() - nDomain / 2, nDomain,
          PlotMovedEvent.MoveType.ZOOMED);
    }
  }
  // NOTE - really this is rangeToPlotY
  public double rangeToScreenY(Tuple2D pt, int datasetIndex, int dim) {
    DatasetRenderer dr = getDatasetRenderer(datasetIndex);
    return plotBounds.height
        - getRangeAxis(datasetIndex).dataToUser(dr.getRangeValue(pt, dim))
          * (plotBounds.height - getRangeAxis(datasetIndex).getTickLabelHeight());
  }
  // NOTE - really this is rangeToPlotY
  public double rangeToScreenY(double dataY, int datasetIndex) {
    return plotBounds.height -
        getRangeAxis(datasetIndex).dataToUser(dataY)
            * (plotBounds.height - getRangeAxis(datasetIndex).getTickLabelHeight() - 8);
        //FIXME -8 to bring it down from top
   }

  public double rangeToWindowY(double rangeY, int datasetIndex) {
    return plotBounds.y + rangeToScreenY(rangeY, datasetIndex);
  }

 
  @Export
  public void redraw() {
    redraw(firstDraw);
  }

  /**
   * If <tt>forceCenterPlotRedraw==false</tt>, the center plot (specifically the
   * datasets and overlays) is redrawn only when the state of <tt>this.plotDomain</tt>
   * changes.
   * Otherwise if <tt>forceDatasetRedraw==true</tt>, the center plot is redrawn
   * unconditionally.
   */
  public void redraw(boolean force) {
    if (!firstDraw) {
      redrawTimer.redraw(force);
    } else {
      realRedraw(force);
    }
  }

  /**
   * This is a hack:
   * This timer schedules a redraw for a few milliseconds, so as
   * new redraws comming in this interval are ignored.
   * This improves performance in IE.
   *
   * TODO: study the code and avoid redraw(true) when possible. 
   */
  private RedrawTimer redrawTimer = new RedrawTimer();
  private class RedrawTimer extends Timer {
    boolean running = false;
    boolean force = false;
    int gracePeriode;
    Long last;
   
    private RedrawTimer() {
      gracePeriode = ChronoscopeOptions.isLowPerformance() ? 300 : 50;
    }
   
    public void run() {
      try {
        Long start = new Date().getTime();
        System.out.println("Executing realRedraw force=" + force);
        realRedraw(force);
        System.out.println("realRedraw force=" + force + " took: " + (new Date().getTime() - start) + "ms.");
        running = false;
        force = false;
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
    public void redraw(boolean f) {
      System.out.println("RedrawTimer, itercepted call to redraw(" + f + "), running:" + running);
      force = force || f;
      if (!running || ChronoscopeOptions.isLowPerformance()) {
        schedule(gracePeriode);
      }
    }
   
    public void schedule(int delayMillis) {
      int l = delayMillis;
      if (!running) {
        running = true;
        last = new Date().getTime();
      } else {
        cancel();
        l = Math.max(50, (int)(delayMillis - (new Date().getTime() - last)));
      }
      super.schedule(l);
    }
  }
 
  private void realRedraw(boolean forceCenterPlotRedraw) {
    view.getCanvas().beginFrame();

    // if on a low performance device, don't re-render axes or legend
    // when animating
    final boolean canDrawFast = !(isAnimating() &&
            ChronoscopeOptions.isAnimationPreview() && ChronoscopeOptions.isLowPerformance());

    final boolean plotDomainChanged = forceCenterPlotRedraw || !visDomain.approx(lastVisDomain);
            // || ChronoscopeOptions.isVerticalCrosshairEnabled();

    if (firstDraw || canDrawFast) {
        topPanel.draw();
    }

    // Draw the hover points, but not when the plot is currently animating.
    if (isAnimating || hoverX < 1) {
      // ...
    } else if (hoverLayer != null && !firstDraw ){
      hoverLayer.save();

      hoverLayer.clear();
      drawCrossHairs(hoverLayer);
      plotRenderer.drawHoverPoints(hoverLayer);
      if (canDrawFast) {
        drawPlotHighlight(hoverLayer);
      }

      hoverLayer.restore();
    }

    if (plotDomainChanged) {
      drawBackground();
      drawPlot();
      // rangePanel should be drawn after the plot
      rangePanel.draw();

      if (canDrawFast) {
        bottomPanel.draw();
      }
     
      drawOverlays(overlayLayer);
    }
   

    if(SHOW_BOXES){
      drawBoxes();
    }

    view.getCanvas().endFrame();
    visDomain.copyTo(lastVisDomain);
    view.flipCanvas();
   
    firstDraw = false;
  }

  public void layerBox(Layer layer) {
    layer.save();
    layer.setTransparency(.5f);
    layer.setStrokeColor(Color.GREEN);
    layer.setFillColor(Color.GREEN);
    Bounds b = layer.getBounds();
    layer.drawText( 5, 12, layer.getLayerId(), "Helvetica", "normal", "8pt", layer.getLayerId(), Cursor.CONTRASTED);
    layer.restore();
  }

  private void panelFill(Panel panel) {
    Bounds pb = panel.getBounds();
    PaintStyle color = new Color(0,212,0,128);
    Layer layer = panel.getLayer();
    layer.save();
    layer.setFillColor(color);
    layer.fillRect(pb.x,pb.y,pb.width,pb.height);
    layer.closePath();
    layer.restore();
  }

  public void panelBox(Panel panel) {
    for (Panel p:panel.getChildren()){
      layerBox(p.getLayer());
    }
  }

  public void drawBoxes(){
    panelBox(topPanel);
    panelBox(rangePanel.getLeftSubPanel());
    panelBox(rangePanel.getRightSubPanel());
    panelBox(bottomPanel);
    // layerBox(backgroundLayer);
    layerBox(plotLayer);
  }


  @Export
  public void reloadStyles() {

    bottomPanel.clearDrawCaches();
    Interval tmpPlotDomain = visDomain.copy();

    // hack, eval order dependency
    initViewIndependent(datasets);
    fixDomainDisjoint();
    init(view, false);
    ArrayList<Overlay> oldOverlays = overlays;
    overlays = new ArrayList<Overlay>();
    visDomain = plotRenderer.calcWidestPlotDomain();
    tmpPlotDomain.copyTo(visDomain);
    overlays = oldOverlays;

    initCrosshairs();

    redraw(true);
  }


  @Export
  public void removeOverlay(Overlay over) {
    if (null == over) { return; }
    overlays.remove(over);
    over.setPlot(null);
  }

  public void scrollAndCenter(double domainX, PortableTimerTask continuation) {
    pushHistory();

    final double newOrigin = domainX - visDomain.length() / 2;
    animateTo(newOrigin, visDomain.length(), PlotMovedEvent.MoveType.CENTERED,
        continuation);
  }

  @Export
  public void scrollPixels(int amt) {
    final double domainAmt = (double) amt / plotBounds.width * visDomain
        .length();
    final double minDomain = widestDomain.getStart();
    final double maxDomain = widestDomain.getEnd();

    double newDomainOrigin = visDomain.getStart() + domainAmt;
    if (newDomainOrigin + visDomain.length() > maxDomain) {
      newDomainOrigin = maxDomain - visDomain.length();
    } else if (newDomainOrigin < minDomain) {
      newDomainOrigin = minDomain;
    }
    movePlotDomain(newDomainOrigin);
    fireMoveEvent(PlotMovedEvent.MoveType.DRAGGED);
    if (changeTimer != null) {
      changeTimer.cancelTimer();
    }
    changeTimer = view.createTimer(new PortableTimerTask() {
      public void run(PortableTimer timer) {
        changeTimer = null;
        fireChangeEvent();
      }
    });
    changeTimer.schedule(1000);

    redraw(true);
  }

  public void setAnimating(boolean animating) {
    this.isAnimating = animating;
  }

  @Override
  @Export
  public void setAnimationPreview(boolean enabled) {
    ChronoscopeOptions.setAnimationPreview(enabled);
  }

  @Export
  public void setAutoZoomVisibleRange(int dataset, boolean autoZoom) {
    rangePanel.getRangeAxes()[dataset].setAutoZoomVisibleRange(autoZoom);
  }

  public void setDatasetRenderer(int datasetIndex, DatasetRenderer<T> renderer) {
    ArgChecker.isNotNull(renderer, "renderer");
    renderer.setCustomInstalled(true);
    this.plotRenderer.setDatasetRenderer(datasetIndex, renderer);
    this.reloadStyles();
  }

  @Export
  public void setDatasets(Datasets<T> datasets) {
    ArgChecker.isNotNull(datasets, "datasets");
    ArgChecker.isGT(datasets.size(), 0, "datasets.size");
    datasets.addListener(this);
    this.datasets = datasets;
  }
 
  public void setDomainAxisPanel(DomainAxisPanel domainAxisPanel) {
    bottomPanel.setDomainAxisPanel(domainAxisPanel);
  }

  public void setFocus(Focus focus) {
    this.focus = focus;
  }

  public boolean setFocusXY(int x, int y) {
    int nearestPt = NO_SELECTION;
    int nearestSer = 0;
    int nearestDim = 0;
    double minNearestDist = MAX_FOCUS_DIST;
    boolean somePointHasFocus = false;
    if (null == plotBounds) {
      return somePointHasFocus;
    }

    if((x>=plotBounds.x)&&(x<=plotBounds.x+plotBounds.width)&&(y>=plotBounds.y)&&(y<=plotBounds.y+plotBounds.height)) {
      int plotX = x - (int)plotBounds.x;
      int plotY = y - (int)plotBounds.y;
      if (plotX < 0 || plotY < 0) { return false; }
      for (int i = 0; i < datasets.size(); i++) {
        NearestPoint nearest = this.nearestSingleton;
        HashSet<RenderedPoint> nearby = getDatasetRenderer(i).getClickable(plotX, plotY);
        if ((null != nearby) && (nearby.size()>0)) {
          Iterator<RenderedPoint> clique = nearby.iterator();
          while(clique.hasNext()) {
            RenderedPoint pt = clique.next();
            // double domainX = pt.getDomain();
            // double rangeY = pt.getRange0();
            double sx = pt.getPlotX();
            double sy = pt.getPlotY();
            double distance = DistanceFormula.XY.dist(plotX, plotY, sx, sy);
            if (distance < minNearestDist) {
              nearestPt = pt.getDomainIndex();
              nearestSer = pt.getDatasetIndex();
              minNearestDist = distance;
              nearestDim = pt.getDimension();
            }
          }
        }
      }

      somePointHasFocus = pointExists(nearestPt);
      if (somePointHasFocus) {
        setFocusAndNotifyView(nearestSer, nearestPt, nearestDim);
        redraw(true);
      } else {
        setFocusAndNotifyView(null);
      }
    }
    return somePointHasFocus;
  }

  @Export
  public void setHighlight(double startDomainX, double endDomainX) {
    beginHighlight = startDomainX;
    endHighlight = endDomainX;
  }

  public void setHighlight(int selStart, int selEnd) {
    int tmp = Math.min(selStart, selEnd);
    selEnd = Math.max(selStart, selEnd);
    selStart = tmp;
    beginHighlight = windowXtoDomain(selStart);
    endHighlight = windowXtoDomain(selEnd);
    redraw();
  }

  public boolean setHover(int x, int y) {
    if (x<0 || y<0) {
      return false;
    }
    if (null == plotBounds) {
      return false;
    }
    // At the end of this method, this flag should be true iff *any* of the
    // datasets are sufficiently close to 1 or more of the dataset curves to
    // be considered "clickable".
    // closenessThreshold is the cutoff for "sufficiently close".
    this.hoverX = x - (int) plotBounds.x;
    this.hoverY = y - (int) plotBounds.y;

    boolean isCloseToCurve = false;
    final int closenessThreshold = MAX_FOCUS_DIST;

    // True iff one or more hoverPoints have changed since the last call to this method
    boolean isDirty = false;
    if (this.hoverX < 0 || this.hoverY < 0) { return false; }
    NearestPoint nearestHoverPt = this.nearestSingleton;
    for (int i = 0; i < datasets.size(); i++) {
      double dataX = windowXtoDomain(x);
      double dataY = windowYtoRange(y, i);
      findNearestPt(dataX, dataY, i, DistanceFormula.X_ONLY, nearestHoverPt);

      int nearestPointIdx = (nearestHoverPt.dist < MAX_HOVER_DIST)
          ? nearestHoverPt.pointIndex : NO_SELECTION;

      if (nearestPointIdx != hoverPoints[i]) {
        isDirty = true;
      }

      hoverPoints[i] = pointExists(nearestPointIdx) ? nearestPointIdx : NO_SELECTION;

      if (nearestHoverPt.dist <= closenessThreshold) {
        isCloseToCurve = true;
      }
    }

    if (isDirty) {
      fireHoverEvent();
    }
    redraw();

    return isCloseToCurve;
  }

  @Export
  public void setLegendEnabled(boolean b) {
    legendOverridden = true;
    for(int i = 0; i<hoverPoints.length; i++) {
      hoverPoints[i] = -1;
    }
    for(Dataset d : datasets) {
      if(plotRenderer.isInitialized()) {
        plotRenderer.invalidate(d);
      }
    }
    plotRenderer.resetMipMapLevels();
    plotRenderer.sync();
   
    topPanel.setEnabled(b);
    if (plotRenderer.isInitialized()) {
      reloadStyles();
    }
   
  }

  @Override
  @Export
  public void setLegendLabelsVisible(boolean visible) {
    GssProperties gss = getComputedStyle("axislegend labels");
    if (gss != null) {
      gss.setVisible(visible);
      setLegendEnabled(true);
    }
  }

  @Export
  public void setMultiaxis(boolean enabled) {
    this.multiaxis = enabled;
    reloadStyles();
  }

  @Export
  public boolean isOverviewVisible() {
    if (null == bottomPanel) { return false; }
    else return bottomPanel.isOverviewVisible();
  }

  @Export
  public void setOverviewVisible(boolean overviewVisible) {
    bottomPanel.setOverviewVisible(overviewVisible);
  }

  // Deprecated, use setOverviewVisible instead.
  @Export @Deprecated
  public void setOverviewEnabled(boolean overviewEnabled) {
    // bottomPanel.setOverviewEnabled(overviewEnabled);
    setOverviewVisible(overviewEnabled);
  }

  @Export @Deprecated
  public boolean isOverviewEnabled() {
    return isOverviewVisible();
  }

  public void setPlotRenderer(XYPlotRenderer<T> plotRenderer) {
    if (plotRenderer != null) {
      plotRenderer.setPlot(this);
    }
    this.plotRenderer = plotRenderer;
  }

  @Export
  public void setSubPanelsEnabled(boolean enabled) {
    if (!plotRenderer.isInitialized()) {
        plotRenderer.resetMipMapLevels();
  plotRenderer.sync();
    }
    topPanel.setEnabled(enabled);
    bottomPanel.setEnabled(enabled);
    rangePanel.setEnabled(enabled);
  }

  @Export
  public void setVisibleRangeMax(int dataset, double visRangeMax) {
    rangePanel.getRangeAxes()[dataset].setVisibleRangeMax(visRangeMax);
  }

  @Export
  public void setVisibleRangeMin(int dataset, double visRangeMin) {
    rangePanel.getRangeAxes()[dataset].setVisibleRangeMin(visRangeMin);
  }

  public double windowXtoDomain(double x) {
    return bottomPanel.getDomainAxisPanel().getValueAxis().userToData(windowXtoUser(x));
  }


  public double windowXtoUser(double x) {
    return (x - plotBounds.x) / plotBounds.width;
  }

  private double windowYtoRange(int y, int datasetIndex) {
    double userY = (plotBounds.height - (y - plotBounds.y)) / plotBounds.height;
    return getRangeAxis(datasetIndex).userToData(userY);
  }

  @Export
  public void zoomToHighlight() {
    final double newOrigin = beginHighlight;
    double newdomain = endHighlight - beginHighlight;
    pushHistory();
    animateTo(newOrigin, newdomain, PlotMovedEvent.MoveType.ZOOMED);
  }

  /**
   * Methods which do not depend on any visual state of the chart being
   * initialized first. Can be moved early in Plot initialization. Put stuff
   * here that doesn't depend on the axes or layers being initialized.
   */
  protected void initViewIndependent(Datasets<T> datasets) {
    maxDrawableDatapoints = ChronoscopeOptions.getMaxDynamicDatapoints()
        / datasets.size();
    visibleDomainMax = calcVisibleDomainMax(getMaxDrawableDataPoints(),
        datasets);
    resetHoverPoints(datasets.size());
  }

  void drawPlot() {
    plotLayer.save();
    plotLayer.clear();
    // plotLayer.clearTextLayer(Layer.PLOTAREA_OVERLAY);
    plotLayer.setScrollLeft(0);
    plotRenderer.drawDatasets();
    plotLayer.restore();

  }

  private void animateTo(final double destDomainOrigin,
      final double destDomainLength, final PlotMovedEvent.MoveType eventType,
      final PortableTimerTask continuation, final boolean fence) {
    final DefaultXYPlot plot = this;
    if (!isAnimatable()) {
      return;
    }

    // if there is already an animation running, cancel it
    if (animationTimer != null) {
      animationTimer.cancelTimer();
      if (animationContinuation != null) {
        animationContinuation.run(animationTimer);
      }
      animationTimer = null;
    }

    final Interval destDomain;
    if (fence) {
      destDomain = fenceDomain(destDomainOrigin, destDomainLength);
    } else {
      destDomain = new Interval(destDomainOrigin,
          destDomainOrigin + destDomainLength);
    }

    animationContinuation = continuation;
    final Interval visibleDomain = this.visDomain;

    animationTimer = view.createTimer(new PortableTimerTask() {
      final double destDomainMid = destDomain.midpoint();

      final Interval srcDomain = visibleDomain.copy();

      // Ratio of destination domain to current domain
      final double zoomFactor = destDomain.length() / srcDomain.length();

      double startTime = 0;

      boolean lastFrame = false;

      public void run(PortableTimer t) {
       
        // lerpFactor==1 means do just one step, better for low-performance devices (flash)
        double lerpFactor = 1;
        if (!ChronoscopeOptions.isLowPerformance()) {
          if (startTime == 0) {
            startTime = t.getTime();
          }
          double curTime = t.getTime();
          lerpFactor = (curTime - startTime) / 300;
          if (lerpFactor > 1) {
            lerpFactor = 1;
          }
        }
       
        isAnimating = true;

        final double domainCenter =
            (destDomainMid - srcDomain.midpoint()) * lerpFactor + srcDomain
                .midpoint();
        final double domainLength = srcDomain.length() * ((1 - lerpFactor) + (
            zoomFactor * lerpFactor));
        final double domainStart = domainCenter - domainLength / 2;
        visibleDomain.setEndpoints(domainStart, domainStart + domainLength);
        redraw(true);

        if (lerpFactor < 1) {
          t.schedule(10);
        } else if (lastFrame) {
          fireMoveEvent(eventType);
          if (continuation != null) {
            continuation.run(t);
            animationContinuation = null;
          }
          isAnimating = false;
          animationTimer = null;
          //Enlarged load to new data
          plotRenderer.cleanIncrementalData();
          redraw(true);
          fireChangeEvent();
        } else {
          lastFrame = true;
          plot.cancelHighlight();
          animationTimer.schedule(300);
        }
      }
    });

    animationTimer.schedule(10);
  }

  private void calcDomainWidths() {
    widestDomain = plotRenderer.calcWidestPlotDomain();
    visDomain = widestDomain.copy();
  }

  protected double fixDomainWidth(double span) {
    double max = Math.min(span, MAX_WIDTH_FACTOR * getDatasets().getDomainExtrema().length());
    double min = Math.max(span, MIN_WIDTH_FACTOR * getDatasets().getMinInterval());
    span = Math.min(max, span);
    span = Math.max(min, span);
    return span;
  }

   
  /**
   * Turns off an existing plot highlight.
   */
  @Export
  private void cancelHighlight() {
    setHighlight(0, 0);
  }

  private void clearDrawCaches() {
    bottomPanel.clearDrawCaches();
    topPanel.clearDrawCaches();
    rangePanel.clearDrawCaches();
  }
 
  @SuppressWarnings("unchecked")
  private TickFormatter<ChronoDate> getTickFormater() {
    TickFormatterFactory<ChronoDate> fact =  bottomPanel.getDomainAxisPanel().getTickFormatterFactory();
    return fact.findBestFormatter(getDomain().length());
  }
 
  private DateFormatter crosshairFmt;

//  public void clearHoverLayer(Layer hoverLayer) {
//    hoverLayer.save();
//    hoverLayer.clear();
//    // hoverLayer.clearTextLayer(hoverLayer.getLayerId());
//    // hoverLayer.clearRect(0, 0, hoverLayer.getWidth(), hoverLayer.getHeight());
//    hoverLayer.restore();
//  }

  private void drawCrossHairs(Layer hoverLayer) {
    if (ChronoscopeOptions.isVerticalCrosshairEnabled() && hoverX > -1) {
      // hoverLayer.clearTextLayer("crosshair");
      hoverLayer.setFillColor(crosshairProperties.bgColor);
      hoverLayer.setStrokeColor(crosshairProperties.color);
      hoverLayer.setTransparency((float)crosshairProperties.transparency);

      if (hoverX > 0) {
        // consider painting crosshair line on overlay layer for Z order underneath (behind) the points
        hoverLayer.fillRect(hoverX-0.5, 0.5, 1, hoverLayer.getBounds().height);
        int hx = hoverX;
        double dx = windowXtoDomain(hoverX + plotBounds.x);
        ChronoDate cronoDate = new FastChronoDate(dx);

        if (ChronoscopeOptions.isCrosshairDateTimeFormat()) {
          if (crosshairFmt == null && !"auto".equals(ChronoscopeOptions.getCrosshairDateTimeFormat())) {
            crosshairFmt = DateFormatterFactory.getInstance().getDateFormatter(ChronoscopeOptions.getCrosshairDateTimeFormat());
          }
          String label;
          if (crosshairFmt != null) {
            label = crosshairFmt.format(cronoDate.getOffsetTime());
          } else {
            label = getTickFormater().formatCrosshair(cronoDate);
          }
          int labelWidth = hoverLayer.stringWidth(label, "Helvetica", "", "8pt");
          int labelHeight = hoverLayer.stringHeight(label, "Helvetica", "", "8pt");
          hx += dx < getDomain().midpoint() ? 1.0 : -1 - labelWidth;

          hoverLayer.drawText(hx, labelHeight, label, "Helvetica", "", "8pt", "crosshair", Cursor.CONTRASTED);
        }

          int nearestPt = NO_SELECTION;
          int nearestSer = 0;
          int nearestDim = 0;
          NearestPoint nearest = this.nearestSingleton;
         
          if ("nearest".equals(crosshairProperties.pointSelection)) {

            double minNearestDist = MAX_FOCUS_DIST;
           
            for (int i = 0; i < datasets.size(); i++) {
              double domainX = windowXtoDomain(hoverX + plotBounds.x);
              double rangeY = windowYtoRange((int) (hoverY + plotBounds.y), i);
              findNearestPt(domainX, rangeY, i, DistanceFormula.XY, nearest);

              if (nearest.dist < minNearestDist) {
                nearestPt = nearest.pointIndex;
                nearestSer = i;
                minNearestDist = nearest.dist;
                nearestDim = nearest.dim;
              }
            }
          }
          if (hoverPoints != null) {
            List<LabelLayoutPoint> labelPointList = new ArrayList<LabelLayoutPoint>();
            for (int i = 0; i < hoverPoints.length; i++) {
              int hoverPoint = hoverPoints[i];
              if (nearestPt != NO_SELECTION && i != nearestSer) {
                continue;
              }
              if (hoverPoint > -1) {
                Dataset d = getDatasets().get(i);
                RangeAxis ra = getRangeAxis(i);
                DatasetRenderer r = getDatasetRenderer(i);
                for (int dim  : r.getLegendEntries(d)) {
                  if (nearestPt != NO_SELECTION && dim != nearestDim) {
                    continue;
                  }
                  // Tuple2D tuple = d.getFlyweightTuple(hoverPoint);
                  double realY = getDataCoord(i, hoverPoints[i], dim);
                  double y = r.getRangeValue(getDataTuple(i, hoverPoints[i]), dim);
                  double dy = Math.max(19, rangeToScreenY(y, i)); // FIXME 2xlabelheight
                  String rLabel = "";
                  if (crosshairLabelsProperties.labelVisible) {
                      rLabel = DatasetLegendPanel.createDatasetLabel(this, i, -1, dim, true);
                  }
                  if (crosshairLabelsProperties.valueVisible) {
                      rLabel = ra.getFormattedLabel(realY) + " " + rLabel;
                  }
                  RenderState rs = new RenderState();
                  rs.setPassNumber(dim);
                  // GssProperties props = r.getLegendProperties(dim, rs);

                  hoverLayer.setStrokeColor(Color.BLACK);
                  hx = hoverX + (int) (dx < getDomain().midpoint() ? 1.0
                      : -1 - hoverLayer.stringWidth(rLabel, "Helvetica", "", "8pt"));

                  // Add the label positions for layout and display later
                  labelPointList.add(new LabelLayoutPoint(hx, dy, rLabel, crosshairLabelsProperties, hoverLayer));
                }
              }
            }
            // Sort the labels in to groups and display them in horizontal rows if needed.
            layoutAndDisplayLabels(labelPointList);
          }
        }

    }

    if (ChronoscopeOptions.isHorizontalCrosshairEnabled() && hoverY > -1) {
      hoverLayer.setFillColor(crosshairProperties.bgColor);
      hoverLayer.setStrokeColor(crosshairProperties.color);
      hoverLayer.setTransparency((float)crosshairProperties.transparency);
      hoverLayer.fillRect(0, hoverY, hoverLayer.getBounds().width, 1);
    }
  }

  /**
     * Create a Label Layout Point Comparator
     * @return
     */
    private Comparator<LabelLayoutPoint> createLabelLayoutPointComparator() {
        Comparator<LabelLayoutPoint> comparator = new Comparator<LabelLayoutPoint>() {

            @Override
            public int compare(LabelLayoutPoint point, LabelLayoutPoint compare) {
                return (int) ((point.dy - compare.dy) * 100);
            }
        };
        return comparator;
    }


    private List<List<LabelLayoutPoint>> groupLabelList(List<LabelLayoutPoint> allLabelPoints) {
         List<List<LabelLayoutPoint>> labelGroups = new ArrayList<List<LabelLayoutPoint>>();
        if (allLabelPoints.size() > 1) {

            // Sort the labels by height
            Collections.sort(allLabelPoints, createLabelLayoutPointComparator());

            // create list of label groups
            List<LabelLayoutPoint> currentGroup = new ArrayList<LabelLayoutPoint>();

            double currentY = allLabelPoints.get(0).dy;

            currentGroup.add(allLabelPoints.get(0));
            labelGroups.add(currentGroup);

            int labelSize = 10; // make this dynamic according to font size.

            // loop through the labels and sort them a into groups.
            for (int i = 1; i < allLabelPoints.size(); i++) {

                double nextY = allLabelPoints.get(i).dy;
                if (Math.abs(currentY - nextY) > labelSize) {
                    // new group
                    currentGroup = new ArrayList<LabelLayoutPoint>();
                    labelGroups.add(currentGroup);

                }
                // add to current group
                currentGroup.add(allLabelPoints.get(i));
                currentY = nextY;
            }

        }
         return labelGroups;
    }

    /**
     * Treating Layout which LabelLayoutPoint may overlap and show them
     * @param allLabelPoints
     */
    private void layoutAndDisplayLabels(List<LabelLayoutPoint> allLabelPoints) {
       List<List<LabelLayoutPoint>> labelGroups =groupLabelList(allLabelPoints);
        if (labelGroups.size() > 0) {
            for (int i = 0; i < labelGroups.size(); i++) {
                List<LabelLayoutPoint> overlapList = labelGroups.get(i);
                if (overlapList.size() > 1) {
                    if (hoverX < plotBounds.width * 0.25) {
                        //All labels shows on the right between the region 0-0.25
                        LabelLayoutPoint point = overlapList.get(overlapList.size() - 1);
                        point.layer.setStrokeColor(point.gssProperties.color);
                        point.layer.drawText(point.hx, point.dy, point.labelText, "Helvetica", "", "8pt", "crosshair", Cursor.CONTRASTED);
                        double beforePointHx = point.hx;
                        int textLength = point.labelText.length() * 7;
                        for (int j = overlapList.size() - 2; j >= 0; j--) {
                            LabelLayoutPoint nextPoint = overlapList.get(j);
                            List<Number> infoList = drawLabelOnCrossHairRight(nextPoint, beforePointHx, textLength);
                            beforePointHx = infoList.get(0).doubleValue();
                            textLength = infoList.get(1).intValue();
                        }
                    } else if (hoverX > plotBounds.width * 0.75) {
                        //All labels shows on the left between the region 0.75-1
                        LabelLayoutPoint point = overlapList.get(0);
                        point.layer.setStrokeColor(point.gssProperties.color);
                        point.layer.drawText(point.hx, point.dy, point.labelText, "Helvetica", "", "8pt", "crosshair", Cursor.CONTRASTED);
                        double beforePointHx = point.hx;
                        for (int j = 1; j < overlapList.size(); j++) {
                            LabelLayoutPoint nextPoint = overlapList.get(j);
                            beforePointHx = drawLabelOnCrossHairLeft(nextPoint, beforePointHx);
                        }
                    } else if (hoverX < plotBounds.width * 0.5) {
                        // Shows label between the region 0.25-0.5
                        int middle;
                        if (overlapList.size() % 2 == 0) {
                            middle = overlapList.size() / 2 - 1;
                        } else {
                            middle = overlapList.size() / 2;
                        }
                        LabelLayoutPoint point = overlapList.get(middle);
                        double beforePointHx = point.hx;
                        int textLength = 0;
                        //show labels on the crossHair right
                        for (int j = middle; j >= 0; j--) {
                            LabelLayoutPoint nextPoint = overlapList.get(j);
                            List<Number> infoList = drawLabelOnCrossHairRight(nextPoint, beforePointHx, textLength);
                            beforePointHx = infoList.get(0).doubleValue();
                            textLength = infoList.get(1).intValue();
                        }
                        beforePointHx = point.hx;
                        for (int j = middle + 1; j < overlapList.size(); j++) {
                            //show labels on the crossHair left
                            LabelLayoutPoint nextPoint = overlapList.get(j);
                            beforePointHx = drawLabelOnCrossHairLeft(nextPoint, beforePointHx);
                        }
                    } else {
                        // Shows label between the region 0.5-0.75
                        int middle = overlapList.size() / 2;
                        LabelLayoutPoint point = overlapList.get(middle);
                        double beforePointHx = point.hx + point.labelText.length() * 7;
                        //show labels on the crossHair left
                        for (int j = middle; j < overlapList.size(); j++) {
                            LabelLayoutPoint nextPoint = overlapList.get(j);
                            beforePointHx = drawLabelOnCrossHairLeft(nextPoint, beforePointHx);
                        }
                        beforePointHx = point.hx + point.labelText.length() * 7;
                        int textLength = 0;
                        //show labels on the crossHair right
                        for (int j = middle - 1; j >= 0; j--) {
                            LabelLayoutPoint nextPoint = overlapList.get(j);
                            List<Number> infoList = drawLabelOnCrossHairRight(nextPoint, beforePointHx, textLength);
                            beforePointHx = infoList.get(0).doubleValue();
                            textLength = infoList.get(1).intValue();
                        }
                    }
                } else {
                    //Only one point
                    LabelLayoutPoint pendingPoint = overlapList.get(0);
                    pendingPoint.layer.setStrokeColor(pendingPoint.gssProperties.color);
                    // TODO - find out where the adjustment is made to take it out
                    pendingPoint.layer.drawText(pendingPoint.hx, pendingPoint.dy + 8, pendingPoint.labelText, "Helvetica", "", "8pt", "crosshair", Cursor.CONTRASTED);
                }
            }
        }
    }

    /**
     * Draw a Label On CrossHair Left
     * @param nextPoint
     * @param beforePointHx
     * @return
     */
    private double drawLabelOnCrossHairLeft(LabelLayoutPoint nextPoint, double beforePointHx) {
        nextPoint.layer.setStrokeColor(nextPoint.gssProperties.color);
        beforePointHx -= nextPoint.labelText.length() * 7;
        nextPoint.layer.drawText(beforePointHx, nextPoint.dy, nextPoint.labelText, "Helvetica", "", "8pt", "crosshair", Cursor.CONTRASTED);
        return beforePointHx;
    }

    /**
     * Draw a Label On Cross Hair Right
     * @param nextPoint
     * @param beforePointHx
     * @param textLength
     * @return
     */
    private List<Number> drawLabelOnCrossHairRight(LabelLayoutPoint nextPoint, double beforePointHx, int textLength) {
        nextPoint.layer.setStrokeColor(nextPoint.gssProperties.color);
        beforePointHx += textLength;
        nextPoint.layer.drawText(beforePointHx, nextPoint.dy, nextPoint.labelText, "Helvetica", "", "8pt", "crosshair", Cursor.CONTRASTED);
        textLength = nextPoint.labelText.length() * 7;
        List<Number> beforeInfor = new ArrayList<Number>();
        beforeInfor.add(0, beforePointHx);
        beforeInfor.add(1, textLength);
        return beforeInfor;
    }

    /**
     * Points need to display information,Location to be determined
     */
    private class LabelLayoutPoint {

        private double hx;
        private double dy;
        private String labelText;
        private GssProperties gssProperties;
        private Layer layer;

        LabelLayoutPoint(double hx, double dy, String labelText, GssProperties props, Layer layer) {
            this.hx = hx;
            this.dy = dy - 10; // move labels up an em or so
            this.labelText = labelText;
            this.gssProperties = props;
            this.layer = layer;
        }

        public double getDy() {
            return dy;
        }

        public void setDy(double dy) {
            this.dy = dy;
        }

        public GssProperties getGssProperties() {
            return gssProperties;
        }

        public void setGssProperties(GssProperties gssProperties) {
            this.gssProperties = gssProperties;
        }

        public double getHx() {
            return hx;
        }

        public void setHx(double hx) {
            this.hx = hx;
        }

        public String getLabelText() {
            return labelText;
        }

        public void setLabelText(String labelText) {
            this.labelText = labelText;
        }

        public Layer getLayer() {
            return layer;
        }

        public void setLayer(Layer layer) {
            if (null == layer) { return; } else
            if (layer.equals(this.layer)) { return; } else
            if (this.layer != null) {
              this.layer.dispose();
            }

            this.layer = layer;
        }


    }


  /**
   * Draws the overlays (e.g. markers) onto the center plot.
   */
  private void drawOverlays(Layer layer) {
    layer.save();
    layer.clear();
    layer.restore();

    for (Overlay o : overlays) {
      if (null != o) { o.draw(layer, "overlays"); }
    }
  }

  /**
   * Draws the highlighted region onto the center plot.
   */
  private void drawPlotHighlight(Layer layer) {
    final double domainStart = visDomain.getStart();
    final double domainEnd = visDomain.getEnd();

    if (endHighlight - beginHighlight == 0
        || (beginHighlight < domainStart && endHighlight < domainStart) || (
        beginHighlight > domainEnd && endHighlight > domainEnd)) {

      if (highlightDrawn) {
        //layer.save();
        layer.clear();
        //layer.restore();
        highlightDrawn = false;
      }

      return;
    }

    // need plotBounds relative
    double ux = Math.max(0, domainToScreenX(beginHighlight, 0));
    double ex = Math.min(0 + getInnerBounds().width, domainToScreenX(endHighlight, 0));

    // layer.save();
    layer.setFillColor(new Color("#14FFFF"));
    // layer.setLayerAlpha(0.2f);
    layer.setTransparency(0.2f);
    //layer.clear();
    layer.fillRect(ux, 0, ex - ux, getInnerBounds().height);
    // layer.restore();
    highlightDrawn = true;
  }

  private Interval fenceDomain(double destDomainOrig, double destDomainLength) {
    final double minDomain = widestDomain.getStart();
    final double maxDomain = widestDomain.getEnd();
    final double maxDomainLength = maxDomain - minDomain;
    final double minTickSize = bottomPanel.getDomainAxisPanel().getMinimumTickSize();

    // First ensure that the destination domain length is smaller than the
    // difference between the minimum and maximum dataset domain values. 
    // Then ensure that the destDomain is larger than what the DateAxis thinks
    //is it's smallest tick interval it can handle.
    final double fencedDomainLength = Math
        .max(Math.min(destDomainLength, maxDomainLength), minTickSize);

    double d = destDomainOrig;
    // if destDomainLength was bigger than entire date range of dataset
    // we set the domainOrigin to be the beginning of the dataset range
    if (destDomainLength >= maxDomainLength) {
      d = minDomain;
    } else {
      // else, our domain range is smaller than the max check to see if
      // our origin is smaller than the smallest date range
      if (destDomainOrig < minDomain) {
        // and force it to be the min dataset range value
        d = minDomain;
      } else if (destDomainOrig + destDomainLength > maxDomain) {
        // we we check if the right side of the domain window
        // is past the maximum dataset date range value
        // and if it is, we place the domain origin so that the entire
        // chart fits perfectly in view
        d = maxDomain - destDomainLength;
      }
    }

    final double fencedDomainOrigin = d;
    return new Interval(fencedDomainOrigin,
        fencedDomainOrigin + fencedDomainLength);
  }

  /**
   * Finds the data point on a given dataset whose location is closest to the
   * specified (dataX, dataY) location.  This method modifies the fields in the
   * input argument <tt>np</tt>.
   *
   * @param dataX        - the domain value in data space
   * @param dataY        - the range value in data space
   * @param datasetIndex - the 0-based index of a dataset
   * @param df           - determines which distance formula to use when
   *                     determining the "closeness" of 2 points.
   * @param np           - result object that represents the point nearest to
   *                     (dataX, dataY).
   */
  private void findNearestPt(double dataX, double dataY, int datasetIndex,
      DistanceFormula df, NearestPoint np) {

    MipMap currMipMap = plotRenderer
        .getDrawableDataset(datasetIndex).currMipMap;

    // Find index of data point closest to the right of dataX at the current MIP level
    int closestPtToRight = Util.binarySearch(currMipMap.getDomain(), dataX);

    double sx = domainToScreenX(dataX, datasetIndex);
    double sy = rangeToScreenY(dataY, datasetIndex);
    Tuple2D tupleRight = currMipMap.getTuple(closestPtToRight);
    double rx = domainToScreenX(tupleRight.getDomain(), datasetIndex);
    double ry = rangeToScreenY(tupleRight, datasetIndex, 0);

    int nearestHoverPt;
    if (closestPtToRight == 0) {
      nearestHoverPt = closestPtToRight;
      np.dist = df.dist(sx, sy, rx, ry);
      np.dim = 0;
      for (int d = 1; d < currMipMap.getRangeTupleSize(); d++) {
        double dist2 = df.dist(sx, sy, rx,
            rangeToScreenY(tupleRight, datasetIndex, d));
        if (dist2 < np.dist) {
          np.dist = dist2;
          np.dim = d;
        }
      }
    } else {
      int closestPtToLeft = closestPtToRight - 1;
      Tuple2D tupleLeft = currMipMap.getTuple(closestPtToLeft);
      double lx = domainToScreenX(tupleLeft.getDomain(), datasetIndex);
      double ly = rangeToScreenY(tupleLeft, datasetIndex, 0);
      double lDist = df.dist(sx, sy, lx, ly);
      double rDist = df.dist(sx, sy, rx, ry);
      np.dim = 0;
      if (lDist <= rDist) {
        nearestHoverPt = closestPtToLeft;
        np.dist = lDist;
      } else {
        nearestHoverPt = closestPtToRight;
        np.dist = rDist;
      }
      for (int d = 1; d < currMipMap.getRangeTupleSize(); d++) {
        lDist = df.dist(sx, sy, lx,
            rangeToScreenY(tupleLeft, datasetIndex, d));
        rDist = df.dist(sx, sy, rx,
            rangeToScreenY(tupleRight, datasetIndex, d));
        if (lDist <= rDist && lDist <= np.dist) {
          nearestHoverPt = closestPtToLeft;
          np.dist = lDist;
          np.dim = d;
        } else if (rDist <= lDist && rDist <= np.dist) {
          nearestHoverPt = closestPtToRight;
          np.dist = rDist;
          np.dim = d;
        }
      }
    }
    np.pointIndex = nearestHoverPt;
  }

  private void fireChangeEvent() {
    handlerManager.fireEvent(new PlotChangedEvent(this, getDomain()));
  }

  private void fireFocusEvent(int datasetIndex, int pointIndex) {
    handlerManager
        .fireEvent(new PlotFocusEvent(this, pointIndex, datasetIndex));
  }

  private void fireHoverEvent() {
    handlerManager
        .fireEvent(new PlotHoverEvent(this, Util.copyArray(hoverPoints)));
  }

  private void fireMoveEvent(PlotMovedEvent.MoveType moveType) {
    handlerManager
        .fireEvent(new PlotMovedEvent(this, getDomain().copy(), moveType));
  }

  /**
   * If the Datasets extrema does not intersect the plot's domain, force the
   * plot's domain to be the Datasets extrema.
   */
  private void fixDomainDisjoint() {
    if (!datasets.getDomainExtrema().intersects(getDomain())) {
      getDomain().expand(datasets.getDomainExtrema());
      calcDomainWidths();
    }
  }

 
  public void init() {
    plotRenderer.reset();
    init(view, true);
    redraw(true);
  }

  public void init(View view) {
    init(view, true);
 
 
  private void init(View view, boolean forceNewRangeAxes) {
    ArgChecker.isNotNull(view, "view");
    ArgChecker.isNotNull(datasets, "datasets");
    ArgChecker.isNotNull(plotRenderer, "plotRenderer");

    plotBounds = new Bounds();

    this.view = view;
    this.focus = null;
    plotRenderer.setPlot(this);
    plotRenderer.setView(view);

    initViewIndependent(datasets);

    initCrosshairs();

    if (!plotRenderer.isInitialized()) {
      plotRenderer.init();
    } else {
      plotRenderer.sync();
      plotRenderer.resetMipMapLevels();
      plotRenderer.checkForGssChanges();
    }

    calcDomainWidths();
    ArgChecker.isNotNull(view.getCanvas(), "view.canvas");
    // FIXME - this should use somethings like Canvas.setVisibility which doesn't exist yet
    // view.getCanvas().getRootLayer().setVisibility(true);


    // rangePanel will establish rangeAxis widths used to calculate plotPanel and bottomPanel widths
    rangePanel.setCreateNewAxesOnInit(forceNewRangeAxes);
    initAuxiliaryPanel(rangePanel, view);
    Bounds leftRangeBounds=rangePanel.getLeftSubPanel().getLayer().getBounds();
    leftRangeBounds.x = 0;
    log("setting leftRangeBounds: "+leftRangeBounds);
    rangePanel.setLeftPanelBounds(leftRangeBounds);
    Bounds rightRangeBounds=rangePanel.getRightSubPanel().getLayer().getBounds();
    rightRangeBounds.x = (double)view.getWidth() - rightRangeBounds.width;
    log("setting rightRangeBounds: "+rightRangeBounds);
    rangePanel.setRightPanelBounds(rightRangeBounds);


    plotBounds.x = leftRangeBounds.width;
    plotBounds.width = ((double)view.getWidth()) - plotBounds.x - rightRangeBounds.width;

    // bottomPanel will establish bottomPanel height used to calculate range, plot heights
    initAuxiliaryPanel(bottomPanel, view);
    // If center plot too squished, remove the overview
   /* if (bottomPanel.isOverviewVisible() && bottomPanel.getHeight() > MIN_PLOT_HEIGHT/3.0) {
      bottomPanel.setOverviewVisible(false);
    }*/


    GssProperties legendProps = view.getGssProperties(new GssElementImpl("axislegend", null), "");
    if (legendProps.gssSupplied && !legendOverridden) {
      topPanel.setEnabled(legendProps.visible);
    }

    // FIXME: the top panel's initialization currently depends on the initialization
    // of the bottomPanel (height calc) and the rangePanel (datasets for legend).
    // Remove this dependency if possible.
    initAuxiliaryPanel(topPanel, view);
    topPanel.setPosition(0, 0);

     /* if (topPanel.isEnabled()) {
        // If center plot is too squished, remove legends
        if (plotBounds.height < MIN_PLOT_HEIGHT) {
          log("topPanel height"+topPanel.getBounds().height +" > +"+MIN_PLOT_HEIGHT/3.0);
          // topPanel.getBounds().height = 0;
          topPanel.setEnabled(false);
        }
      }*/

    rangePanel.setY(topPanel.getBounds().bottomY());
    plotBounds.y = topPanel.getBounds().height;
    plotBounds.height = ((double)view.getHeight()) - plotBounds.y - bottomPanel.getHeight();
    rangePanel.setHeight(plotBounds.height);

    // bottomY depends on height being set
    log("setting bottomPanel position " + plotBounds.x + ", " + plotBounds.bottomY());
    bottomPanel.setPosition(plotBounds.x, plotBounds.bottomY());

    // HACK - FIXME - rangePanels are sued to determine width but the tick labels overlap plotbounds
//    rangePanel.getLeftSubPanel().setWidth(leftRangeBounds.width + 100.0);
//    rangePanel.getRightSubPanel().setBounds(new Bounds(
//            rightRangeBounds.x - 100, rightRangeBounds.y,
//            rightRangeBounds.width + 100.0, rightRangeBounds.height));
    // rangePanel.getRightSubPanel().setLayerOffset(100, 0);
     // plotBounds = layoutAll();
    innerBounds = new Bounds(0, 0, plotBounds.width, plotBounds.height);
    // innerBounds = new Bounds(plotBounds);

    clearDrawCaches();
    lastVisDomain = new Interval(0, 0);

    initLayers(plotBounds);
    ArgChecker.isNotNull(view.getCanvas().getLayer(Layer.BACKGROUND), "view.canvas.backgroundLayer");
    background = new GssBackground(view);

    view.canvasSetupDone();
  }

  private void initCrosshairs() {
      GssElement crosshairElement = new GssElementImpl("crosshair", null);
      crosshairProperties = view.getGssProperties(crosshairElement, "");
      if (crosshairProperties.gssSupplied && crosshairProperties.visible) {
        ChronoscopeOptions.setVerticalCrosshairEnabled(true);
        if (!ChronoscopeOptions.isCrosshairDateTimeFormat()) {
          ChronoscopeOptions.setCrosshairDateTimeFormat(crosshairProperties.dateFormat);
        }
      }

      crosshairLabelsProperties = view.getGssPropertiesBySelector("crosshair labels");
      if (null == crosshairLabelsProperties) {
        crosshairLabelsProperties = view.getGssProperties(new GssElementImpl("labels", crosshairElement),"");
      }
  }

  private void initAuxiliaryPanel(AuxiliaryPanel panel, View view) {
    panel.setPlot(this);
    panel.setView(view);
    panel.init();
    panel.layout();
  }

  /**
   * Initializes the layers needed by the center plot.
   */
  private void initLayers(Bounds bounds) {
    backgroundLayer = view.getCanvas().createLayer(Layer.BACKGROUND, bounds);
    plotLayer = view.getCanvas().createLayer(Layer.PLOTAREA, bounds);
    hoverLayer = view.getCanvas().createLayer(Layer.PLOTAREA_HOVER, bounds);
    overlayLayer = view.getCanvas().createLayer(Layer.PLOTAREA_OVERLAY, bounds);
    plotRangeLayer = view.getCanvas().createLayer(Layer.PLOTAREA_RANGE, bounds);
    plotDomainLayer = view.getCanvas().createLayer(Layer.PLOTAREA_DOMAIN, bounds);

  }

  /**
   * @return true only if this plot is in a state such that animations
   *  (e.g. zoom in, pan) are possible.
   */
  private boolean isAnimatable() {
    return this.visDomain.length() != 0.0;
  }

  // NOTE - computing the plot height is why bottom and top panels need to be initialized before the plot
  // and the rangepanel (which depends on the plotheight for the rangeheight)
  private double computePlotHeight() {
    double topHeight, bottomHeight;
    if (null != topPanel && topPanel.isInitialized()) {
      topHeight = topPanel.getBounds().height;
    } else {
      log ("topPanel was NOT INITIALIZED");
      topPanel.init();
      topHeight = topPanel.getBounds().height;
    }

    if (null != bottomPanel && bottomPanel.isInitialized()) {
        bottomHeight = bottomPanel.getHeight();
      } else {
        log ("bottomPanel was NOT INITIALIZED");
        bottomPanel.init();
        bottomHeight = bottomPanel.getHeight();
      }

    log("computerPlotHeight topHeight:"+topHeight+" bottomHeight:"+bottomHeight);
    return view.getHeight() - topHeight - bottomHeight;
  }

  private void maxZoomToPoint(int pointIndex, int datasetIndex) {
    pushHistory();

    Dataset<T> dataset = datasets.get(datasetIndex);
    DrawableDataset dds = plotRenderer.getDrawableDataset(datasetIndex);
    double currMipLevelDomainX = dds.currMipMap.getDomain().get(pointIndex);
    Array1D rawDomain = dds.dataset.getMipMapChain().getMipMap(0).getDomain();
    pointIndex = Util.binarySearch(rawDomain, currMipLevelDomainX);

    final int zoomOffset = 10;
    final double newOrigin = dataset.getX(Math.max(0, pointIndex - zoomOffset));
    final double newdomain =
        dataset.getX(Math.min(dataset.getNumSamples() - 1, pointIndex + zoomOffset))
            - newOrigin;

    animateTo(newOrigin, newdomain, PlotMovedEvent.MoveType.ZOOMED);
  }

  /**
   * Assigns a new domain start value while maintaining the current domain
   * length (i.e. the domain end value is implicitly modified).
   */
  private void movePlotDomain(double newDomainStart) {
    double len = this.visDomain.length();
    this.visDomain.setEndpoints(newDomainStart, newDomainStart + len);
  }

  private void page(double pageSize) {
    pushHistory();
    final double newOrigin = visDomain.getStart() + (visDomain.length()
        * pageSize);
    animateTo(newOrigin, visDomain.length(), PlotMovedEvent.MoveType.PAGED);
  }

  private void pushHistory() {
    HistoryManager.pushHistory();
  }

  /**
   * Fills this.hoverPoints[] with {@link #NO_SELECTION} values. If
   * hoverPoints[] is null or not the same length as this.datsets.size(), it is
   * initialized to the correct size.
   */
  private void resetHoverPoints(int numDatasets) {
    if (hoverPoints == null || hoverPoints.length != numDatasets) {
      hoverPoints = new int[numDatasets];
    }
    Arrays.fill(hoverPoints, NO_SELECTION);
  }

  private void setFocusAndNotifyView(Focus focus) {
    if (focus == null) {
      this.focus = null;
      fireFocusEvent(NO_SELECTION, NO_SELECTION);
    } else {
      setFocusAndNotifyView(focus.getDatasetIndex(), focus.getPointIndex(),
          focus.getDimensionIndex());
    }
  }

  private void setFocusAndNotifyView(int datasetIndex, int pointIndex, int nearestDim) {

    boolean damage = false;
    if (!multiaxis) {
      if (focus == null || focus.getDatasetIndex() != datasetIndex) {
        RangeAxis ra = getRangeAxis(datasetIndex);
        ra.getAxisPanel().setValueAxis(ra);
        damage = true;
      }
    }
    if (this.focus == null) {
      this.focus = new Focus();
    }
    this.focus.setDatasetIndex(datasetIndex);
    this.focus.setPointIndex(pointIndex);
    this.focus.setDimensionIndex(nearestDim);
    double domainX = plotRenderer.getDrawableDataset(datasetIndex).currMipMap.getDomain().get(pointIndex);
    this.focus.setDomainX(domainX);

    if (!multiaxis && damage) {
      damageAxes();
      rangePanel.layout();
    }
    fireFocusEvent(datasetIndex, pointIndex);
  }

  /**
   * Shifts the focus point <tt>n</tt> data points forward or backwards (e.g. a
   * value of <tt>+1</tt> moves the focus point forward, and a value of
   * <tt>-1</tt> moves the focus point backwards).
   */
  private void shiftFocus(int n) {
    if (n == 0) {
      return; // shift focus 0 data points left/right -- that was easy.
    }

    Dataset<T> ds;
    int focusDatasetIdx, focusPointIdx, focusDim = 0;

    if (focus == null) {
      // If no data point currently has the focus, then set the focus point to
      // the point on dataset [0] that's closest to the center of the screen.
      focusDatasetIdx = 0;
      ds = datasets.get(focusDatasetIdx);
      MipMap mipMap = plotRenderer
          .getDrawableDataset(focusDatasetIdx).currMipMap;
      double domainCenter = visDomain.midpoint();
      focusPointIdx = Util.binarySearch(mipMap.getDomain(), domainCenter);
    } else {
      // some data point currently has the focus.
      focusDatasetIdx = focus.getDatasetIndex();
      focusPointIdx = focus.getPointIndex();
      focusDim = focus.getDimensionIndex();
      MipMap mipMap = plotRenderer
          .getDrawableDataset(focusDatasetIdx).currMipMap;
      focusPointIdx += n;

      if (focusPointIdx >= mipMap.size()) {
        focusDim++;
        if (focus.getDimensionIndex() < mipMap.getRangeTupleSize()) {
          focusPointIdx = 0;
        } else {
          focusDim = 0;
          ++focusDatasetIdx;
          if (focusDatasetIdx >= datasets.size()) {
            focusDatasetIdx = 0;
          }
        }
        focusPointIdx = 0;
      } else if (focusPointIdx < 0) {
        focusDim--;
        if (focusDim >= 0) {
          focusPointIdx =
              plotRenderer.getDrawableDataset(focusDatasetIdx).currMipMap.size()
                  - 1;
        } else {
          focusDim = 0;
          --focusDatasetIdx;
          if (focusDatasetIdx < 0) {
            focusDatasetIdx = datasets.size() - 1;
          }
          focusPointIdx =
              plotRenderer.getDrawableDataset(focusDatasetIdx).currMipMap.size()
                  - 1;
        }
      }

      ds = datasets.get(focusDatasetIdx);
    }

    MipMap currMipMap = plotRenderer
        .getDrawableDataset(focusDatasetIdx).currMipMap;
    Tuple2D dataPt = currMipMap.getTuple(focusPointIdx);
    double dataX = dataPt.getDomain();
    double dataY = dataPt.getRange0();
    ensureVisible(dataX, dataY, null);
    setFocusAndNotifyView(focusDatasetIdx, focusPointIdx, focusDim);
    redraw();
  }


    // TODO - these should go elsewhere

    @Export
    @Override
    public void showLegendLabels(boolean visible) {
        topPanel.setlegendLabelGssProperty(visible, null, null, null, null, null, null, null);
    }

    @Export
    @Override
    public void showLegendLabelsValues(boolean visible) {
        topPanel.setlegendLabelGssProperty(null, visible, null, null, null, null, null, null);
    }

    @Export
    @Override
    public void setLegendLabelsFontSize(int pixels) {
        topPanel.setlegendLabelGssProperty(null, null, pixels, null, null, null, null, null);
    }

    @Export
    @Override
    public void setLegendLabelsIconWidth(int pixels) {
        topPanel.setlegendLabelGssProperty(null, null, null, pixels, null, null, null, null);
    }

    @Export
    @Override
    public void setLegendLabelsIconHeight(int pixels) {
        topPanel.setlegendLabelGssProperty(null, null, null, null, pixels, null, null, null);
    }

    @Export
    @Override
    public void setLegendLabelsColumnWidth(int pixels) {
        topPanel.setlegendLabelGssProperty(null, null, null, null, null, pixels, null, null);
    }

    @Export
    @Override
    public void setLegendLabelsColumnCount(int count) {
        topPanel.setlegendLabelGssProperty(null, null, null, null, null, null, count, null);
    }
   
    @Export
    @Override
    public void setLegendLabelsColumnAlignment(boolean align) {
        topPanel.setlegendLabelGssProperty(null, null, null, null, null, null, null, align);
    }

    @Override
    public void clear() {
      // TODO: MCM check if there are objects which could be cleared

      animationContinuation = null;
      animationTimer = null;

      if (null != backgroundLayer) {
        backgroundLayer.dispose();
        backgroundLayer = null;
        background = null;
      }

      if (null != bottomPanel) {
        bottomPanel.dispose();
        bottomPanel = null;
      }

      changeTimer = null;
      crosshairFmt = null;
      crosshairLabelsProperties = null;
      crosshairProperties = null;

      if (datasets != null) {
        datasets.clear();
        datasets = null;
      }

      focus = null;
      handlerManager = null;

      hoverBounds = null;
      if (null != hoverLayer) {
        hoverLayer.dispose();
        hoverLayer = null;
      }

      innerBounds = null;
      lastVisDomain = null;

      if (null != overlayLayer) {
        overlayLayer.dispose();
        overlayLayer = null;
      }
      if (overlays != null) {
        overlays.clear();
        overlays = null;
      }

      plotBounds = null;
      plotRenderer = null;

      if (null != rangePanel) {
        rangePanel.dispose();
        rangePanel = null;
      }

      //stringSizer = null;

      if (null != topPanel) {
        topPanel.dispose();
        topPanel = null;
      }

      if (view != null) {
        view.dispose();
        view = null;
      }

      visDomain = null;
      widestDomain = null;
    }

    private static void log (String msg) {
      System.out.println("DefaultXYPlot> "+msg);
    }
}
TOP

Related Classes of org.timepedia.chronoscope.client.plot.DefaultXYPlot$RedrawTimer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.