All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jfree.chart.renderer.xy.StackedXYAreaRenderer Maven / Gradle / Ivy

Go to download

JFreeChart is a class library, written in Java, for generating charts. Utilising the Java2D APIs, it currently supports bar charts, pie charts, line charts, XY-plots and time series plots.

There is a newer version: 1.5.5
Show newest version
/* ===========================================================
 * JFreeChart : a free chart library for the Java(tm) platform
 * ===========================================================
 *
 * (C) Copyright 2000-2014, by Object Refinery Limited and Contributors.
 *
 * Project Info:  http://www.jfree.org/jfreechart/index.html
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 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 Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
 * USA.
 *
 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
 * Other names may be trademarks of their respective owners.]
 *
 * --------------------------
 * StackedXYAreaRenderer.java
 * --------------------------
 * (C) Copyright 2003-2014, by Richard Atkinson and Contributors.
 *
 * Original Author:  Richard Atkinson;
 * Contributor(s):   Christian W. Zuckschwerdt;
 *                   David Gilbert (for Object Refinery Limited);
 *
 * Changes:
 * --------
 * 27-Jul-2003 : Initial version (RA);
 * 30-Jul-2003 : Modified entity constructor (CZ);
 * 18-Aug-2003 : Now handles null values (RA);
 * 20-Aug-2003 : Implemented Cloneable, PublicCloneable and Serializable (DG);
 * 22-Sep-2003 : Changed to be a two pass renderer with optional shape Paint
 *               and Stroke (RA);
 * 07-Oct-2003 : Added renderer state (DG);
 * 10-Feb-2004 : Updated state object and changed drawItem() method to make
 *               overriding easier (DG);
 * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState.  Renamed
 *               XYToolTipGenerator --> XYItemLabelGenerator (DG);
 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
 *               getYValue() (DG);
 * 10-Sep-2004 : Removed getRangeType() method (DG);
 * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
 * 06-Jan-2005 : Override equals() (DG);
 * 07-Jan-2005 : Update for method name changes in DatasetUtilities (DG);
 * 28-Mar-2005 : Use getXValue() and getYValue() from dataset (DG);
 * 06-Jun-2005 : Fixed null pointer exception, plus problems with equals() and
 *               serialization (DG);
 * ------------- JFREECHART 1.0.x ---------------------------------------------
 * 10-Nov-2006 : Fixed bug 1593156, NullPointerException with line
 *               plotting (DG);
 * 02-Feb-2007 : Fixed bug 1649686, crosshairs don't stack y-values (DG);
 * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG);
 * 22-Mar-2007 : Fire change events in setShapePaint() and setShapeStroke()
 *               methods (DG);
 * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG);
 *
 */

package org.jfree.chart.renderer.xy;

import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Stack;

import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.entity.EntityCollection;
import org.jfree.chart.entity.XYItemEntity;
import org.jfree.chart.event.RendererChangeEvent;
import org.jfree.chart.labels.XYToolTipGenerator;
import org.jfree.chart.plot.CrosshairState;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.PlotRenderingInfo;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.urls.XYURLGenerator;
import org.jfree.data.Range;
import org.jfree.data.general.DatasetUtilities;
import org.jfree.data.xy.TableXYDataset;
import org.jfree.data.xy.XYDataset;
import org.jfree.io.SerialUtilities;
import org.jfree.util.ObjectUtilities;
import org.jfree.util.PaintUtilities;
import org.jfree.util.PublicCloneable;
import org.jfree.util.ShapeUtilities;

/**
 * A stacked area renderer for the {@link XYPlot} class.
 * 

* The example shown here is generated by the * StackedXYAreaRendererDemo1.java program included in the * JFreeChart demo collection: *

* StackedXYAreaRendererSample.png *

* SPECIAL NOTE: This renderer does not currently handle negative data values * correctly. This should get fixed at some point, but the current workaround * is to use the {@link StackedXYAreaRenderer2} class instead. */ public class StackedXYAreaRenderer extends XYAreaRenderer implements Cloneable, PublicCloneable, Serializable { /** For serialization. */ private static final long serialVersionUID = 5217394318178570889L; /** * A state object for use by this renderer. */ static class StackedXYAreaRendererState extends XYItemRendererState { /** The area for the current series. */ private Polygon seriesArea; /** The line. */ private Line2D line; /** The points from the last series. */ private Stack lastSeriesPoints; /** The points for the current series. */ private Stack currentSeriesPoints; /** * Creates a new state for the renderer. * * @param info the plot rendering info. */ public StackedXYAreaRendererState(PlotRenderingInfo info) { super(info); this.seriesArea = null; this.line = new Line2D.Double(); this.lastSeriesPoints = new Stack(); this.currentSeriesPoints = new Stack(); } /** * Returns the series area. * * @return The series area. */ public Polygon getSeriesArea() { return this.seriesArea; } /** * Sets the series area. * * @param area the area. */ public void setSeriesArea(Polygon area) { this.seriesArea = area; } /** * Returns the working line. * * @return The working line. */ public Line2D getLine() { return this.line; } /** * Returns the current series points. * * @return The current series points. */ public Stack getCurrentSeriesPoints() { return this.currentSeriesPoints; } /** * Sets the current series points. * * @param points the points. */ public void setCurrentSeriesPoints(Stack points) { this.currentSeriesPoints = points; } /** * Returns the last series points. * * @return The last series points. */ public Stack getLastSeriesPoints() { return this.lastSeriesPoints; } /** * Sets the last series points. * * @param points the points. */ public void setLastSeriesPoints(Stack points) { this.lastSeriesPoints = points; } } /** * Custom Paint for drawing all shapes, if null defaults to series shapes */ private transient Paint shapePaint = null; /** * Custom Stroke for drawing all shapes, if null defaults to series * strokes. */ private transient Stroke shapeStroke = null; /** * Creates a new renderer. */ public StackedXYAreaRenderer() { this(AREA); } /** * Constructs a new renderer. * * @param type the type of the renderer. */ public StackedXYAreaRenderer(int type) { this(type, null, null); } /** * Constructs a new renderer. To specify the type of renderer, use one of * the constants: SHAPES, LINES, * SHAPES_AND_LINES, AREA or * AREA_AND_SHAPES. * * @param type the type of renderer. * @param labelGenerator the tool tip generator to use (null * is none). * @param urlGenerator the URL generator (null permitted). */ public StackedXYAreaRenderer(int type, XYToolTipGenerator labelGenerator, XYURLGenerator urlGenerator) { super(type, labelGenerator, urlGenerator); } /** * Returns the paint used for rendering shapes, or null if * using series paints. * * @return The paint (possibly null). * * @see #setShapePaint(Paint) */ public Paint getShapePaint() { return this.shapePaint; } /** * Sets the paint for rendering shapes and sends a * {@link RendererChangeEvent} to all registered listeners. * * @param shapePaint the paint (null permitted). * * @see #getShapePaint() */ public void setShapePaint(Paint shapePaint) { this.shapePaint = shapePaint; fireChangeEvent(); } /** * Returns the stroke used for rendering shapes, or null if * using series strokes. * * @return The stroke (possibly null). * * @see #setShapeStroke(Stroke) */ public Stroke getShapeStroke() { return this.shapeStroke; } /** * Sets the stroke for rendering shapes and sends a * {@link RendererChangeEvent} to all registered listeners. * * @param shapeStroke the stroke (null permitted). * * @see #getShapeStroke() */ public void setShapeStroke(Stroke shapeStroke) { this.shapeStroke = shapeStroke; fireChangeEvent(); } /** * Initialises the renderer. This method will be called before the first * item is rendered, giving the renderer an opportunity to initialise any * state information it wants to maintain. * * @param g2 the graphics device. * @param dataArea the area inside the axes. * @param plot the plot. * @param data the data. * @param info an optional info collection object to return data back to * the caller. * * @return A state object that should be passed to subsequent calls to the * drawItem() method. */ @Override public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea, XYPlot plot, XYDataset data, PlotRenderingInfo info) { XYItemRendererState state = new StackedXYAreaRendererState(info); // in the rendering process, there is special handling for item // zero, so we can't support processing of visible data items only state.setProcessVisibleItemsOnly(false); return state; } /** * Returns the number of passes required by the renderer. * * @return 2. */ @Override 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 (null permitted). * * @return The range ([0.0, 0.0] if the dataset contains no values, and * null if the dataset is null). * * @throws ClassCastException if dataset is not an instance * of {@link TableXYDataset}. */ @Override public Range findRangeBounds(XYDataset dataset) { if (dataset != null) { return DatasetUtilities.findStackedRangeBounds( (TableXYDataset) dataset); } else { return null; } } /** * Draws the visual representation of a single data item. * * @param g2 the graphics device. * @param state the renderer state. * @param dataArea the area within which the data is being drawn. * @param info collects information about the drawing. * @param plot the plot (can be used to obtain standard color information * etc). * @param domainAxis the domain axis. * @param rangeAxis the range axis. * @param dataset the dataset. * @param series the series index (zero-based). * @param item the item index (zero-based). * @param crosshairState information about crosshairs on a plot. * @param pass the pass index. * * @throws ClassCastException if state is not an instance of * StackedXYAreaRendererState or dataset * is not an instance of {@link TableXYDataset}. */ @Override public void drawItem(Graphics2D g2, XYItemRendererState state, Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot, ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, int series, int item, CrosshairState crosshairState, int pass) { PlotOrientation orientation = plot.getOrientation(); StackedXYAreaRendererState areaState = (StackedXYAreaRendererState) state; // Get the item count for the series, so that we can know which is the // end of the series. TableXYDataset tdataset = (TableXYDataset) dataset; int itemCount = tdataset.getItemCount(); // get the data point... double x1 = dataset.getXValue(series, item); double y1 = dataset.getYValue(series, item); boolean nullPoint = false; if (Double.isNaN(y1)) { y1 = 0.0; nullPoint = true; } // Get height adjustment based on stack and translate to Java2D values double ph1 = getPreviousHeight(tdataset, series, item); double transX1 = domainAxis.valueToJava2D(x1, dataArea, plot.getDomainAxisEdge()); double transY1 = rangeAxis.valueToJava2D(y1 + ph1, dataArea, plot.getRangeAxisEdge()); // Get series Paint and Stroke Paint seriesPaint = getItemPaint(series, item); Paint seriesFillPaint = seriesPaint; if (getUseFillPaint()) { seriesFillPaint = getItemFillPaint(series, item); } Stroke seriesStroke = getItemStroke(series, item); if (pass == 0) { // On first pass render the areas, line and outlines if (item == 0) { // Create a new Area for the series areaState.setSeriesArea(new Polygon()); areaState.setLastSeriesPoints( areaState.getCurrentSeriesPoints()); areaState.setCurrentSeriesPoints(new Stack()); // start from previous height (ph1) double transY2 = rangeAxis.valueToJava2D(ph1, dataArea, plot.getRangeAxisEdge()); // The first point is (x, 0) if (orientation == PlotOrientation.VERTICAL) { areaState.getSeriesArea().addPoint((int) transX1, (int) transY2); } else if (orientation == PlotOrientation.HORIZONTAL) { areaState.getSeriesArea().addPoint((int) transY2, (int) transX1); } } // Add each point to Area (x, y) if (orientation == PlotOrientation.VERTICAL) { Point point = new Point((int) transX1, (int) transY1); areaState.getSeriesArea().addPoint((int) point.getX(), (int) point.getY()); areaState.getCurrentSeriesPoints().push(point); } else if (orientation == PlotOrientation.HORIZONTAL) { areaState.getSeriesArea().addPoint((int) transY1, (int) transX1); } if (getPlotLines()) { if (item > 0) { // get the previous data point... double x0 = dataset.getXValue(series, item - 1); double y0 = dataset.getYValue(series, item - 1); double ph0 = getPreviousHeight(tdataset, series, item - 1); double transX0 = domainAxis.valueToJava2D(x0, dataArea, plot.getDomainAxisEdge()); double transY0 = rangeAxis.valueToJava2D(y0 + ph0, dataArea, plot.getRangeAxisEdge()); if (orientation == PlotOrientation.VERTICAL) { areaState.getLine().setLine(transX0, transY0, transX1, transY1); } else if (orientation == PlotOrientation.HORIZONTAL) { areaState.getLine().setLine(transY0, transX0, transY1, transX1); } g2.setPaint(seriesPaint); g2.setStroke(seriesStroke); g2.draw(areaState.getLine()); } } // Check if the item is the last item for the series and number of // items > 0. We can't draw an area for a single point. if (getPlotArea() && item > 0 && item == (itemCount - 1)) { double transY2 = rangeAxis.valueToJava2D(ph1, dataArea, plot.getRangeAxisEdge()); if (orientation == PlotOrientation.VERTICAL) { // Add the last point (x,0) areaState.getSeriesArea().addPoint((int) transX1, (int) transY2); } else if (orientation == PlotOrientation.HORIZONTAL) { // Add the last point (x,0) areaState.getSeriesArea().addPoint((int) transY2, (int) transX1); } // Add points from last series to complete the base of the // polygon if (series != 0) { Stack points = areaState.getLastSeriesPoints(); while (!points.empty()) { Point point = (Point) points.pop(); areaState.getSeriesArea().addPoint((int) point.getX(), (int) point.getY()); } } // Fill the polygon g2.setPaint(seriesFillPaint); g2.setStroke(seriesStroke); g2.fill(areaState.getSeriesArea()); // Draw an outline around the Area. if (isOutline()) { g2.setStroke(lookupSeriesOutlineStroke(series)); g2.setPaint(lookupSeriesOutlinePaint(series)); g2.draw(areaState.getSeriesArea()); } } int domainAxisIndex = plot.getDomainAxisIndex(domainAxis); int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis); updateCrosshairValues(crosshairState, x1, ph1 + y1, domainAxisIndex, rangeAxisIndex, transX1, transY1, orientation); } else if (pass == 1) { // On second pass render shapes and collect entity and tooltip // information Shape shape = null; if (getPlotShapes()) { shape = getItemShape(series, item); if (plot.getOrientation() == PlotOrientation.VERTICAL) { shape = ShapeUtilities.createTranslatedShape(shape, transX1, transY1); } else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) { shape = ShapeUtilities.createTranslatedShape(shape, transY1, transX1); } if (!nullPoint) { if (getShapePaint() != null) { g2.setPaint(getShapePaint()); } else { g2.setPaint(seriesPaint); } if (getShapeStroke() != null) { g2.setStroke(getShapeStroke()); } else { g2.setStroke(seriesStroke); } g2.draw(shape); } } else { if (plot.getOrientation() == PlotOrientation.VERTICAL) { shape = new Rectangle2D.Double(transX1 - 3, transY1 - 3, 6.0, 6.0); } else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) { shape = new Rectangle2D.Double(transY1 - 3, transX1 - 3, 6.0, 6.0); } } // collect entity and tool tip information... if (state.getInfo() != null) { EntityCollection entities = state.getEntityCollection(); if (entities != null && shape != null && !nullPoint) { String tip = null; XYToolTipGenerator generator = getToolTipGenerator(series, item); if (generator != null) { tip = generator.generateToolTip(dataset, series, item); } String url = null; if (getURLGenerator() != null) { url = getURLGenerator().generateURL(dataset, series, item); } XYItemEntity entity = new XYItemEntity(shape, dataset, series, item, tip, url); entities.add(entity); } } } } /** * Calculates the stacked value of the all series up to, but not including * series for the specified item. It returns 0.0 if * series is the first series, i.e. 0. * * @param dataset the dataset. * @param series the series. * @param index the index. * * @return The cumulative value for all series' values up to but excluding * series for index. */ protected double getPreviousHeight(TableXYDataset dataset, int series, int index) { double result = 0.0; for (int i = 0; i < series; i++) { double value = dataset.getYValue(i, index); if (!Double.isNaN(value)) { result += value; } } return result; } /** * Tests the renderer for equality with an arbitrary object. * * @param obj the object (null permitted). * * @return A boolean. */ @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof StackedXYAreaRenderer) || !super.equals(obj)) { return false; } StackedXYAreaRenderer that = (StackedXYAreaRenderer) obj; if (!PaintUtilities.equal(this.shapePaint, that.shapePaint)) { return false; } if (!ObjectUtilities.equal(this.shapeStroke, that.shapeStroke)) { return false; } return true; } /** * Returns a clone of the renderer. * * @return A clone. * * @throws CloneNotSupportedException if the renderer cannot be cloned. */ @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } /** * Provides serialization support. * * @param stream the input stream. * * @throws IOException if there is an I/O error. * @throws ClassNotFoundException if there is a classpath problem. */ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); this.shapePaint = SerialUtilities.readPaint(stream); this.shapeStroke = SerialUtilities.readStroke(stream); } /** * Provides serialization support. * * @param stream the output stream. * * @throws IOException if there is an I/O error. */ private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); SerialUtilities.writePaint(this.shapePaint, stream); SerialUtilities.writeStroke(this.shapeStroke, stream); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy