org.gwt.speedchart.client.graph.AbstractGraph.java Source code

Java tutorial

Introduction

Here is the source code for org.gwt.speedchart.client.graph.AbstractGraph.java

Source

/*
 * This file was original part of Timepedia Chronoscope.
 *
 * Copyright (C) Timepedia
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

package org.gwt.speedchart.client.graph;

import com.google.gwt.graphics.client.Canvas;
import com.google.gwt.graphics.client.Color;
import com.google.gwt.user.client.ui.FocusPanel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.RequiresResize;

import org.gwt.speedchart.client.GraphUiProps;
import org.gwt.speedchart.client.ChartUiProps;
import org.gwt.speedchart.client.Dataset;
import org.gwt.speedchart.client.Overlay;
import org.gwt.speedchart.client.fx.AnimationListener;
//import org.gwt.speedchart.client.Datasets;
//import org.gwt.speedchart.client.Focus;
//import org.gwt.speedchart.client.XYPlot;
//import org.gwt.speedchart.client.axis.RangeAxis;
//import org.gwt.speedchart.client.canvas.Layer;
//import org.gwt.speedchart.client.canvas.View;
import org.gwt.speedchart.client.data.MipMap;
import org.gwt.speedchart.client.data.MipMapRegion;
import org.gwt.speedchart.client.data.tuple.Tuple2D;
//import org.gwt.speedchart.client.gss.GssElement;
//import org.gwt.speedchart.client.gss.GssProperties;
//import org.gwt.speedchart.client.plot.DefaultXYPlot;
import org.gwt.speedchart.client.util.ArgChecker;
import org.gwt.speedchart.client.util.Interval;
import org.gwt.speedchart.client.util.Array1D;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.dom.client.Element;

/**
 *
 * @author Ray Cromwell <ray@timepedia.org>
 * @author Johan Rydberg <johan.rydberg@gmail.com>
 */
public abstract class AbstractGraph<T extends Tuple2D> extends FocusPanel
        implements AnimationListener, RequiresResize {

    private int COORD_X_WIDTH;
    private int COORD_Y_HEIGHT;

    private boolean isAnimating = false;

    protected final List<DrawableDataset<T>> drawableDatasets = new ArrayList<DrawableDataset<T>>();

    protected List<Overlay> rangeOverlays = new ArrayList<Overlay>();

    protected List<Overlay> domainOverlays = new ArrayList<Overlay>();

    protected final Canvas canvas;

    protected final ChartUiProps chartUiProps;

    protected AbstractGraph(int width, int height, ChartUiProps chartUiProps) {
        this.chartUiProps = chartUiProps;
        COORD_X_WIDTH = width;
        COORD_Y_HEIGHT = height;
        canvas = new Canvas(COORD_X_WIDTH, COORD_Y_HEIGHT);
        setElement(canvas.getElement());
    }

    protected AbstractGraph(ChartUiProps chartUiProps) {
        this(1000, 400, chartUiProps);
    }

    protected Interval visRange;
    protected Interval visDomain;

    public void animationStart() {
        isAnimating = true;
    }

    public void animationStop() {
        isAnimating = false;
    }

    public Interval getVisRange() {
        return visRange;
    }

    public void addDomainOverlay(Overlay domainOverlay) {
        domainOverlays.add(domainOverlay);
    }

    public void addRangeOverlay(Overlay rangeOverlay) {
        rangeOverlays.add(rangeOverlay);
    }

    public void setSize(int coordWidth, int coordHeight) {
        COORD_X_WIDTH = coordWidth;
        COORD_Y_HEIGHT = coordHeight;
        canvas.resize(COORD_X_WIDTH, COORD_Y_HEIGHT);
        canvas.setCoordSize(COORD_X_WIDTH, COORD_Y_HEIGHT);
    }

    public void onResize() {
        Element parentElem = getElement().getParentElement();
        final int clientWidth = parentElem.getClientWidth();
        final int clientHeight = parentElem.getClientHeight();
        setSize(clientWidth, clientHeight);
    }

    private void calcVisibleDomainAndRange(List<DrawableDataset<T>> dds, Interval plotDomain) {

        final int numDatasets = dds.size();

        //     for (int i = 0; i < plot.getRangeAxisCount(); i++) {
        //       plot.getRangeAxis(i).resetVisibleRange();
        //     }

        visRange = null;

        for (int datasetIdx = 0; datasetIdx < numDatasets; datasetIdx++) {
            DrawableDataset<T> drawableDataset = dds.get(datasetIdx);
            Dataset<T> dataSet = drawableDataset.dataset;

            if (!plotDomain.intersects(dataSet.getDomainExtrema())) {
                continue;
            }

            // Find the highest-resolution mipmap whose number of data points
            // that lie within the plot domain is <= maxDataPoints.
            final int maxDrawableDataPoints = getMaxDrawableDataPoints(drawableDataset);
            MipMapRegion bestMipMapRegion = dataSet.getBestMipMapForInterval(plotDomain, maxDrawableDataPoints);

            MipMap bestMipMap = bestMipMapRegion.getMipMap();

            //if (drawableDataset.currMipMap.getLevel() != bestMipMap.getLevel()) {
            if (drawableDataset.currMipMap != bestMipMap) {
                drawableDataset.currMipMap = bestMipMap;
                //plot.getHoverPoints()[datasetIdx] = DefaultXYPlot.NO_SELECTION;
            }

            int domainStartIdx = bestMipMapRegion.getStartIndex();
            int domainEndIdx = bestMipMapRegion.getEndIndex();
            domainStartIdx = Math.max(0, domainStartIdx - 1);
            domainEndIdx = Math.min(domainEndIdx, bestMipMap.size() - 1);

            drawableDataset.visDomainStartIndex = domainStartIdx;
            drawableDataset.visDomainEndIndex = domainEndIdx;
            //Log.info("draw domain start: " + domainStartIdx);
            //Log.info("draw end start: " + domainEndIdx);

            //RangeAxis rangeAxis = drawableDataset.graphUiProps.getRangeAxis(datasetIdx);
            Interval visRange = null;
            if (visRange == null) {
                visRange = calcVisibleRange(bestMipMap, domainStartIdx, domainEndIdx);
            }
            if (this.visRange == null) {
                this.visRange = visRange;
            } else {
                this.visRange.expand(visRange);
            }

            //       if (rangeAxis.isCalcRangeAsPercent()) {
            //         final double refY = calcReferenceY(rangeAxis, drawableDataset);
            //         double maxY = visRange.getEnd();
            //         double minY = visRange.getStart();
            //         visRange.setEndpoints(
            //             RangeAxis.calcPrctDiff(refY, minY), 
            //             RangeAxis.calcPrctDiff(refY, maxY));
            //       }

            //       rangeAxis.adjustVisibleRange(visRange);
        }

        for (Overlay rangeOverlay : rangeOverlays) {
            if (this.visRange == null) {
                this.visRange = rangeOverlay.getInterval();
            } else {
                this.visRange.expand(rangeOverlay.getInterval());
            }
        }

        Interval fixVisibleRange = chartUiProps.getVisibleRangeInterval();
        if (fixVisibleRange != null && visRange != null) {
            if (!chartUiProps.isAutoZoomRangeTop()) {
                this.visRange.setEndpoints(this.visRange.getStart(), fixVisibleRange.getEnd());
            }

            if (!chartUiProps.isAutoZoomRangeBottom()) {
                this.visRange.setEndpoints(fixVisibleRange.getStart(), this.visRange.getEnd());
            }
        }

    }

    private double lx, ly, fx;

    protected double rangeToScreenY(double dataY) {
        //Log.info("rangeToScreenY: " + dataY);
        double c = (dataY - visRange.getStart()) / (visRange.getEnd() - visRange.getStart());
        return COORD_Y_HEIGHT - c * COORD_Y_HEIGHT;
    }

    protected double domainToScreenX(double dataX) {
        double c = visDomain.getRatioFromPoint(dataX);
        //Log.info("domainToScreenX: " + dataX + "; c=" + c);
        return COORD_X_WIDTH * c;
    }

    public double getDomainWidthByUserX(int userPx) {
        double c = (userPx / (double) COORD_X_WIDTH);
        return visDomain.length() * c;
    }

    protected void beginDrawing() {
        canvas.clear();

        if (chartUiProps.isDrawBorders()) {
            canvas.setFillStyle(Color.BLACK);
            canvas.fillRect(0, COORD_Y_HEIGHT - 1, COORD_X_WIDTH - 1, 1);
            canvas.fillRect(0, 0, 1, COORD_Y_HEIGHT - 1);
        }
    }

    protected void beginCurve(DrawableDataset<T> dds) {
        canvas.beginPath();
        lx = ly = fx = -1;
    }

    protected void drawCurvePart(DrawableDataset<T> dds, T point, int methodCallCount) {
        double ux = domainToScreenX(point.getDomain());
        double uy = rangeToScreenY(point.getRange0());

        if (methodCallCount == 0) {
            fx = lx = ux;
            ly = uy;
        } else {
            if (methodCallCount == 1) {
                canvas.moveTo(lx, ly);
            }
            canvas.lineTo(ux, uy);

            lx = ux;
            ly = uy;
        }
    }

    protected void endCurve(DrawableDataset<T> dds) {
        canvas.setGlobalAlpha(1.0);
        canvas.setStrokeStyle(dds.graphUiProps.getStrokeColor());
        canvas.stroke();

        if (!isAnimating) {
            canvas.setGlobalAlpha(0.8);
            canvas.setFillStyle(dds.graphUiProps.getGraphColor());
            //canvas.setFillStyle(dds.fillGradient);
            canvas.lineTo(lx, COORD_Y_HEIGHT);
            canvas.lineTo(fx, COORD_Y_HEIGHT);
            canvas.fill();
        }
    }

    /**
     * Calculates the range-Y extrema values of the specified {@link MipMap}.
     */
    private Interval calcVisibleRange(MipMap mipMap, int domainStartIdx, int domainEndIdx) {
        double rangeMin = Double.POSITIVE_INFINITY;
        double rangeMax = Double.NEGATIVE_INFINITY;
        Iterator<Tuple2D> tupleItr = mipMap.getTupleIterator(domainStartIdx);
        for (int i = domainStartIdx; i <= domainEndIdx; i++) {
            double y = tupleItr.next().getRange0();
            rangeMin = Math.min(rangeMin, y);
            rangeMax = Math.max(rangeMax, y);
        }

        return new Interval(rangeMin, rangeMax);
    }

    private void drawDataset(int datasetIndex) {
        DrawableDataset dds = drawableDatasets.get(datasetIndex);

        Dataset<T> dataSet = dds.dataset;
        //DatasetRenderer<T> renderer = dds.getRenderer();

        if (dds.currMipMap.size() < 2) {
            return;
        }

        MipMap currMipMap = dds.currMipMap;
        final int domainStartIdx = dds.visDomainStartIndex;
        final int domainEndIdx = dds.visDomainEndIndex;
        //RangeAxis rangeAxis = dds.graphUiProps.getRangeAxis(datasetIndex);
        //final boolean calcRangeAsPercent = rangeAxis.isCalcRangeAsPercent();

        beginCurve(dds);

        // Render the curve
        // final double refY = calcReferenceY(dds.graphUiProps, dds);

        int methodCallCount = 0;
        Iterator<Tuple2D> tupleItr = currMipMap.getTupleIterator(domainStartIdx);
        for (int i = domainStartIdx; i <= domainEndIdx; i++) {
            Tuple2D dataPt = tupleItr.next();

            //       if (calcRangeAsPercent) {
            //         LocalTuple tmpTuple = new LocalTuple();
            //         tmpTuple.setXY(dataPt.getDomain(), 
            //              RangeAxis.calcPrctDiff(refY, dataPt.getRange0()));
            //         dataPt = tmpTuple;
            //       }
            // FIXME: refactor to remove cast
            drawCurvePart(dds, (T) dataPt, methodCallCount++);
        }
        endCurve(dds);
    }

    public Interval calcWidestDomain() {
        if (drawableDatasets.isEmpty()) {
            return null;
        }

        Interval widestPlotDomain = null;
        for (DrawableDataset<T> dds : drawableDatasets) {
            Dataset<T> ds = dds.dataset;
            final int maxDrawableDataPoints = getMaxDrawableDataPoints(dds);
            MipMap mm = ds.getMipMapChain().findHighestResolution(maxDrawableDataPoints);

            Interval drawableDomain = getDrawableDomain(mm.getDomain());
            if (widestPlotDomain == null) {
                widestPlotDomain = drawableDomain;
            } else {
                widestPlotDomain.expand(drawableDomain);
            }
        }

        return widestPlotDomain;
    }

    public void drawOverlays() {

        canvas.setGlobalAlpha(0.8);

        for (Overlay rangeOverlay : rangeOverlays) {
            Interval rangeInterval = rangeOverlay.getInterval();
            double startY = rangeToScreenY(rangeInterval.getStart());
            double height = Math.max(rangeToScreenY(rangeInterval.getEnd()) - startY, 1);
            canvas.setFillStyle(rangeOverlay.getUiProps().getColor());
            canvas.fillRect(0, startY, COORD_X_WIDTH, height);
        }

        for (Overlay domainOverlay : domainOverlays) {
            Interval domainInterval = domainOverlay.getInterval();
            if (!domainInterval.intersects(visDomain))
                continue;

            double startX = domainToScreenX(domainInterval.getStart());
            double width = domainToScreenX(domainInterval.getEnd()) - startX;
            width = Math.max(width, 1);
            canvas.setFillStyle(domainOverlay.getUiProps().getColor());
            canvas.fillRect(startX, 0, width, COORD_Y_HEIGHT);
        }

    }

    /**
     * Subclasses can override this method to return a domain span that's larger
     * than the maximum according to current mipmapped domain associated with the
     * {@link DrawableDataset}.  This is sometimes necessary depending on how each
     * datapoint is rendered (e.g. barchart requires domain padding to avoid
     * cropping of the end point bars).
     */
    protected Interval getDrawableDomain(Array1D mipmappedDomain) {
        return new Interval(mipmappedDomain.get(0), mipmappedDomain.getLast());
    }

    public void draw() {
        if (visDomain == null) {
            visDomain = calcWidestDomain();
        }
        drawDatasets();
    }

    public void draw(Interval domain) {
        visDomain = domain.copy();
        drawDatasets();
    }

    protected void drawDatasets() {
        calcVisibleDomainAndRange(drawableDatasets, visDomain);

        //Log.info("visible domain is: " + visDomain.getStart() 
        //        + ", " + visDomain.getEnd());

        beginDrawing();
        drawOverlays();

        final int numDatasets = drawableDatasets.size();
        for (int i = 0; i < numDatasets; i++) {
            drawDataset(i);
        }
    }

    /**
     * Adds a new dataset to the list of datasets that this renderer is
     * responsible for.
     */
    public void addDataset(Dataset<T> dataset, GraphUiProps graphUiProps) {
        ArgChecker.isNotNull(dataset, "dataset");

        DrawableDataset<T> drawableDataset = new DrawableDataset<T>();
        drawableDataset.dataset = dataset;
        drawableDataset.graphUiProps = graphUiProps;
        drawableDataset.currMipMap = drawableDataset.dataset.getMipMapChain().getMipMap(0);
        drawableDataset.maxDrawablePoints = getCurrentMaxDrawableDatapoints();
        drawableDataset.fillGradient = canvas.createLinearGradient(0, 0, 0, COORD_Y_HEIGHT);
        drawableDataset.fillGradient.addColorStop(0.8, Color.TRANSPARENT);
        drawableDataset.fillGradient.addColorStop(0, graphUiProps.getGraphColor());

        drawableDatasets.add(drawableDataset);
    }

    public int getCurrentMaxDrawableDatapoints() {
        return (isAnimating ? 100 : 800);
    }

    /**
     * Returns the lesser of what the {@link XYPlot} and the {@link
     * DatasetRenderer} report as the maximum number of datapoints that they can
     * handle.
     */
    protected int getMaxDrawableDataPoints(DrawableDataset dds) {
        int maxDrawablePoints = getCurrentMaxDrawableDatapoints();
        return Math.min(dds.maxDrawablePoints, maxDrawablePoints);
    }

    /**
     * Removes the specified dataset from the list of datasets that this renderer
     * is responsible for.
     */
    public void removeDataset(Dataset<T> dataset) {
        ArgChecker.isNotNull(dataset, "dataset");

        boolean wasDatasetFound = false;
        for (int i = 0; i < drawableDatasets.size(); i++) {
            DrawableDataset<T> drawableDataset = this.drawableDatasets.get(i);
            if (dataset == drawableDataset.dataset) {
                drawableDatasets.remove(i);
                drawableDataset.invalidate();
                wasDatasetFound = true;
                break;
            }
        }

        // throw a fit if we can't find the dataset-to-be-removed
        if (!wasDatasetFound) {
            throw new RuntimeException("dataset did not exist in drawableDatasets list");
        }
    }

    public void resetMipMapLevels() {
        for (DrawableDataset<T> dds : this.drawableDatasets) {
            dds.currMipMap = dds.dataset.getMipMapChain().getMipMap(0);
        }
    }

    public double calcReferenceY(GraphUiProps guip, DrawableDataset dds) {
        final int refYIndex = guip.isAutoZoomVisibleRange() ? dds.visDomainStartIndex : 0;
        return dds.currMipMap.getTuple(refYIndex).getRange0();
    }

    public void removeDomainOverlay(Overlay domainOverlay) {
        domainOverlays.remove(domainOverlay);
    }

    public void removeRangeOverlay(Overlay rangeOverlay) {
        rangeOverlays.remove(rangeOverlay);
    }

    private static final class LocalTuple implements Tuple2D {
        private double x, y;

        public void setXY(double x, double y) {
            this.x = x;
            this.y = y;
        }

        public double getDomain() {
            return x;
        }

        public double getRange(int index) {
            if (index == 0) {
                return y;
            }
            throw new UnsupportedOperationException("unsupported tuple index: " + index);
        }

        public double getRange0() {
            return y;
        }

        public int size() {
            return 2;
        }

        public String toString() {
            return "x=" + (long) x + "; y=" + y;
        }
    }
}