sorry about that, I neglected to include it.
The gradientpainttransformer should be able to be added to most of the renderers that actually fill in an area, it's in BarRender already.
Here's my XYDifferenceRendererFill, it makes sort of a hybrid between a difference chart and an area chart. I've been playing around w/ different ways of moving the gradient points to make up, down or bouncing motions, but the one that everyone seems to like was an accident using Math.abs() that makes a fractal pattern.
Code: Select all
/* ===========================================================
* JFreeChart : a free chart library for the Java(tm) platform
* ===========================================================
*
* (C) Copyright 2000-2007, 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.
*
* [Java is a trademark or registered trademark of Sun Microsystems, Inc.
* in the United States and other countries.]
*
* -------------------------
* XYDifferenceRenderer.java
* -------------------------
* (C) Copyright 2003-2007, by Object Refinery Limited.
*
* Original Author: David Gilbert (for Object Refinery Limited);
* Contributor(s): Christian W. Zuckschwerdt;
*
* $Id: XYDifferenceRenderer.java,v 1.12.2.10 2007/03/08 17:22:53 mungady Exp $
*
* Changes:
* --------
* 30-Apr-2003 : Version 1 (DG);
* 30-Jul-2003 : Modified entity constructor (CZ);
* 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
* 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
* 09-Feb-2004 : Updated to support horizontal plot orientation (DG);
* 10-Feb-2004 : Added default constructor, setter methods and updated
* Javadocs (DG);
* 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
* 30-Mar-2004 : Fixed bug in getNegativePaint() method (DG);
* 15-Jul-2004 : Switched getX() with getXValue() and getY() with
* getYValue() (DG);
* 25-Aug-2004 : Fixed a bug preventing the use of crosshairs (DG);
* 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
* 19-Jan-2005 : Now accesses only primitive values from dataset (DG);
* 22-Feb-2005 : Override getLegendItem(int, int) to return "line" items (DG);
* 13-Apr-2005 : Fixed shape positioning bug (id = 1182062) (DG);
* 20-Apr-2005 : Use generators for legend tooltips and URLs (DG);
* 04-May-2005 : Override equals() method, renamed get/setPlotShapes() -->
* get/setShapesVisible (DG);
* 09-Jun-2005 : Updated equals() to handle GradientPaint (DG);
* 16-Jun-2005 : Fix bug (1221021) affecting stroke used for each series (DG);
* ------------- JFREECHART 1.0.x ---------------------------------------------
* 24-Jan-2007 : Added flag to allow rounding of x-coordinates, and fixed
* bug in clone() (DG);
* 05-Feb-2007 : Added an extra call to updateCrosshairValues() in
* drawItemPass1(), to fix bug 1564967 (DG);
* 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG);
* 08-Mar-2007 : Fixed entity generation (DG);
*
*/
package com.actsolar.ui.util;
import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.io.Serializable;
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.renderer.xy.XYDifferenceRenderer;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.data.xy.XYDataset;
import org.jfree.ui.GradientPaintTransformer;
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.StandardGradientPaintTransformer;
import org.jfree.util.PublicCloneable;
/**
* A renderer for an {@link XYPlot} that highlights the differences between two
* series. The renderer expects a dataset that:
* <ul>
* <li>has exactly two series;</li>
* <li>each series has the same x-values;</li>
* <li>no <code>null</code> values;
* </ul>
*/
public class XYDifferenceRendererFill extends XYDifferenceRenderer
implements XYItemRenderer,
Cloneable,
PublicCloneable,
Serializable {
/** For serialization. */
private static final long serialVersionUID = -8447915602375584857L;
/**
* Fill in space below difference area w/ opposite color
* 1 Fill below negative, 2 fill below positive
*/
private int fillBelow = 1;
/**
* An optional class used to transform gradient paint objects
*/
private GradientPaintTransformer gradientPaintTransformer;
/**
* This flag controls whether or not the x-coordinates (in Java2D space)
* are rounded to integers. When set to true, this can avoid the vertical
* striping that anti-aliasing can generate. However, the rounding may not
* be appropriate for output in high resolution formats (for example,
* vector graphics formats such as SVG and PDF).
*
* @since 1.0.4
*/
private boolean roundXCoordinates;
/**
* Creates a new renderer with default attributes.
*/
public XYDifferenceRendererFill() {
this(Color.green, Color.red, false);
}
/**
* Creates a new renderer.
*
* @param positivePaint the highlight color for positive differences
* (<code>null</code> not permitted).
* @param negativePaint the highlight color for negative differences
* (<code>null</code> not permitted).
* @param shapes draw shapes?
*/
public XYDifferenceRendererFill(Paint positivePaint, Paint negativePaint,
boolean shapes) {
if (positivePaint == null) {
throw new IllegalArgumentException(
"Null 'positivePaint' argument.");
}
if (negativePaint == null) {
throw new IllegalArgumentException(
"Null 'negativePaint' argument.");
}
super.setPositivePaint(positivePaint);
super.setNegativePaint(negativePaint);
super.setShapesVisible(shapes);
super.setLegendLine(new Line2D.Double(-7.0, 0.0, 7.0, 0.0));
super.setRoundXCoordinates(false);
this.gradientPaintTransformer = new StandardGradientPaintTransformer();
}
/**
* Returns the flag that controls whether or not the area below the difference
* area is filled in w/ the opposing color
*
* @return The flag.
*
* @since 1.0.4
* afoss
*
* @see #setFillBelow(boolean)
*/
public int getFillBelow() {
return this.fillBelow;
}
/**
* Sets the flag that controls whether or not the area below the difference
* area is filled in w/ the opposing color
* {@link RendererChangeEvent} to all registered listeners.
*
* @param round the new flag value.
*
* @since 1.0.4
* afoss ?is this really necessary, if you don't want fillbelow, just use XYDifferenceRenderer?
*
* @see #getFillBelow()
*/
public void setFillBelow(int fill) {
this.fillBelow = fill;
notifyListeners(new RendererChangeEvent(this));
}
/**
* Returns the gradient paint transformer
*
* @return A transformer (<code>null</code> possible).
*
* @see #setGradientPaintTransformer(GradientPaintTransformer)
*/
public GradientPaintTransformer getGradientPaintTransformer() {
return this.gradientPaintTransformer;
}
/**
* Sets the gradient paint transformer and sends a
* {@link RendererChangeEvent} to all registered listeners.
*
* @param transformer the transformer (<code>null</code> permitted).
*
* @see #getGradientPaintTransformer()
*/
public void setGradientPaintTransformer(
GradientPaintTransformer transformer) {
this.gradientPaintTransformer = transformer;
notifyListeners(new RendererChangeEvent(this));
}
/**
* Draws the visual representation of a single data item, first pass.
*
* @param g2 the graphics device.
* @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 (horizontal) axis.
* @param rangeAxis the range (vertical) axis.
* @param dataset the dataset.
* @param series the series index (zero-based).
* @param item the item index (zero-based).
* @param crosshairState crosshair information for the plot
* (<code>null</code> permitted).
*/
protected void drawItemPass0(Graphics2D g2,
Rectangle2D dataArea,
PlotRenderingInfo info,
XYPlot plot,
ValueAxis domainAxis,
ValueAxis rangeAxis,
XYDataset dataset,
int series,
int item,
CrosshairState crosshairState) {
EntityCollection entities = null;
Shape positive = null;
Shape negative = null;
if (info != null) {
entities = info.getOwner().getEntityCollection();
}
if (series == 0) {
PlotOrientation orientation = plot.getOrientation();
RectangleEdge domainAxisLocation = plot.getDomainAxisEdge();
RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge();
double y0 = dataset.getYValue(0, item);
double x1 = dataset.getXValue(0, item);
double y1 = rangeAxis.getLowerBound();
try {
if (dataset.getSeriesCount() > 1) {
x1 = dataset.getXValue(1, item);
y1 = dataset.getYValue(1, item);
}
} catch( Exception e ) {
// If the series' have a different number of elements, we come here.
// Allow the circumstance by not doing anything rash.
return;
}
if( Double.isNaN(y0)) return;
if( Double.isNaN(x1)) return;
if( Double.isNaN(y1)) return;
double transY0 = rangeAxis.valueToJava2D(y0, dataArea,
rangeAxisLocation);
double transX1 = domainAxis.valueToJava2D(x1, dataArea,
domainAxisLocation);
double trans0 = rangeAxis.valueToJava2D(0, dataArea,
rangeAxisLocation);
if (this.roundXCoordinates) {
transX1 = Math.rint(transX1);
}
double transY1 = rangeAxis.valueToJava2D(y1, dataArea,
rangeAxisLocation);
if (item > 0) {
double prevx0 = dataset.getXValue(0, item - 1);
double prevy0 = dataset.getYValue(0, item - 1);
double prevy1 = rangeAxis.getLowerBound();
if (dataset.getSeriesCount() > 1)
prevy1 = dataset.getYValue(1, item - 1);
if( Double.isNaN(prevx0)) return;
if( Double.isNaN(prevy0)) return;
if( Double.isNaN(prevy1)) return;
double prevtransX0 = domainAxis.valueToJava2D(prevx0, dataArea,
domainAxisLocation);
if (this.roundXCoordinates) {
prevtransX0 = Math.rint(prevtransX0);
}
double prevtransY0 = rangeAxis.valueToJava2D(prevy0, dataArea,
rangeAxisLocation);
double prevtransY1 = rangeAxis.valueToJava2D(prevy1, dataArea,
rangeAxisLocation);
positive = getPositiveArea((float) prevtransX0,
(float) prevtransY0, (float) prevtransY1,
(float) transX1, (float) transY0, (float) transY1,
orientation);
if (positive != null) {
Paint positivePaint = getPositivePaint();
if (getGradientPaintTransformer()
!= null && positivePaint instanceof GradientPaint) {
GradientPaint gp = (GradientPaint) positivePaint;
positivePaint = getGradientPaintTransformer().transform(gp, positive);
}
g2.setPaint(positivePaint);
g2.fill(positive);
if(fillBelow > 0) {
negative = getNegativeArea(
(float) prevtransX0, (float) trans0, (float) prevtransY1,
(float) transX1, (float) trans0, (float) transY1,
orientation);
if (negative != null) {
Paint negativePaint = getNegativePaint();
/* Fill in space w/ positive paint */
/*
if (fillBelow > 1 ) {
negativePaint = getPositivePaint();
}
*/
if (getGradientPaintTransformer()
!= null && negativePaint instanceof GradientPaint) {
GradientPaint gp = (GradientPaint) negativePaint;
negativePaint = getGradientPaintTransformer().transform(gp, negative);
}
g2.setPaint(negativePaint);
g2.fill(negative);
}
}
} else {
negative = getNegativeArea((float) prevtransX0,
(float) prevtransY0, (float) prevtransY1,
(float) transX1, (float) transY0, (float) transY1,
orientation);
if (negative != null) {
Paint negativePaint = getNegativePaint();
if (getGradientPaintTransformer()
!= null && negativePaint instanceof GradientPaint) {
GradientPaint gp = (GradientPaint) negativePaint;
negativePaint = getGradientPaintTransformer().transform(gp, negative);
}
g2.setPaint(negativePaint);
g2.fill(negative);
if(fillBelow > 0) {
positive = getPositiveArea(
(float) prevtransX0, (float) prevtransY0, (float) trans0,
(float) transX1, (float) transY0, (float) trans0,
orientation);
if (positive != null) {
Paint positivePaint = getPositivePaint();
if(fillBelow > 1)
positivePaint = getNegativePaint();
if (getGradientPaintTransformer()
!= null && positivePaint instanceof GradientPaint) {
GradientPaint gp = (GradientPaint) positivePaint;
positivePaint = getGradientPaintTransformer().transform(gp, positive);
}
g2.setPaint(positivePaint);
g2.fill(positive);
}
}
}
}
if (entities != null) {
if (positive != null) {
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(positive, dataset,
series, item, tip, url);
entities.add(entity);
}
if (negative != null && dataset.getSeriesCount() > 1 ) {
String tip=null;
XYToolTipGenerator generator = getToolTipGenerator(series+1,
item);
if (generator != null) {
tip = generator.generateToolTip(dataset, series+1, item);
}
String url = null;
if (getURLGenerator() != null) {
url = getURLGenerator().generateURL(dataset, series+1,
item);
}
XYItemEntity entity = new XYItemEntity(negative, dataset,
series+1, item, tip, url);
entities.add(entity);
}
}
}
}
}
/**
* Returns the positive area for a crossover point.
*
* @param x0 x coordinate.
* @param y0A y coordinate A.
* @param y0B y coordinate B.
* @param x1 x coordinate.
* @param y1A y coordinate A.
* @param y1B y coordinate B.
* @param orientation the plot orientation.
*
* @return The positive area.
*/
protected Shape getPositiveArea(float x0, float y0A, float y0B,
float x1, float y1A, float y1B,
PlotOrientation orientation) {
Shape result = null;
boolean startsNegative = (y0A >= y0B);
boolean endsNegative = (y1A >= y1B);
if (orientation == PlotOrientation.HORIZONTAL) {
startsNegative = (y0B >= y0A);
endsNegative = (y1B >= y1A);
}
if (startsNegative) { // starts negative
if (endsNegative) {
// all negative - return null
result = null;
}
else {
// changed from negative to positive
float[] p = getIntersection(x0, y0A, x1, y1A, x0, y0B, x1, y1B);
GeneralPath area = new GeneralPath();
if (orientation == PlotOrientation.HORIZONTAL) {
area.moveTo(y1A, x1);
area.lineTo(p[1], p[0]);
area.lineTo(y1B, x1);
area.closePath();
}
else if (orientation == PlotOrientation.VERTICAL) {
area.moveTo(x1, y1A);
area.lineTo(p[0], p[1]);
area.lineTo(x1, y1B);
area.closePath();
}
result = area;
}
}
else { // starts positive
if (endsNegative) {
// changed from positive to negative
float[] p = getIntersection(x0, y0A, x1, y1A, x0, y0B, x1, y1B);
GeneralPath area = new GeneralPath();
if (orientation == PlotOrientation.HORIZONTAL) {
area.moveTo(y0A, x0);
area.lineTo(p[1], p[0]);
area.lineTo(y0B, x0);
area.closePath();
}
else if (orientation == PlotOrientation.VERTICAL) {
area.moveTo(x0, y0A);
area.lineTo(p[0], p[1]);
area.lineTo(x0, y0B);
area.closePath();
}
result = area;
}
else {
GeneralPath area = new GeneralPath();
if (orientation == PlotOrientation.HORIZONTAL) {
area.moveTo(y0A, x0);
area.lineTo(y1A, x1);
area.lineTo(y1B, x1);
area.lineTo(y0B, x0);
area.closePath();
}
else if (orientation == PlotOrientation.VERTICAL) {
area.moveTo(x0, y0A);
area.lineTo(x1, y1A);
area.lineTo(x1, y1B);
area.lineTo(x0, y0B);
area.closePath();
}
result = area;
}
}
return result;
}
/**
* Returns the negative area for a cross-over section.
*
* @param x0 x coordinate.
* @param y0A y coordinate A.
* @param y0B y coordinate B.
* @param x1 x coordinate.
* @param y1A y coordinate A.
* @param y1B y coordinate B.
* @param orientation the plot orientation.
*
* @return The negative area.
*/
protected Shape getNegativeArea(float x0, float y0A, float y0B,
float x1, float y1A, float y1B,
PlotOrientation orientation) {
Shape result = null;
boolean startsNegative = (y0A >= y0B);
boolean endsNegative = (y1A >= y1B);
if (orientation == PlotOrientation.HORIZONTAL) {
startsNegative = (y0B >= y0A);
endsNegative = (y1B >= y1A);
}
if (startsNegative) { // starts negative
if (endsNegative) { // all negative
GeneralPath area = new GeneralPath();
if (orientation == PlotOrientation.HORIZONTAL) {
area.moveTo(y0A, x0);
area.lineTo(y1A, x1);
area.lineTo(y1B, x1);
area.lineTo(y0B, x0);
area.closePath();
}
else if (orientation == PlotOrientation.VERTICAL) {
area.moveTo(x0, y0A);
area.lineTo(x1, y1A);
area.lineTo(x1, y1B);
area.lineTo(x0, y0B);
area.closePath();
}
result = area;
}
else { // changed from negative to positive
float[] p = getIntersection(x0, y0A, x1, y1A, x0, y0B, x1, y1B);
GeneralPath area = new GeneralPath();
if (orientation == PlotOrientation.HORIZONTAL) {
area.moveTo(y0A, x0);
area.lineTo(p[1], p[0]);
area.lineTo(y0B, x0);
area.closePath();
}
else if (orientation == PlotOrientation.VERTICAL) {
area.moveTo(x0, y0A);
area.lineTo(p[0], p[1]);
area.lineTo(x0, y0B);
area.closePath();
}
result = area;
}
}
else {
if (endsNegative) {
// changed from positive to negative
float[] p = getIntersection(x0, y0A, x1, y1A, x0, y0B, x1, y1B);
GeneralPath area = new GeneralPath();
if (orientation == PlotOrientation.HORIZONTAL) {
area.moveTo(y1A, x1);
area.lineTo(p[1], p[0]);
area.lineTo(y1B, x1);
area.closePath();
}
else if (orientation == PlotOrientation.VERTICAL) {
area.moveTo(x1, y1A);
area.lineTo(p[0], p[1]);
area.lineTo(x1, y1B);
area.closePath();
}
result = area;
}
else {
// all negative - return null
}
}
return result;
}
/**
* Returns the intersection point of two lines.
*
* @param x1 x1
* @param y1 y1
* @param x2 x2
* @param y2 y2
* @param x3 x3
* @param y3 y3
* @param x4 x4
* @param y4 y4
*
* @return The intersection point.
*/
private float[] getIntersection(float x1, float y1, float x2, float y2,
float x3, float y3, float x4, float y4) {
float n = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
float d = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
float u = n / d;
float[] result = new float[2];
result[0] = x1 + u * (x2 - x1);
result[1] = y1 + u * (y2 - y1);
return result;
}
}