Package org.pentaho.plugin.jfreereport.reportcharts.backport

Source Code of org.pentaho.plugin.jfreereport.reportcharts.backport.StackedAreaRenderer

/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program 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 Lesser General Public License for more details.
*
* Copyright (c) 2009 Pentaho Corporation.  All rights reserved.
*/

package org.pentaho.plugin.jfreereport.reportcharts.backport;

import java.io.Serializable;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.Paint;
import java.awt.geom.Rectangle2D;
import java.awt.geom.GeneralPath;

import org.jfree.chart.renderer.category.AreaRenderer;
import org.jfree.chart.renderer.category.CategoryItemRendererState;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.entity.EntityCollection;
import org.jfree.util.PublicCloneable;
import org.jfree.data.Range;
import org.jfree.data.DataUtilities;
import org.jfree.data.general.DatasetUtilities;
import org.jfree.data.category.CategoryDataset;
import org.jfree.ui.RectangleEdge;


/**
* This is a backport of the StackedAreaRenderer of JFreeChart 1.0.13; this class will be removed as soon as we
* are able to upgrade to a more recent version of JFreeChart.
* <p/>
* A renderer that draws stacked area charts for a {@link org.jfree.chart.plot.CategoryPlot}.
* The example shown here is generated by the
* <code>StackedAreaChartDemo1.java</code> program included in the
* JFreeChart Demo Collection:
* <br><br>
* <img src="../../../../../images/StackedAreaRendererSample.png"
* alt="StackedAreaRendererSample.png" />
*/
public class StackedAreaRenderer extends AreaRenderer
        implements PublicCloneable, Serializable
{

    /** For serialization. */
    private static final long serialVersionUID = -3595635038460823663L;

    /** A flag that controls whether the areas display values or percentages. */
    private boolean renderAsPercentages;

    /**
     * Creates a new renderer.
     */
    public StackedAreaRenderer() {
        this(false);
    }

    /**
     * Creates a new renderer.
     *
     * @param renderAsPercentages  a flag that controls whether the data values
     *                             are rendered as percentages.
     */
    public StackedAreaRenderer(final boolean renderAsPercentages) {
        super();
        this.renderAsPercentages = renderAsPercentages;
    }

    /**
     * Returns <code>true</code> if the renderer displays each item value as
     * a percentage (so that the stacked areas add to 100%), and
     * <code>false</code> otherwise.
     *
     * @return A boolean.
     *
     * @since 1.0.3
     */
    public boolean getRenderAsPercentages() {
        return this.renderAsPercentages;
    }

    /**
     * Sets the flag that controls whether the renderer displays each item
     * value as a percentage (so that the stacked areas add to 100%), and sends
     * a {@link org.jfree.chart.event.RendererChangeEvent} to all registered listeners.
     *
     * @param asPercentages  the flag.
     *
     * @since 1.0.3
     */
    public void setRenderAsPercentages(final boolean asPercentages) {
        this.renderAsPercentages = asPercentages;
        fireChangeEvent();
    }

    /**
     * Returns the number of passes (<code>2</code>) required by this renderer.
     * The first pass is used to draw the areas, the second pass is used to
     * draw the item labels (if visible).
     *
     * @return The number of passes required by the renderer.
     */
    public int getPassCount() {
        return 2;
    }

    /**
     * Returns the range of values the renderer requires to display all the
     * items from the specified dataset.
     *
     * @param dataset  the dataset (<code>null</code> not permitted).
     *
     * @return The range (or <code>null</code> if the dataset is empty).
     */
    public Range findRangeBounds(final CategoryDataset dataset) {
        if (dataset == null) {
            return null;
        }
        if (this.renderAsPercentages) {
            return new Range(0.0, 1.0);
        }
        else {
            return DatasetUtilities.findStackedRangeBounds(dataset);
        }
    }

    /**
     * Draw a single data item.
     *
     * @param g2  the graphics device.
     * @param state  the renderer state.
     * @param dataArea  the data plot area.
     * @param plot  the plot.
     * @param domainAxis  the domain axis.
     * @param rangeAxis  the range axis.
     * @param dataset  the data.
     * @param row  the row index (zero-based).
     * @param column  the column index (zero-based).
     * @param pass  the pass index.
     */
    public void drawItem(final Graphics2D g2,
                         final CategoryItemRendererState state,
                         final Rectangle2D dataArea,
                         final CategoryPlot plot,
                         final CategoryAxis domainAxis,
                         final ValueAxis rangeAxis,
                         final CategoryDataset dataset,
                         final int row,
                         final int column,
                         final int pass) {

        if (!isSeriesVisible(row)) {
            return;
        }
       
        if ( (pass == 1) && !isItemLabelVisible(row, column) )  {
          return;
        }

        // setup for collecting optional entity info...
        Shape entityArea = null;
        final EntityCollection entities = state.getEntityCollection();

        double y1 = 0.0;
        Number n = dataset.getValue(row, column);
        if (n != null) {
            y1 = n.doubleValue();
            if (this.renderAsPercentages) {
                final double total = DataUtilities.calculateColumnTotal(dataset, column);
                y1 = y1 / total;
            }
        }
        final double[] stack1 = getStackValues(dataset, row, column);


        // leave the y values (y1, y0) untranslated as it is going to be be
        // stacked up later by previous series values, after this it will be
        // translated.
        double xx1 = domainAxis.getCategoryMiddle(column, getColumnCount(),
                dataArea, plot.getDomainAxisEdge());


        // get the previous point and the next point so we can calculate a
        // "hot spot" for the area (used by the chart entity)...
        double y0 = 0.0;
        n = dataset.getValue(row, Math.max(column - 1, 0));
        if (n != null) {
            y0 = n.doubleValue();
            if (this.renderAsPercentages) {
                final double total = DataUtilities.calculateColumnTotal(dataset, Math.max(column - 1, 0));
                y0 = y0 / total;
            }
        }
        final double[] stack0 = getStackValues(dataset, row, Math.max(column - 1, 0));

        // FIXME: calculate xx0
        double xx0 = domainAxis.getCategoryStart(column, getColumnCount(),
                dataArea, plot.getDomainAxisEdge());

        final int itemCount = dataset.getColumnCount();
        double y2 = 0.0;
        n = dataset.getValue(row, Math.min(column + 1, itemCount - 1));
        if (n != null) {
            y2 = n.doubleValue();
            if (this.renderAsPercentages) {
                final double total = DataUtilities.calculateColumnTotal(dataset,
                        Math.min(column + 1, itemCount - 1));
                y2 = y2 / total;
            }
        }
        final double[] stack2 = getStackValues(dataset, row, Math.min(column + 1, itemCount - 1));

        double xx2 = domainAxis.getCategoryEnd(column, getColumnCount(),
                dataArea, plot.getDomainAxisEdge());

        // This gets rid of the white lines between most category values
        // Doug Moran - Pentaho
        xx0 = Math.round(xx0);
        xx1 = Math.round(xx1);
        xx2 = Math.round(xx2);
       
        // FIXME: calculate xxLeft and xxRight
        final double xxLeft = xx0;
        final double xxRight = xx2;

        final double[] stackLeft = averageStackValues(stack0, stack1);
        final double[] stackRight = averageStackValues(stack1, stack2);
        final double[] adjStackLeft = adjustedStackValues(stack0, stack1);
        final double[] adjStackRight = adjustedStackValues(stack1, stack2);

        final float transY1;

        final RectangleEdge edge1 = plot.getRangeAxisEdge();

        final GeneralPath left = new GeneralPath();
        final GeneralPath right = new GeneralPath();
        if (y1 >= 0.0) {  // handle positive value
            transY1 = (float) rangeAxis.valueToJava2D(y1 + stack1[1], dataArea,
                    edge1);
            final float transStack1 = (float) rangeAxis.valueToJava2D(stack1[1],
                    dataArea, edge1);
            final float transStackLeft = (float) rangeAxis.valueToJava2D(
                    adjStackLeft[1], dataArea, edge1);

            // LEFT POLYGON
            if (y0 >= 0.0) {
                final double yleft = (y0 + y1) / 2.0 + stackLeft[1];
                final float transYLeft
                    = (float) rangeAxis.valueToJava2D(yleft, dataArea, edge1);
                left.moveTo((float) xx1, transY1);
                left.lineTo((float) xx1, transStack1);
                left.lineTo((float) xxLeft, transStackLeft);
                left.lineTo((float) xxLeft, transYLeft);
                left.closePath();
            }
            else {
                left.moveTo((float) xx1, transStack1);
                left.lineTo((float) xx1, transY1);
                left.lineTo((float) xxLeft, transStackLeft);
                left.closePath();
            }

            final float transStackRight = (float) rangeAxis.valueToJava2D(
                    adjStackRight[1], dataArea, edge1);
            // RIGHT POLYGON
            if (y2 >= 0.0) {
                final double yright = (y1 + y2) / 2.0 + stackRight[1];
                final float transYRight
                    = (float) rangeAxis.valueToJava2D(yright, dataArea, edge1);
                right.moveTo((float) xx1, transStack1);
                right.lineTo((float) xx1, transY1);
                right.lineTo((float) xxRight, transYRight);
                right.lineTo((float) xxRight, transStackRight);
                right.closePath();
            }
            else {
                right.moveTo((float) xx1, transStack1);
                right.lineTo((float) xx1, transY1);
                right.lineTo((float) xxRight, transStackRight);
                right.closePath();
            }
        }
        else // handle negative value
            transY1 = (float) rangeAxis.valueToJava2D(y1 + stack1[0], dataArea,
                    edge1);
            final float transStack1 = (float) rangeAxis.valueToJava2D(stack1[0],
                    dataArea, edge1);
            final float transStackLeft = (float) rangeAxis.valueToJava2D(
                    adjStackLeft[0], dataArea, edge1);

            // LEFT POLYGON
            if (y0 >= 0.0) {
                left.moveTo((float) xx1, transStack1);
                left.lineTo((float) xx1, transY1);
                left.lineTo((float) xxLeft, transStackLeft);
                left.clone();
            }
            else {
                final double yleft = (y0 + y1) / 2.0 + stackLeft[0];
                final float transYLeft = (float) rangeAxis.valueToJava2D(yleft,
                        dataArea, edge1);
                left.moveTo((float) xx1, transY1);
                left.lineTo((float) xx1, transStack1);
                left.lineTo((float) xxLeft, transStackLeft);
                left.lineTo((float) xxLeft, transYLeft);
                left.closePath();
            }
            final float transStackRight = (float) rangeAxis.valueToJava2D(
                    adjStackRight[0], dataArea, edge1);

            // RIGHT POLYGON
            if (y2 >= 0.0) {
                right.moveTo((float) xx1, transStack1);
                right.lineTo((float) xx1, transY1);
                right.lineTo((float) xxRight, transStackRight);
                right.closePath();
            }
            else {
                final double yright = (y1 + y2) / 2.0 + stackRight[0];
                final float transYRight = (float) rangeAxis.valueToJava2D(yright,
                        dataArea, edge1);
                right.moveTo((float) xx1, transStack1);
                right.lineTo((float) xx1, transY1);
                right.lineTo((float) xxRight, transYRight);
                right.lineTo((float) xxRight, transStackRight);
                right.closePath();
            }
        }

        if (pass == 0) {
            final Paint itemPaint = getItemPaint(row, column);
            g2.setPaint(itemPaint);
            g2.fill(left);
            g2.fill(right);

            // add an entity for the item...
            if (entities != null) {
                final GeneralPath gp = new GeneralPath(left);
                gp.append(right, false);
                entityArea = gp;
                addItemEntity(entities, dataset, row, column, entityArea);
            }
        }
        else if (pass == 1) {
            drawItemLabel(g2, plot.getOrientation(), dataset, row, column,
                    xx1, transY1, y1 < 0.0);
        }

    }

    /**
     * Calculates the stacked values (one positive and one negative) of all
     * series up to, but not including, <code>series</code> for the specified
     * item. It returns [0.0, 0.0] if <code>series</code> is the first series.
     *
     * @param dataset  the dataset (<code>null</code> not permitted).
     * @param series  the series index.
     * @param index  the item index.
     *
     * @return An array containing the cumulative negative and positive values
     *     for all series values up to but excluding <code>series</code>
     *     for <code>index</code>.
     */
    protected double[] getStackValues(final CategoryDataset dataset,
            final int series, final int index) {
        final double[] result = new double[2];
        double total = 0.0;
        if (this.renderAsPercentages) {
            total = DataUtilities.calculateColumnTotal(dataset, index);
        }
        for (int i = 0; i < series; i++) {
            if (isSeriesVisible(i)) {
                double v = 0.0;
                final Number n = dataset.getValue(i, index);
                if (n != null) {
                    v = n.doubleValue();
                    if (this.renderAsPercentages) {
                        v = v / total;
                    }
                }
                if (!Double.isNaN(v)) {
                    if (v >= 0.0) {
                        result[1] += v;
                    }
                    else {
                        result[0] += v;
                    }
                }
            }
        }
        return result;
    }

    /**
     * Returns a pair of "stack" values calculated as the mean of the two
     * specified stack value pairs.
     *
     * @param stack1  the first stack pair.
     * @param stack2  the second stack pair.
     *
     * @return A pair of average stack values.
     */
    private double[] averageStackValues(final double[] stack1, final double[] stack2) {
        final double[] result = new double[2];
        result[0] = (stack1[0] + stack2[0]) / 2.0;
        result[1] = (stack1[1] + stack2[1]) / 2.0;
        return result;
    }

    /**
     * Calculates adjusted stack values from the supplied values.  The value is
     * the mean of the supplied values, unless either of the supplied values
     * is zero, in which case the adjusted value is zero also.
     *
     * @param stack1  the first stack pair.
     * @param stack2  the second stack pair.
     *
     * @return A pair of average stack values.
     */
    private double[] adjustedStackValues(final double[] stack1, final double[] stack2) {
        final double[] result = new double[2];
        if (stack1[0] == 0.0 || stack2[0] == 0.0) {
            result[0] = 0.0;
        }
        else {
            result[0] = (stack1[0] + stack2[0]) / 2.0;
        }
        if (stack1[1] == 0.0 || stack2[1] == 0.0) {
            result[1] = 0.0;
        }
        else {
            result[1] = (stack1[1] + stack2[1]) / 2.0;
        }
        return result;
    }

    /**
     * Checks this instance for equality with an arbitrary object.
     *
     * @param obj  the object (<code>null</code> not permitted).
     *
     * @return A boolean.
     */
    public boolean equals(final Object obj) {
        if (obj == this) {
            return true;
        }
        if (!(obj instanceof StackedAreaRenderer)) {
            return false;
        }
        final StackedAreaRenderer that = (StackedAreaRenderer) obj;
        if (this.renderAsPercentages != that.renderAsPercentages) {
            return false;
        }
        return super.equals(obj);
    }

    /**
     * Calculates the stacked value of the all series up to, but not including
     * <code>series</code> for the specified category, <code>category</code>.
     * It returns 0.0 if <code>series</code> is the first series, i.e. 0.
     *
     * @param dataset  the dataset (<code>null</code> not permitted).
     * @param series  the series.
     * @param category  the category.
     *
     * @return double returns a cumulative value for all series' values up to
     *         but excluding <code>series</code> for Object
     *         <code>category</code>.
     *
     * @deprecated As of 1.0.13, as the method is never used internally.
     */
    protected double getPreviousHeight(final CategoryDataset dataset,
            final int series, final int category) {

        double result = 0.0;
        Number n;
        double total = 0.0;
        if (this.renderAsPercentages) {
            total = DataUtilities.calculateColumnTotal(dataset, category);
        }
        for (int i = 0; i < series; i++) {
            n = dataset.getValue(i, category);
            if (n != null) {
                double v = n.doubleValue();
                if (this.renderAsPercentages) {
                    v = v / total;
                }
                result += v;
            }
        }
        return result;

    }

}
TOP

Related Classes of org.pentaho.plugin.jfreereport.reportcharts.backport.StackedAreaRenderer

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.