How to animate a chart

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
Locked
Andrew Foss
Posts: 36
Joined: Thu Apr 12, 2007 6:54 am

How to animate a chart

Post by Andrew Foss » Wed May 07, 2008 7:54 pm

JFree folks,

the topic of animating charts has come up on the list a few times in the past and I've tried a few approaches, but so far the best is to change the gradient paint, since the gradient helps smooth things regardless of how fast or consistently the repaints are happening.

Following is VariableGradientPaintTransformer, which is a modification of DefaultGradientPaintTransformer

The chart has a progress listener something like this;

Code: Select all

		addProgressListener(new ChartProgressListener() {
			public void chartProgress(final ChartProgressEvent event) {
				if (event.getType() == ChartProgressEvent.DRAWING_FINISHED  ) {
					//System.out.println("Event working");
					if ( animate ) {
						fireChartChanged();
						((VariableGradientPaintTransformer)((XYDifferenceRendererFill)event.getChart().getXYPlot().getRenderer()).getGradientPaintTransformer()).increment(0.05f);
					}
				}
			} });
The VariableGradientPaintTransformer looks like this;

Code: Select all


package com.actsolar.ui.util;

import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Shape;
import java.awt.geom.Rectangle2D;
import java.io.Serializable;

import org.jfree.ui.GradientPaintTransformType;
import org.jfree.ui.GradientPaintTransformer;
import org.jfree.ui.StandardGradientPaintTransformer;
import org.jfree.util.PublicCloneable;

/**
 * Transforms a <code>GradientPaint</code> to range over the width of a target 
 * shape with the ability for the caller to move the gradient points and animate the target
 * @author David Gilbert
 */
public class VariableGradientPaintTransformer 
implements GradientPaintTransformer, Cloneable, PublicCloneable, 
Serializable {

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

	/** The transform type. */
	private GradientPaintTransformType type;

	private Number transformPercent = 0;
	private float autoIncrement = 0;

	/**
	 * Creates a new transformer.
	 */
	public VariableGradientPaintTransformer() {
		this(GradientPaintTransformType.VERTICAL);
	}

	/**
	 * Creates a new transformer.
	 * 
	 * @param type  the transform type.
	 */
	public VariableGradientPaintTransformer(
			final GradientPaintTransformType type) {
		this.type = type;
	}

	/**
	 * Creates a new transformer.
	 * 
	 * @param type  the transform type.
	 * @param float the starting percentage offset of the gradient
	 * @param autoincrement  the amount to increment the gradient
	 */
	public VariableGradientPaintTransformer(
			final GradientPaintTransformType type, float percent, float autoincrement) {
		this.type = type;
		this.transformPercent = percent;
		this.autoIncrement = autoincrement;
	}

	/**
	 * Variably Transforms a <code>GradientPaint</code> instance.
	 * 
	 * @param paint  the original paint.
	 * @param target  the target shape.
	 * 
	 * @return The transformed paint.
	 */
	public GradientPaint transform(final GradientPaint paint, 
			final Shape target) {

		final Rectangle2D bounds = target.getBounds2D();
		float X1 = (float) bounds.getCenterX();
		float Y1 = (float) bounds.getCenterY();
		float X2 = (float) bounds.getCenterX();
		float Y2 = (float) bounds.getCenterY();

		//For the painting of very narrow targets the legend lines use the standard transformer
		if ( bounds.getHeight() < 10) return new StandardGradientPaintTransformer().transform(paint, target);

		Color color1=paint.getColor1();
		Color color2=paint.getColor2();

		/*
		 * Every cycle through switch the colors, so the animation stays smooth
		 */
		if ( transformPercent.intValue()%2 == 0 ) {
			color1 = paint.getColor2();
			color2 = paint.getColor1();
		}

		transformPercent = transformPercent.floatValue() + autoIncrement;

		/* Does a good up/down
		result = new GradientPaint(
				(float) bounds.getCenterX(), (float) (bounds.getMinY() + Math.sin(Math.toRadians(degrees))*bounds.getHeight()),
				color1, (float) bounds.getCenterX(), 
				(float) (bounds.getMaxY()+Math.sin(Math.toRadians(degrees))*bounds.getHeight()), color2    
		);
		// System.out.println("pointX "+(bounds.getMinY()+Math.sin(Math.toRadians(degrees))*bounds.getMinY())+" "+Math.sin(Math.toRadians(degrees))+" "+degrees);
		degrees++;
		 */

        if (this.type.equals(GradientPaintTransformType.HORIZONTAL) || this.type.equals(GradientPaintTransformType.CENTER_HORIZONTAL) )
        {
			X1 = (float)(bounds.getCenterX() - transformPercent.floatValue()%1*bounds.getWidth()/2);
			X2 = (float)(bounds.getMaxX()- transformPercent.floatValue()%1*bounds.getWidth()/2);
        }
        else if (this.type.equals(GradientPaintTransformType.VERTICAL) || this.type.equals(GradientPaintTransformType.CENTER_VERTICAL) ) {
			Y1 = (float)(bounds.getMinY() - transformPercent.floatValue()%1*bounds.getHeight()/2);
			Y2 = (float) (bounds.getCenterY() - transformPercent.floatValue()%1*bounds.getHeight()/2);
        }
 
	 	return new GradientPaint(
				X1 , Y1, color1, 
				X2,  Y2 , color2, 
				true );
	}

	/**
	 * 0 for no autoincrement
	 * non-zero for the amount to increment the height or width of gradient each call
	 * 
	 * @param float autoincrement
	 * 
	 */
	public void setAutoIncrement(float inc) {
		this.autoIncrement = inc;
	}
	
	/**
	 * Sets the starting gradient offset percentage
	 * 
	 * @param float percent
	 * 
	 */
	public void setPercent(float perc) {
		this.transformPercent = perc;
	}
	
	/**
	 * Increments the height or width of the gradient point
	 * 
	 * @param float increment
	 * 
	 */
	public void increment(float inc) {
		transformPercent = transformPercent.floatValue() + inc;
	}

	/**
	 * Tests this instance for equality with an arbitrary object.
	 * 
	 * @param obj  the object to test against (<code>null</code> permitted).
	 * 
	 * @return A boolean.
	 */
	public boolean equals(final Object obj) {
		if (obj == this) {
			return true;   
		}
		if (!(obj instanceof VariableGradientPaintTransformer)) {
			return false;
		}
		final VariableGradientPaintTransformer that 
		= (VariableGradientPaintTransformer) obj;
		if (this.type != that.type) {
			return false;
		}
		return true;
	}

	/**
	 * Returns a clone of the transformer.
	 * 
	 * @return A clone.
	 * 
	 * @throws CloneNotSupportedException not thrown by this class, but 
	 *         subclasses (if any) might.
	 */
	public Object clone() throws CloneNotSupportedException {
		return super.clone();
	}

	/**
	 * Returns a hash code for this object.
	 * 
	 * @return A hash code.
	 */
	public int hashCode() {
		return (this.type != null ? this.type.hashCode() : 0);
	}

}

ralph961
Posts: 3
Joined: Wed May 21, 2008 2:57 pm

Post by ralph961 » Wed May 21, 2008 3:00 pm

Hi,
I am very interested in this. Can you please explain where you plug in the new GradientPaintTransformer.
When I tried this I couldn't find the XYDifferenceRendererFill class and "XYDifferenceRenderer" did not have a method called getGradientPaintTransformer.

Am I missing something?

Andrew Foss
Posts: 36
Joined: Thu Apr 12, 2007 6:54 am

Post by Andrew Foss » Wed May 21, 2008 9:02 pm

Ralph,

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;

    }
    
}

[/img]

RichardWest
Posts: 844
Joined: Fri Oct 13, 2006 9:29 pm
Location: Sunnyvale, CA

Post by RichardWest » Wed May 21, 2008 10:27 pm

Andrew Foss wrote: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.
One large word of warning: the XYDifferenceRenderer underwent a massive overhall starting in v1.0.6 which completely changed how the draw method behaves. The code you modified is not easily merged with the JFreeChart trunk in its current form. Could you take a look at the latest XYDifferenceRenderer code to see if you could adapt your changes?
Richard West
Design Engineer II
Advanced Micro Devices
Sunnyvale, CA

ralph961
Posts: 3
Joined: Wed May 21, 2008 2:57 pm

Post by ralph961 » Thu May 22, 2008 12:33 pm

Andrew,

I have tried your code, it is not working for me. Do you have an example you can give me?

Thanks

lucky7456969
Posts: 15
Joined: Thu Nov 01, 2012 6:41 am
antibot: No, of course not.

Re:

Post by lucky7456969 » Wed Aug 14, 2013 5:22 am

BTW, I reverted to 1.0.5 to no avail

Exception in thread "AWT-EventQueue-0" java.lang.ClassCastException: org.jfree.chart.renderer.category.BarRenderer cannot be cast to jewellery.Charting.XYDifferenceRendererFill

Locked