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

org.eclipse.birt.chart.extension.render.Area Maven / Gradle / Ivy

There is a newer version: 4.6.0-20160607
Show newest version
/*******************************************************************************
 * Copyright (c) 2004 Actuate Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *  Actuate Corporation  - initial API and implementation
 *******************************************************************************/

package org.eclipse.birt.chart.extension.render;

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

import org.eclipse.birt.chart.computation.DataPointHints;
import org.eclipse.birt.chart.computation.GObjectFactory;
import org.eclipse.birt.chart.computation.IGObjectFactory;
import org.eclipse.birt.chart.computation.Methods;
import org.eclipse.birt.chart.computation.Point;
import org.eclipse.birt.chart.computation.withaxes.SeriesRenderingHints;
import org.eclipse.birt.chart.computation.withaxes.SeriesRenderingHints3D;
import org.eclipse.birt.chart.device.IPrimitiveRenderer;
import org.eclipse.birt.chart.event.EventObjectCache;
import org.eclipse.birt.chart.event.Line3DRenderEvent;
import org.eclipse.birt.chart.event.LineRenderEvent;
import org.eclipse.birt.chart.event.Polygon3DRenderEvent;
import org.eclipse.birt.chart.event.PolygonRenderEvent;
import org.eclipse.birt.chart.event.PrimitiveRenderEvent;
import org.eclipse.birt.chart.event.RectangleRenderEvent;
import org.eclipse.birt.chart.event.StructureSource;
import org.eclipse.birt.chart.event.WrappedInstruction;
import org.eclipse.birt.chart.exception.ChartException;
import org.eclipse.birt.chart.factory.RunTimeContext;
import org.eclipse.birt.chart.model.ChartWithAxes;
import org.eclipse.birt.chart.model.attribute.Bounds;
import org.eclipse.birt.chart.model.attribute.ChartDimension;
import org.eclipse.birt.chart.model.attribute.ColorDefinition;
import org.eclipse.birt.chart.model.attribute.Fill;
import org.eclipse.birt.chart.model.attribute.LineAttributes;
import org.eclipse.birt.chart.model.attribute.Location;
import org.eclipse.birt.chart.model.attribute.Location3D;
import org.eclipse.birt.chart.model.component.Axis;
import org.eclipse.birt.chart.model.component.Series;
import org.eclipse.birt.chart.model.data.SeriesDefinition;
import org.eclipse.birt.chart.model.layout.ClientArea;
import org.eclipse.birt.chart.model.layout.Legend;
import org.eclipse.birt.chart.model.layout.Plot;
import org.eclipse.birt.chart.model.type.AreaSeries;
import org.eclipse.birt.chart.model.type.LineSeries;
import org.eclipse.birt.chart.render.AxesRenderHelper;
import org.eclipse.birt.chart.render.CurveRenderer;
import org.eclipse.birt.chart.render.ISeriesRenderingHints;
import org.eclipse.birt.chart.util.ChartUtil;
import org.eclipse.birt.chart.util.FillUtil;
import org.eclipse.emf.common.util.EList;

/**
 * Area
 */
public class Area extends Line
{

	private final static String AREA_ENVELOPS = "Area.Envelops"; //$NON-NLS-1$

	/**
	 * The constructor.
	 */
	public Area( )
	{
		super( );
	}

	private Fill getSeriesPaletteEntry( )
	{
		Fill fPaletteEntry = null;
		SeriesDefinition sd = null;

		Series se = getSeries( );

		if ( se.eContainer( ) instanceof SeriesDefinition )
		{
			sd = (SeriesDefinition) se.eContainer( );
		}

		if ( sd != null )
		{
			int iThisSeriesIndex = sd.getRunTimeSeries( ).indexOf( se );
			if ( iThisSeriesIndex >= 0 )
			{
				EList ePalette = sd.getSeriesPalette( ).getEntries( );
				fPaletteEntry = FillUtil.getPaletteFill( ePalette,
						iThisSeriesIndex );
				updateTranslucency( fPaletteEntry, se );
			}
		}

		return fPaletteEntry;
	}

	@Override
	protected boolean validateShowAsTape( )
	{
		ChartWithAxes cwa = (ChartWithAxes) getModel( );
		AreaSeries as = (AreaSeries) getSeries( );

		if ( !as.isStacked( ) ) // NOT STACKED
		{
			if ( getSeriesCount( ) > 2 && !isDimension3D( ) )
			{
				return false;
			}
		}
		else
		{
			final Axis[] axaOrthogonal = cwa.getOrthogonalAxes( cwa.getBaseAxes( )[0],
					true );
			if ( axaOrthogonal.length > 1 )
			{
				// If it is study layout, it should just check if it has another
				// series type in the same axis.
				if ( ChartUtil.isStudyLayout( cwa ) )
				{
					Axis axis = ChartUtil.getAxisFromSeries( as );
					if ( axis != null )
					{
						for ( Iterator itr = axis.getSeriesDefinitions( )
								.iterator( ); itr.hasNext( ); )
						{
							SeriesDefinition sd = itr.next( );
							for ( Iterator sitr = sd.getRunTimeSeries( )
									.iterator( ); sitr.hasNext( ); )
							{
								Series se = sitr.next( );

								if ( !( se instanceof AreaSeries )
										|| !se.isStacked( ) )
								{
									return false;
								}
							}
						}

						return true;
					}
				}

				return false;
			}
			if ( getSeriesCount( ) > 2 && !isDimension3D( ) )
			{
				for ( Iterator itr = axaOrthogonal[0].getSeriesDefinitions( )
						.iterator( ); itr.hasNext( ); )
				{
					SeriesDefinition sd = itr.next( );
					for ( Iterator sitr = sd.getRunTimeSeries( )
							.iterator( ); sitr.hasNext( ); )
					{
						Series se = sitr.next( );

						if ( !( se instanceof AreaSeries ) || !se.isStacked( ) )
						{
							return false;
						}
					}
				}
			}
		}

		return true;
	}

	@Override
	protected void renderAsCurve( IPrimitiveRenderer ipr, LineAttributes lia,
			ISeriesRenderingHints isrh, Location[] loa, boolean bShowAsTape,
			double tapeWidth, Fill paletteEntry, boolean usePaletteLineColor )
			throws ChartException
	{
		Fill seriesPalette = getSeriesPaletteEntry( );

		double zeroLocation = 0;
		if ( isDimension3D( ) )
		{
			zeroLocation = ( (SeriesRenderingHints3D) isrh ).getPlotBaseLocation( );
		}
		else
		{
			SeriesRenderingHints srh = (SeriesRenderingHints) isrh;
			zeroLocation = srh.getZeroLocation( );

			final Bounds boClientArea = srh.getClientAreaBounds( true );
			final double dSeriesThickness = srh.getSeriesThickness( );
			if ( ( (ChartWithAxes) getModel( ) ).getDimension( ) == ChartDimension.TWO_DIMENSIONAL_WITH_DEPTH_LITERAL )
			{
				boClientArea.delta( -dSeriesThickness, dSeriesThickness, 0, 0 );
			}

			if ( ( (ChartWithAxes) getModel( ) ).isTransposed( ) )
			{
				if ( zeroLocation < boClientArea.getLeft( ) )
				{
					zeroLocation = boClientArea.getLeft( );
				}

				if ( zeroLocation > boClientArea.getLeft( )
						+ boClientArea.getWidth( ) )
				{
					zeroLocation = boClientArea.getLeft( )
							+ boClientArea.getWidth( );
				}
			}
			else
			{
				if ( zeroLocation < boClientArea.getTop( ) )
				{
					zeroLocation = boClientArea.getTop( );
				}

				if ( zeroLocation > boClientArea.getTop( )
						+ boClientArea.getHeight( ) )
				{
					zeroLocation = boClientArea.getTop( )
							+ boClientArea.getHeight( );
				}
			}
		}

		boolean bStacked = getSeries( ).isStacked( );
		AreaSeries as = (AreaSeries) getSeries( );
		if ( !bStacked && as.isConnectMissingValue( ) )
		{
			DataPointsSeeker seeker = DataPointsSeeker.create( isrh.getDataPoints( ),
					(AreaSeries) getSeries( ),
					bStacked );
			List lst = new ArrayList( );

			// #44030 Keep the invalid point before the first valid point if it is not the first of loa list.
			// Set invalid point value to the minimum value of the Y-axis.
			if ( seeker.next( ) )
			{
				if ( seeker.getIndex( ) > 0 )
				{
					lst.add( loa[seeker.getIndex( ) - 1] );
				}
				lst.add( loa[seeker.getIndex( )] );
			}

			while ( seeker.next( ) )
			{
				lst.add( loa[seeker.getIndex( )] );
			}
			// Keep the invalid point after the last valid point if it is not the last of the loa list.
			// Set invalid point value to the minimum value of the Y-axis.
			if ( seeker.getIndex( ) < seeker.size( ) - 1 )
			{
				lst.add( loa[seeker.getIndex( ) + 1] );
			}

			loa = isDimension3D( ) ? new Location3D[lst.size( )]
					: new Location[lst.size( )];
			loa = lst.toArray( loa );
		}

		final CurveRenderer cr = new CurveRenderer( ( (ChartWithAxes) getModel( ) ),
				this,
				lia,
				loa,
				zeroLocation,
				bShowAsTape,
				tapeWidth,
				true,
				getSeries( ).isTranslucent( ),
				getSeries( ).isStacked( ) || getAxis( ).isPercent( ),
				true,
				true,
				seriesPalette != null ? seriesPalette : paletteEntry,
				usePaletteLineColor,
				true );
		cr.draw( ipr );
	}

	@Override
	protected void renderDataPoints( IPrimitiveRenderer ipr, Plot p,
			ISeriesRenderingHints isrh, DataPointHints[] dpha,
			LineAttributes lia, Location[] loa, boolean bShowAsTape,
			double dTapeWidth, Fill paletteEntry, boolean usePaletteLineColor )
			throws ChartException
	{
		Fill seriesPalette = getSeriesPaletteEntry( );
		if ( seriesPalette != null )
		{
			paletteEntry = seriesPalette;
		}

		AreaDataPointsRenderer dpRender = AreaDataPointsRenderer.create( this,
				ipr,
				isrh,
				dpha,
				loa,
				bShowAsTape,
				dTapeWidth,
				paletteEntry );

		dpRender.render( );
	}

	protected void renderShadow( IPrimitiveRenderer ipr, Plot p,
			LineAttributes lia, Location[] loa, boolean bShowAsTape )
	{
		// AREA DONT RENDER A SHADOW
	}

	@Override
	public void renderLegendGraphic( IPrimitiveRenderer ipr, Legend lg,
			Fill fPaletteEntry, Bounds bo ) throws ChartException
	{
		if ( ( bo.getWidth( ) == 0 ) && ( bo.getHeight( ) == 0 ) )
		{
			return;
		}
		final ClientArea ca = lg.getClientArea( );
		final LineAttributes lia = ca.getOutline( );
		final LineSeries ls = (LineSeries) getSeries( );
		if ( fPaletteEntry == null ) // TEMPORARY PATCH: WILL BE REMOVED SOON
		{
			fPaletteEntry = goFactory.RED( );
		}

		final RectangleRenderEvent rre = ( (EventObjectCache) ipr ).getEventObject( StructureSource.createLegend( lg ),
				RectangleRenderEvent.class );
		rre.setBackground( ca.getBackground( ) );
		rre.setOutline( lia );
		rre.setBounds( bo );
		ipr.fillRectangle( rre );

		final PolygonRenderEvent pre = ( (EventObjectCache) ipr ).getEventObject( StructureSource.createLegend( lg ),
				PolygonRenderEvent.class );

		Location[] loa = new Location[5];
		loa[0] = goFactory.createLocation( bo.getLeft( ) + 1 * getDeviceScale( ),
				bo.getTop( ) + bo.getHeight( ) - 2 * getDeviceScale( ) );
		loa[1] = goFactory.createLocation( bo.getLeft( )
				+ bo.getWidth( )
				- 1
				* getDeviceScale( ), bo.getTop( )
				+ bo.getHeight( )
				- 2
				* getDeviceScale( ) );
		loa[2] = goFactory.createLocation( bo.getLeft( )
				+ bo.getWidth( )
				* 5
				/ 6, bo.getTop( ) + bo.getHeight( ) / 3 );
		loa[3] = goFactory.createLocation( bo.getLeft( )
				+ bo.getWidth( )
				* 2
				/ 3, bo.getTop( ) + bo.getHeight( ) / 2 );
		loa[4] = goFactory.createLocation( bo.getLeft( ) + bo.getWidth( ) / 2,
				bo.getTop( ) + 1 * getDeviceScale( ) );

		pre.setBackground( fPaletteEntry );
		pre.setPoints( loa );
		ipr.fillPolygon( pre );

		// render outline
		LineAttributes liaMarker = ls.getLineAttributes( );
		if ( liaMarker.isVisible( ) )
		{
			if ( ls.isPaletteLineColor( ) )
			{
				liaMarker = goFactory.copyOf( liaMarker );
				liaMarker.setColor( FillUtil.getColor( fPaletteEntry ) );
			}

			pre.setOutline( liaMarker );
			ipr.drawPolygon( pre );
		}

	}

	@Override
	protected Location[] filterNull( Location[] ll )
	{
		// Fix null values to use base line instead
		final Bounds boClientArea = getPlotBounds( );
		List al = new ArrayList( );
		for ( int i = 0; i < ll.length; i++ )
		{
			if ( Double.isNaN( ll[i].getX( ) ) )
			{
				ll[i].setX( boClientArea.getLeft( ) );
			}
			if ( Double.isNaN( ll[i].getY( ) ) )
			{
				ll[i].setY( boClientArea.getTop( ) + boClientArea.getHeight( ) );
			}

			al.add( ll[i] );
		}

		if ( ll instanceof Location3D[] )
		{
			return al.toArray( new Location3D[al.size( )] );
		}
		return al.toArray( new Location[al.size( )] );
	}

	/**
	 * DataPointsRenderer is used to render the data points of an area series.
	 */
	private abstract static class AreaDataPointsRenderer extends
			DataPointsRenderer
	{

		protected final Fill fillColor;
		protected final double zeroLocation;

		AreaDataPointsRenderer( Context context ) throws ChartException
		{
			super( context );
			dc.setPlaneShadowsComparator( WrappedInstruction.getDefaultComarator( ) );
			dc.setPlanesComparator( WrappedInstruction.getDefaultComarator( ) );
			this.zeroLocation = initZeroLocation( context.line, context.isrh );

			Fill fillColor = context.paletteEntry;

			if ( ls.isTranslucent( ) )
			{
				if ( fillColor instanceof ColorDefinition )
				{
					fillColor = goFactory.translucent( (ColorDefinition) fillColor );
				}
			}
			this.fillColor = fillColor;
		}

		/**
		 * Computes the zero location.
		 */
		protected abstract double initZeroLocation( Line line,
				ISeriesRenderingHints isrh ) throws ChartException;

		public static AreaDataPointsRenderer create( Area area,
				IPrimitiveRenderer ipr, ISeriesRenderingHints isrh,
				DataPointHints[] dpha, Location[] loa, boolean bShowAsTape,
				double dTapeWidth, Fill paletteEntry ) throws ChartException
		{
			AreaDataPointsRenderer.Context context = new AreaDataPointsRenderer.Context( area,
					ipr,
					isrh,
					dpha,
					paletteEntry );

			if ( area.isDimension3D( ) )
			{
				return new AreaDataPointsRenderer3D( context, loa, dTapeWidth );
			}
			else if ( bShowAsTape )
			{
				return new AreaDataPointsRenderer2Dplus( context, loa );
			}
			else if ( context.bStacked )
			{
				return new AreaDataPointsRenderer2DStacked( context, loa );
			}
			else
			{
				return new AreaDataPointsRenderer2D( context, loa );
			}

		}
	}

	/**
	 * DataPointsRenderer implementation for 2D not-stacked case.
	 */
	private static class AreaDataPointsRenderer2D extends AreaDataPointsRenderer
	{

		protected final SeriesRenderingHints srh;
		protected final Location[] loa;
		protected final Transposition trans;
		protected final LineRenderEvent lre;
		protected final PolygonRenderEvent pre;
		protected final List lstPolygon = new ArrayList( );

		AreaDataPointsRenderer2D( Context context, Location[] loa )
				throws ChartException
		{
			super( context );

			this.srh = (SeriesRenderingHints) context.isrh;
			this.loa = loa;
			this.trans = ( (ChartWithAxes) context.line.getModel( ) ).isTransposed( ) ? Transposition.TRANSPOSED
					: Transposition.NOT_TRANSPOSED;

			StructureSource sourceObj = createSeriesSource( );
			this.pre = ( (EventObjectCache) context.ipr ).getEventObject( sourceObj,
					PolygonRenderEvent.class );
			this.lre = ( (EventObjectCache) context.ipr ).getEventObject( sourceObj,
					LineRenderEvent.class );
			this.lre.setLineAttributes( lia );
		}

		@Override
		protected double initZeroLocation( Line line,
				ISeriesRenderingHints isrh )
				throws ChartException
		{
			SeriesRenderingHints srh = (SeriesRenderingHints) isrh;
			double zeroLocation = srh.getZeroLocation( );

			// Adjusts zero location for study layout case.
			if ( ChartUtil.isStudyLayout( cwa ) )
			{
				zeroLocation = srh.getLocationOnOrthogonal( srh.getOrthogonalScale( )
						.getMinimum( ) );
			}

			final Bounds boClientArea = srh.getClientAreaBounds( true );
			final double dSeriesThickness = srh.getSeriesThickness( );
			if ( cwa.getDimension( ) == ChartDimension.TWO_DIMENSIONAL_WITH_DEPTH_LITERAL )
			{
				boClientArea.delta( -dSeriesThickness, dSeriesThickness, 0, 0 );
			}

			if ( cwa.isTransposed( ) )
			{
				if ( zeroLocation < boClientArea.getLeft( ) )
				{
					zeroLocation = boClientArea.getLeft( );
				}

				if ( zeroLocation > boClientArea.getLeft( )
						+ boClientArea.getWidth( ) )
				{
					zeroLocation = boClientArea.getLeft( )
							+ boClientArea.getWidth( );
				}
			}
			else
			{
				if ( zeroLocation < boClientArea.getTop( ) )
				{
					zeroLocation = boClientArea.getTop( );
				}

				if ( zeroLocation > boClientArea.getTop( )
						+ boClientArea.getHeight( ) )
				{
					zeroLocation = boClientArea.getTop( )
							+ boClientArea.getHeight( );
				}
			}

			return zeroLocation;
		}

		protected void drawFrontLine( int pindex, int index )
		{
			lre.setStart( loa[pindex] );
			lre.setEnd( loa[index] );
			lre.setSourceObject( createDataPointSource( pindex ) );
			dc.addLine( lre );
		}

		@Override
		protected void afterLoop( DataPointsSeeker seeker )
		{
			int lindex = seeker.getIndex( );
			if ( lindex < seeker.size( ) - 1 )
			{
				trans.setY( loa[lindex + 1], zeroLocation );
				addPolygonPoint( loa[lindex + 1] );
				drawFrontLine( lindex, lindex + 1 );
			}
			else
			{
				Location pt1 = loa[lindex].copyInstance( );
				trans.setY( pt1, zeroLocation );
				addPolygonPoint( pt1 );
			}

			fillPolygon( );
		}

		@Override
		protected void beforeLoop( DataPointsSeeker seeker )
		{
			int findex = seeker.getIndex( );

			if ( findex > 0 )
			{
				trans.setY( loa[findex - 1], zeroLocation );
				drawFrontLine( findex - 1, findex );
				addPolygonPoint( loa[findex - 1] );
			}
			else
			{
				Location pt0 = loa[findex].copyInstance( );
				trans.setY( pt0, zeroLocation );
				addPolygonPoint( pt0 );
			}
			addPolygonPoint( loa[findex] );
		}

		@Override
		protected void processDataPoint( DataPointsSeeker seeker )
		{
			int index = seeker.getIndex( );
			int pindex = seeker.getPrevIndex( );

			if ( seeker.isNull( ) )
			{
				trans.setY( loa[index], zeroLocation );
			}

			addPolygonPoint( loa[index] );
			drawFrontLine( pindex, index );
		}

		protected void addPolygonPoint( Location lo )
		{
			lstPolygon.add( lo );
		}

		protected void fillPolygon( )
		{
			if ( lstPolygon.size( ) > 0 )
			{
				Location[] pa = lstPolygon.toArray( new Location[lstPolygon.size( )] );

				pre.setOutline( null );
				pre.setPoints( pa );
				pre.setBackground( fillColor );
				pre.setSourceObject( createSeriesSource( ) );
				dc.addPlane( pre, PrimitiveRenderEvent.FILL );
			}
		}
	}

	/**
	 * DataPointsRenderer implementation for 2D stacked case.
	 */
	private static class AreaDataPointsRenderer2DStacked extends
			AreaDataPointsRenderer2D
	{

		protected Location[] loaLast;

		AreaDataPointsRenderer2DStacked( Context context, Location[] loa )
				throws ChartException
		{
			super( context, loa );
		}

		/*
		 * Fixes a point with null value. It's assumed that the value of the
		 * given point is null.
		 */
		protected void fixPoint( int index )
		{
			if ( loaLast == null )
			{
				trans.setY( loa[index], zeroLocation );
			}
			else
			{
				trans.setY( loa[index], trans.getY( loaLast[index] ) );
			}
		}

		protected void loadLastStates( )
		{
			Object obj = context.line.getRunTimeContext( )
					.getState( STACKED_SERIES_LOCATION_KEY );

			if ( obj instanceof Location[] )
			{
				loaLast = (Location[]) obj;
			}
			else if ( obj instanceof List )
			{
				@SuppressWarnings("rawtypes")
				List lst = (List) obj;
				Location[] last = new Location[lst.size( )];
				for ( int i = 0; i < lst.size( ); i++ )
				{
					Object o = lst.get( i );
					if ( o instanceof double[] )
					{
						last[i] = goFactory.createLocation( ( (double[]) o )[0],
								( (double[]) o )[1] );
					}
				}
				loaLast = last;
			}
		}

		protected void saveStates( )
		{
			if ( context.line.isLastRuntimeSeriesInAxis( ) )
			{
				// clean stack state.
				context.line.getRunTimeContext( )
						.putState( STACKED_SERIES_LOCATION_KEY, null );
			}
			else
			{
				List list = new ArrayList( );
				for ( Location lo : loa )
				{
					double[] l = new double[]{
							lo.getX( ), lo.getY( )
					};
					list.add( l );
				}
				context.line.getRunTimeContext( )
						.putState( STACKED_SERIES_LOCATION_KEY, list );
			}
		}

		// protected double fixNaN( double baseValue, double currentValue,
		// int currentIndex, double[] lastValues )
		// {
		// // only fix NaN values
		// if ( !Double.isNaN( currentValue ) )
		// {
		// return currentValue;
		// }
		//
		// // use baseValue if this is the first series
		// if ( lastValues == null )
		// {
		// return baseValue;
		// }
		//
		// return lastValues[currentIndex];
		// }

		@Override
		protected void beforeLoop( DataPointsSeeker seeker )
		{
			loadLastStates( );
			int findex = seeker.getIndex( );
			if ( seeker.isNull( ) )
			{
				fixPoint( findex );
			}
			addPolygonPoint( loa[findex] );
		}

		@Override
		protected void processDataPoint( DataPointsSeeker seeker )
		{
			int index = seeker.getIndex( );
			if ( seeker.isNull( ) )
			{
				fixPoint( index );
			}
			addPolygonPoint( loa[index] );
			drawFrontLine( index - 1, index );
		}

		@Override
		protected void afterLoop( DataPointsSeeker seeker )
		{
			if ( loaLast != null )
			{
				for ( int i = loaLast.length - 1; i >= 0; i-- )
				{
					addPolygonPoint( loaLast[i] );
				}
			}
			else
			{
				Location pt = lstPolygon.get( lstPolygon.size( ) - 1 )
						.copyInstance( );
				trans.setY( pt, zeroLocation );
				addPolygonPoint( pt );
				pt = lstPolygon.get( 0 ).copyInstance( );
				trans.setY( pt, zeroLocation );
				addPolygonPoint( pt );
			}

			fillPolygon( );
			saveStates( );
		}

	}

	/**
	 * DataPointsRenderer implementation for 2D+ case.
	 */
	private static class AreaDataPointsRenderer2Dplus extends
			AreaDataPointsRenderer2DStacked
	{

		private static class Envelop
		{
		
			private static class IndexedPoint
			{
		
				public int index;
				public Point pt;
		
				public IndexedPoint( int index, double x, double y )
				{
					this.index = index;
					this.pt = new Point( x, y );
				}
		
				public IndexedPoint( int index, Point pt )
				{
					this.index = index;
					this.pt = pt;
				}
		
				public IndexedPoint copy( )
				{
					return new IndexedPoint( index, pt.x, pt.y );
				}
		
				public double getX( )
				{
					return pt.getX( );
				}
		
				public double getY( )
				{
					return pt.getY( );
				}
		
				public void setY( double y )
				{
					pt.setY( y );
				}
		
				@Override
				public String toString( )
				{
					StringBuilder sb = new StringBuilder( "[" ); //$NON-NLS-1$
					sb.append( index );
					sb.append( ", " ); //$NON-NLS-1$
					sb.append( pt.x );
					sb.append( ", " ); //$NON-NLS-1$
					sb.append( pt.y );
					sb.append( "]" ); //$NON-NLS-1$
					return sb.toString( );
				}
			}
		
			protected final static IGObjectFactory _goFactory = GObjectFactory.instance( );
			private List top = new ArrayList( );
			private List bottom = new ArrayList( );
			private double baseStart;
			private double baseEnd;
			private boolean bTransposed;
		
			public Envelop( double baseStart, double baseEnd, double zeroLocation,
					boolean bTransposed )
			{
				this.bTransposed = bTransposed;
				this.baseStart = baseStart;
				this.baseEnd = baseEnd;
				IndexedPoint ipt0 = new IndexedPoint( 0, baseStart, zeroLocation );
				IndexedPoint ipt1 = new IndexedPoint( 0, baseEnd, zeroLocation );
				top.add( ipt0 );
				top.add( ipt1 );
				IndexedPoint ipt2 = ipt0.copy( );
				IndexedPoint ipt3 = ipt1.copy( );
				bottom.add( ipt2 );
				bottom.add( ipt3 );
			}
		
			private List merge( List list, int index,
					StraightLine sl, boolean bLessThan )
			{
				int len = list.size( );
				if ( len < 2 )
				{
					return list;
				}
		
				List list_new = new ArrayList( );
		
				IndexedPoint ipt0 = list.get( 0 );
		
				for ( int i = 1; i < len; i++ )
				{
					IndexedPoint ipt1 = list.get( i );
		
					double y0 = sl.getYfromX( ipt0.getX( ) );
					double y1 = sl.getYfromX( ipt1.getX( ) );
		
					boolean bUpdate0 = bLessThan ? ( y0 < ipt0.getY( ) )
							: ( y0 > ipt0.getY( ) );
					boolean bUpdate1 = bLessThan ? ( y1 < ipt1.getY( ) )
							: ( y1 > ipt1.getY( ) );
		
					Point pt = sl.getCrossPoint( ipt0.pt, ipt1.pt );
					IndexedPoint ipn = new IndexedPoint( bUpdate0 ? ipt0.index
							: index, pt );
		
					if ( i == 1 )
					{
						if ( bUpdate0 )
						{
							ipt0.index = index;
							ipt0.setY( y0 );
						}
						list_new.add( ipt0 );
					}
					else
					{
						if ( !bUpdate0 )
						{
							list_new.add( ipt0 );
						}
					}
		
					if ( pt != null )
					{
						list_new.add( ipn );
					}
		
					if ( i == len - 1 )
					{
						if ( bUpdate1 )
						{
							ipt1.setY( y1 );
						}
						list_new.add( ipt1 );
					}
		
					ipt0 = ipt1;
				}
		
				return list_new;
			}
		
			private void mergeTop( int index, StraightLine sl )
			{
				this.top = merge( this.top, index, sl, !bTransposed );
			}
		
			private void mergeBottom( int index, StraightLine sl )
			{
				this.bottom = merge( this.bottom, index, sl, bTransposed );
			}
		
			public static Location[] createPolygonFromLine( double x0, double y0,
					double x1, double y1, double dTapeWidth )
			{
				Location[] loa = new Location[4];
				loa[0] = _goFactory.createLocation( x0, y0 );
				loa[1] = _goFactory.createLocation( x1, y1 );
				loa[2] = _goFactory.createLocation( x1 + dTapeWidth, y1
						- dTapeWidth );
				loa[3] = _goFactory.createLocation( x0 + dTapeWidth, y0
						- dTapeWidth );
				return loa;
			}
		
			public List getTopChanges( int index, double dTapeWidth )
			{
				List list = new ArrayList( );
				int len = top.size( );
				IndexedPoint[] top_a = new IndexedPoint[len];
				top_a = top.toArray( top_a );
		
				if ( len > 0 )
				{
					for ( int i = 1; i < len; i++ )
					{
						IndexedPoint ipt0 = top_a[i - 1];
						IndexedPoint ipt1 = top_a[i];
		
						if ( ipt0.index == index - 1 || ipt0.index == index )
						{
							if ( !bTransposed )
							{
								Location[] loa = createPolygonFromLine( ipt0.getX( ),
										ipt0.getY( ),
										ipt1.getX( ),
										ipt1.getY( ),
										dTapeWidth );
								list.add( loa );
							}
							else
							{
								Location[] loa = createPolygonFromLine( ipt0.getY( ),
										ipt0.getX( ),
										ipt1.getY( ),
										ipt1.getX( ),
										dTapeWidth );
								list.add( loa );
							}
		
						}
					}
				}
				return list;
			}
		
			public List getBottomChanges( int index, double dTapeWidth )
			{
				List list = new ArrayList( );
				int len = bottom.size( );
				IndexedPoint[] bottom_a = new IndexedPoint[len];
				bottom_a = bottom.toArray( bottom_a );
		
				if ( len > 0 )
				{
					for ( int i = 1; i < len; i++ )
					{
						IndexedPoint ipt0 = bottom_a[i - 1];
						IndexedPoint ipt1 = bottom_a[i];
		
						if ( ipt0.index == index - 1 || ipt0.index == index )
						{
							if ( !bTransposed )
							{
								Location[] loa = createPolygonFromLine( ipt0.getX( ),
										ipt0.getY( ),
										ipt1.getX( ),
										ipt1.getY( ),
										dTapeWidth );
		
								list.add( loa );
							}
							else
							{
								Location[] loa = createPolygonFromLine( ipt0.getY( ),
										ipt0.getX( ),
										ipt1.getY( ),
										ipt1.getX( ),
										dTapeWidth );
		
								list.add( loa );
							}
						}
					}
				}
		
				return list;
			}
		
			public void addLine( int index, double valueStart, double valueEnd )
			{
				StraightLine sl = new StraightLine( baseStart,
						valueStart,
						baseEnd,
						valueEnd );
		
				mergeTop( index, sl );
				mergeBottom( index, sl );
			}
		}

		private static class StraightLine
		{
		
			private double x0, y0;
			private double k;
		
			public StraightLine( double x0, double y0, double x1, double y1 )
			{
				this.x0 = x0;
				this.y0 = y0;
				k = ( y1 - y0 ) / ( x1 - x0 );
			}
		
			/*
			 * return the cross point of this line and linesegment(pt0-pt1) if there
			 * is any, null otherwise
			 */
			public Point getCrossPoint( Point pt0, Point pt1 )
			{
				Point pt = null;
		
				double xst = pt0.x;
				double yst0 = pt0.y;
				double yst1 = getYfromX( xst );
		
				double xed = pt1.x;
				double yed0 = pt1.y;
				double yed1 = getYfromX( xed );
		
				if ( xed != xst )
				{
					if ( ( yst0 - yst1 ) * ( yed0 - yed1 ) < 0 )
					{
						double x, y;
		
						if ( yed1 != yst1 )
						{
							double rate = ( yed0 - yst0 ) / ( yed1 - yst1 );
							y = ( yst0 - rate * yst1 ) / ( 1 - rate );
							x = getXfromY( y );
						}
						else
						{
							y = yst1;
							double rate = ( xed - xst ) / ( yed0 - yst0 );
							x = xst + rate * ( y - yst0 );
						}
		
						pt = new Point( x, y );
					}
					else if ( yst0 == yst1 )
					{
						pt = new Point( xst, yst0 );
					}
					else if ( yed0 == yed1 )
					{
						pt = new Point( xed, yed0 );
					}
					else if ( yst0 == yed0 )
					{
						if ( k != 0 )
						{
							double y = yst0;
							double x = getXfromY( y );
							pt = new Point( x, y );
						}
					}
				}
		
				if ( pt != null )
				{
					if ( pt.x < Math.min( pt0.x, pt1.x )
							|| pt.x > Math.max( pt0.x, pt1.x ) )
					{
						pt = null;
					}
				}
		
				return pt;
			}
		
			public double getYfromX( double x )
			{
				return y0 + k * ( x - x0 );
			}
		
			public double getXfromY( double y )
			{
				return x0 + ( y - y0 ) / k;
			}
		}

		protected final int iSeriesIndex;
		private final double dTapeWidth;
		private final ColorDefinition tapeColor;
		private final ColorDefinition sideColor;
		private final Location[] loaPlane = createLocationArray( 4 );

		private Envelop[] envelops;

		AreaDataPointsRenderer2Dplus( Context context, Location[] loa )
				throws ChartException
		{
			super( context, loa );
			this.iSeriesIndex = context.line.getSeriesIndex( );
			this.dTapeWidth = srh.getSeriesThickness( );

			Fill paletteEntry = context.paletteEntry;
			ColorDefinition tapeColor = FillUtil.getBrighterColor( paletteEntry );
			ColorDefinition sideColor = FillUtil.getDarkerColor( paletteEntry );

			if ( ls.isTranslucent( ) )
			{
				tapeColor = tapeColor.translucent( );
				sideColor = sideColor.translucent( );
			}
			this.tapeColor = tapeColor;
			this.sideColor = sideColor;
		}

		/*
		 * fill the left most side plane
		 */
		private void fillLeftSide( int findex )
		{
			int index = findex;
			double x = loa[index].getX( );
			double y = loa[index].getY( );

			loaPlane[0].set( x, y );
			loaPlane[1].set( x + dTapeWidth, y - dTapeWidth );

			double lastLocation = loaLast == null ? zeroLocation
					: trans.getY( loaLast[index] );

			if ( trans == Transposition.TRANSPOSED )
			{
				loaPlane[2].set( lastLocation + dTapeWidth, y - dTapeWidth );
				loaPlane[3].set( lastLocation, y );
			}
			else
			{
				loaPlane[2].set( x + dTapeWidth, lastLocation - dTapeWidth );
				loaPlane[3].set( x, lastLocation );
			}

			pre.setOutline( null );
			pre.setBackground( sideColor );
			pre.setPoints( loaPlane );
			dc.addPlaneShadow( pre, PrimitiveRenderEvent.FILL );
		}

		private void fillRightSide( int lindex )
		{
			int index = lindex;
			double x = loa[index].getX( );
			double y = loa[index].getY( );
			loaPlane[0].set( x, y );
			loaPlane[1].set( x + dTapeWidth, y - dTapeWidth );

			// get corresponding index considering last series may be curve line
			double lastLocation = loaLast == null ? zeroLocation
					: trans.getY( loaLast[loaLast.length
							- ( loa.length - index )] );

			if ( trans == Transposition.TRANSPOSED )
			{
				loaPlane[2].set( lastLocation + dTapeWidth, y - dTapeWidth );
				loaPlane[3].set( lastLocation, y );
			}
			else
			{
				loaPlane[2].set( x + dTapeWidth, lastLocation - dTapeWidth );
				loaPlane[3].set( x, lastLocation );
			}
			pre.setOutline( null );
			pre.setBackground( sideColor );
			pre.setPoints( loaPlane );
			pre.setSourceObject( createDataPointSource( index ) );
			// Rightmost shadow must be in the top of z-order, so
			// it's not addPlaneShadow
			dc.addPlane( pre, PrimitiveRenderEvent.FILL );
		}

		// private void fillPlaneShadow( Fill fill, Location[] loa,
		// Object sourceObject, int zorder_hint )
		// {
		// pre.setOutline( null );
		// pre.setBackground( tapeColor );
		// pre.setPoints( loa );
		// pre.setSourceObject( sourceObject );
		// dc.addPlaneShadow( pre, PrimitiveRenderEvent.FILL, zorder_hint );
		// }

		@Override
		protected void beforeLoop( DataPointsSeeker seeker )
		{
			super.beforeLoop( seeker );

			if ( context.line.isRightToLeft( )
					&& context.dpha[0].getOrthogonalValue( ) != null )
			{
				fillLeftSide( seeker.getIndex( ) );
			}

		}

		@Override
		protected void processDataPoint( DataPointsSeeker seeker )
		{
			super.processDataPoint( seeker );
			int index = seeker.getIndex( );
			int pindex = seeker.getPrevIndex( );

			double loX = loa[index].getX( );
			double loY = loa[index].getY( );
			double loXp = loa[pindex].getX( );
			double loYp = loa[pindex].getY( );

			if ( envelops == null )
			{
				envelops = new Envelop[seeker.size( )];
			}

			boolean isTransposed = trans == Transposition.TRANSPOSED;

			if ( trans == Transposition.TRANSPOSED )
			{
				if ( envelops[index] == null )
				{
					envelops[index] = new Envelop( loYp,
							loY,
							zeroLocation,
							isTransposed );
				}

				envelops[index].addLine( iSeriesIndex, loXp, loX );

			}
			else
			{
				if ( envelops[index] == null )
				{
					envelops[index] = new Envelop( loXp,
							loX,
							zeroLocation,
							isTransposed );
				}

				envelops[index].addLine( iSeriesIndex, loYp, loY );
			}

			// ------ Render the top tape.
			List tops = envelops[index].getTopChanges( iSeriesIndex,
					dTapeWidth );
			for ( Location[] polygon : tops )
			{
				pre.setOutline( null );
				pre.setBackground( tapeColor );
				pre.setPoints( polygon );
				pre.setSourceObject( createDataPointSource( index ) );
				dc.addPlaneShadow( pre,
						PrimitiveRenderEvent.FILL,
						context.dpha.length + index );
			}

			// Render the bottom tape.
			// Only if the current or previous value is negative
			List bottoms = envelops[index].getBottomChanges( iSeriesIndex,
					dTapeWidth );
			for ( Location[] polygon : bottoms )
			{
				pre.setOutline( null );
				pre.setBackground( tapeColor );
				pre.setPoints( polygon );
				pre.setSourceObject( createDataPointSource( index ) );
				dc.addPlaneShadow( pre, PrimitiveRenderEvent.FILL, index );
			}

		}

		@Override
		protected void afterLoop( DataPointsSeeker seeker )
		{
			if ( !context.line.isRightToLeft( )
					&& context.dpha[seeker.size( ) - 1].getOrthogonalValue( ) != null )
			{
				fillRightSide( seeker.getIndex( ) );
			}
			super.afterLoop( seeker );
		}

		@Override
		protected void loadLastStates( )
		{
			super.loadLastStates( );
			envelops = (Envelop[]) context.line.getRunTimeContext( )
					.getState( AREA_ENVELOPS );
		}

		@Override
		protected void saveStates( )
		{
			RunTimeContext rtc = context.line.getRunTimeContext( );
			if ( context.line.isLastRuntimeSeriesInAxis( ) )
			{
				// clean stack state.
				rtc.putState( STACKED_SERIES_LOCATION_KEY, null );
				rtc.putState( AREA_ENVELOPS, null );
			}
			else
			{
				List list = new ArrayList( );
				for ( Location lo : loa )
				{
					double[] l = new double[]{
							lo.getX( ), lo.getY( )
					};
					list.add( l );
				}
				rtc.putState( STACKED_SERIES_LOCATION_KEY, list );
				rtc.putState( AREA_ENVELOPS, envelops );
			}
		}

	}

	/*
	 * DataPointsRenderer implementation for 3D.
	 */
	private static class AreaDataPointsRenderer3D extends AreaDataPointsRenderer
	{

		private final SeriesRenderingHints3D srh3d;
		private final Location3D[] loa3d;
		private final Location3D loStart = goFactory.createLocation3D( 0, 0, 0 );
		private final Location3D loEnd = goFactory.createLocation3D( 0, 0, 0 );

		private final Location3D[] loaPlane3d = createLocation3DArray( 4 );
		private final Polygon3DRenderEvent pre3d;
		private final Line3DRenderEvent lre3d;

		private final double dTapeWidth;

		private final ColorDefinition tapeColor;
		private final ColorDefinition sideColor;

		private final double plotBaseLocation;
		private final double plotHeight;

		private int findex;
		
		AreaDataPointsRenderer3D( Context context, Location[] loa, double dTapeWidth )
				throws ChartException
		{
			super( context );

			Object sourceObj = createSeriesSource( );
			this.pre3d = ( (EventObjectCache) context.ipr ).getEventObject( sourceObj,
					Polygon3DRenderEvent.class );
			this.lre3d = ( (EventObjectCache) context.ipr ).getEventObject( sourceObj,
					Line3DRenderEvent.class );
			lre3d.setLineAttributes( lia );

			this.srh3d = (SeriesRenderingHints3D) context.isrh;
			this.plotBaseLocation = srh3d.getPlotBaseLocation( );
			this.plotHeight = srh3d.getPlotHeight( );
			this.loa3d = (Location3D[]) loa;
			this.dTapeWidth = dTapeWidth;

			Fill paletteEntry = context.paletteEntry;
			ColorDefinition tapeColor = FillUtil.getBrighterColor( paletteEntry );
			ColorDefinition sideColor = FillUtil.getDarkerColor( paletteEntry );

			if ( ls.isTranslucent( ) )
			{
				tapeColor = tapeColor.translucent( );
				sideColor = sideColor.translucent( );
			}
			this.tapeColor = tapeColor;
			this.sideColor = sideColor;
		}

		private static double shear( double plotBase, double plotHeight,
				double y )
		{
			if ( y < plotBase )
			{
				y = plotBase;
			}
			if ( y > plotBase + plotHeight )
			{
				y = plotBase + plotHeight;
			}
			return y;
		}

		private void fillBackPlane( int pindex, int index,
				DataPointsSeeker seeker ) throws ChartException
		{
			// Because chart use Painter's algorithm to compute the polygons
			// Z-order, if the polygon is not convex hull, it needs to separate
			// the polygon to multiple convex polygon.
			double x0 = loa3d[pindex].getX( );
			double y0 = shear( plotBaseLocation,
					plotHeight,
					loa3d[pindex].getY( ) );
			double z0 = loa3d[pindex].getZ( );

			double x1 = loa3d[index].getX( );
			double y1 = shear( plotBaseLocation,
					plotHeight,
					loa3d[index].getY( ) );
			double z1 = loa3d[index].getZ( );

			Double pValue = Methods.asDouble( seeker.getDataPointHints( pindex )
					.getOrthogonalValue( ) );
			Double value = Methods.asDouble( seeker.getDataPointHints( index )
					.getOrthogonalValue( ) );
			Object source = createDataPointSource( index );
			if ( zeroLocation <= y0 )
			{
				if ( pValue != null
						&& value != null
						&& ( pValue.doubleValue( ) * value.doubleValue( ) ) < 0 )
				{
					double rate = ( 0 - pValue.doubleValue( ) )
							/ ( value.doubleValue( ) - pValue.doubleValue( ) );
					double midX = x0 + ( x1 - x0 ) * rate;
					double midY = y0 + ( y1 - y0 ) * rate;

					Location3D[] loc = createLocation3DArray( 3 );
					loc[0].set( x0, y0, z0 - dTapeWidth );
					loc[1].set( midX, midY, z0 - dTapeWidth );
					loc[2].set( x0, midY, z0 - dTapeWidth );
					fill3DPlane( fillColor, source, loc, false );

					loc[0].set( midX, midY, z1 - dTapeWidth );
					loc[1].set( x1, midY, z1 - dTapeWidth );
					loc[2].set( x1, y1, z1 - dTapeWidth );
					fill3DPlane( fillColor, source, loc, false );
				}
				else
				{
					loaPlane3d[0].set( x0, y0, z0 - dTapeWidth );
					loaPlane3d[1].set( x1, y1, z1 - dTapeWidth );
					loaPlane3d[2].set( x1, zeroLocation, z1 - dTapeWidth );
					loaPlane3d[3].set( x0, zeroLocation, z0 - dTapeWidth );
					fill3DPlane( fillColor, source, false );
				}
			}
			else
			{
				if ( pValue != null
						&& value != null
						&& ( pValue.doubleValue( ) * value.doubleValue( ) ) < 0 )
				{
					double rate = ( 0 - pValue.doubleValue( ) )
							/ ( value.doubleValue( ) - pValue.doubleValue( ) );
					double midX = x0 + ( x1 - x0 ) * rate;
					double midY = y0 + ( y1 - y0 ) * rate;

					Location3D[] loc = createLocation3DArray( 3 );
					loc[0].set( x0, midY, z0 - dTapeWidth );
					loc[1].set( midX, midY, z0 - dTapeWidth );
					loc[2].set( x0, y0, z0 - dTapeWidth );
					fill3DPlane( fillColor, source, loc, false );

					loc[0].set( midX, midY, z1 - dTapeWidth );
					loc[1].set( x1, y1, z1 - dTapeWidth );
					loc[2].set( x1, midY, z1 - dTapeWidth );
					fill3DPlane( fillColor, source, loc, false );
				}
				else
				{
					loaPlane3d[0].set( x0, zeroLocation, z0 - dTapeWidth );
					loaPlane3d[1].set( x1, zeroLocation, z1 - dTapeWidth );
					loaPlane3d[2].set( x1, y1, z1 - dTapeWidth );
					loaPlane3d[3].set( x0, y0, z0 - dTapeWidth );
					fill3DPlane( fillColor, source, false );
				}
			}
		}

		private Object fillFrontPlane( int pindex, int index,
				DataPointsSeeker seeker ) throws ChartException
		{
			// Because chart use Painter's algorithm to compute the polygons
			// Z-order, if the polygon is not convex hull, it needs to separate
			// the polygon to multiple convex polygon.
			double x0 = loa3d[pindex].getX( );
			double y0 = shear( plotBaseLocation,
					plotHeight,
					loa3d[pindex].getY( ) );
			double z0 = loa3d[pindex].getZ( );

			double x1 = loa3d[index].getX( );
			double y1 = shear( plotBaseLocation,
					plotHeight,
					loa3d[index].getY( ) );
			double z1 = loa3d[index].getZ( );

			Double pValue = Methods.asDouble( seeker.getDataPointHints( pindex )
					.getOrthogonalValue( ) );
			Double value = Methods.asDouble( seeker.getDataPointHints( index )
					.getOrthogonalValue( ) );
			Object source = createDataPointSource( index );
			if ( zeroLocation <= y0 )
			{
				if ( pValue != null
						&& value != null
						&& ( pValue.doubleValue( ) * value.doubleValue( ) ) < 0 )
				{
					double rate = ( 0 - pValue.doubleValue( ) )
							/ ( value.doubleValue( ) - pValue.doubleValue( ) );
					double midX = x0 + ( x1 - x0 ) * rate;
					double midY = y0 + ( y1 - y0 ) * rate;

					Location3D[] loc = createLocation3DArray( 3 );
					loc[0].set( x0, y0, z0 );
					loc[1].set( midX + 1, midY, z0 );
					loc[2].set( x0, midY, z0 );
					fill3DPlane( fillColor, source, loc, true );

					loc[0].set( midX, midY, z1 );
					loc[1].set( x1, midY, z1 );
					loc[2].set( x1, y1, z1 );
					return fill3DPlane( fillColor, source, loc, true );
				}
				else
				{
					loaPlane3d[0].set( x0, y0, z0 );
					loaPlane3d[1].set( x0, zeroLocation, z0 );
					loaPlane3d[2].set( x1, zeroLocation, z1 );
					loaPlane3d[3].set( x1, y1, z1 );
					return fill3DPlane( fillColor, source, false );
				}
			}
			else
			{
				if ( pValue != null
						&& value != null
						&& ( pValue.doubleValue( ) * value.doubleValue( ) ) < 0 )
				{
					double rate = ( 0 - pValue.doubleValue( ) )
							/ ( value.doubleValue( ) - pValue.doubleValue( ) );
					double midX = x0 + ( x1 - x0 ) * rate;
					double midY = y0 + ( y1 - y0 ) * rate;

					Location3D[] loc = createLocation3DArray( 3 );
					loc[0].set( x0, midY, z0 );
					loc[1].set( midX + 1, midY, z0 );
					loc[2].set( x0, y0, z0 );
					fill3DPlane( fillColor, source, loc, true );

					loc[0].set( midX, midY, z1 );
					loc[1].set( x1, y1, z1 );
					loc[2].set( x1, midY, z1 );
					return fill3DPlane( fillColor, source, loc, true );
				}
				else
				{
					loaPlane3d[0].set( x0, zeroLocation, z0 );
					loaPlane3d[1].set( x0, y0, z0 );
					loaPlane3d[2].set( x1, y1, z1 );
					loaPlane3d[3].set( x1, zeroLocation, z1 );
					return fill3DPlane( fillColor, source, false );
				}
			}
		}

		private void fillLeftSidePlane( int findex ) throws ChartException
		{
			double x = loa3d[findex].getX( );
			double y = shear( plotBaseLocation,
					plotHeight,
					loa3d[findex].getY( ) );
			double z = loa3d[findex].getZ( );

			if ( zeroLocation < loa3d[findex].getY( ) )
			{
				loaPlane3d[0].set( x, y, z );
				loaPlane3d[1].set( x, y, z - dTapeWidth );
				loaPlane3d[2].set( x, zeroLocation, z - dTapeWidth );
				loaPlane3d[3].set( x, zeroLocation, z );
			}
			else
			{
				loaPlane3d[0].set( x, y, z );
				loaPlane3d[1].set( x, zeroLocation, z );
				loaPlane3d[2].set( x, zeroLocation, z - dTapeWidth );
				loaPlane3d[3].set( x, y, z - dTapeWidth );
			}

			fill3DPlane( tapeColor, createSeriesSource( ), false );
		}

		private void fillRightSidePlane( int lindex ) throws ChartException
		{
			double x = loa3d[lindex].getX( );
			double y = shear( plotBaseLocation,
					plotHeight,
					loa3d[lindex].getY( ) );
			double z = loa3d[lindex].getZ( );

			if ( shear( plotBaseLocation, plotHeight, loa3d[lindex].getY( ) ) > zeroLocation )
			{
				loaPlane3d[0].set( x, y, z );
				loaPlane3d[1].set( x, zeroLocation, z );
				loaPlane3d[2].set( x, zeroLocation, z - dTapeWidth );
				loaPlane3d[3].set( x, y, z - dTapeWidth );
			}
			else
			{
				loaPlane3d[0].set( x, y, z );
				loaPlane3d[1].set( x, y, z - dTapeWidth );
				loaPlane3d[2].set( x, zeroLocation, z - dTapeWidth );
				loaPlane3d[3].set( x, zeroLocation, z );
			}

			fill3DPlane( sideColor, createDataPointSource( lindex ), false );

		}

		private void fillBottomPlane( int findex, int lindex, DataPointsSeeker seeker )
				throws ChartException
		{
			// Different polygons of chart might be crossing with each other, in order to avoid
			// polygon overlay, here must divide bottom polygon to multiple polygons
			// according to every data point on series.
			Double pValue = Methods.asDouble( seeker.getDataPointHints( findex ).getOrthogonalValue( ) );
			Double value = Methods.asDouble( seeker.getDataPointHints( lindex ).getOrthogonalValue( ) );
			
			if ( pValue != null
					&& value != null
					&& ( pValue.doubleValue( ) * value.doubleValue( ) ) < 0 )
			{
				double x0 = loa3d[findex].getX( );
				double y0 = shear( plotBaseLocation,
						plotHeight,
						loa3d[findex].getY( ) );
				double z0 = loa3d[findex].getZ( );

				double x1 = loa3d[lindex].getX( );
				double y1 = shear( plotBaseLocation,
						plotHeight,
						loa3d[lindex].getY( ) );
				double z1 = loa3d[lindex].getZ( );
				boolean up = ( y0 < y1 );
				double rate = ( 0 - pValue.doubleValue( ) ) / ( value.doubleValue( ) - pValue.doubleValue( ) );
				x1 = x0 + ( x1 - x0 ) * rate; 
				y1 = y0 + ( y1 - y0 ) * rate;

				fillBottomPlane( x0, z0, x1, z1, up );

				x0 = x1;
				y0 = y1;
				z0 = loa3d[findex].getZ( );

				x1 = loa3d[lindex].getX( );
				y1 = shear( plotBaseLocation,
						plotHeight,
						loa3d[lindex].getY( ) );
				z1 = loa3d[lindex].getZ( );

				fillBottomPlane( x0, z0, x1, z1, !up );
			}
			else
			{
				double x0 = loa3d[findex].getX( );
				double z0 = loa3d[findex].getZ( );

				double x1 = loa3d[lindex].getX( );
				double z1 = loa3d[lindex].getZ( );

				fillBottomPlane( x0, z0, x1, z1, true );
			}
		}

		private void fillBottomPlane( double x0, double z0, double x1, double z1, boolean bDoubleSided  )
				throws ChartException
		{
			loaPlane3d[0].set( x0, zeroLocation, z0 );
			loaPlane3d[1].set( x0, zeroLocation, z0 - dTapeWidth );
			loaPlane3d[2].set( x1, zeroLocation, z1 - dTapeWidth );
			loaPlane3d[3].set( x1, zeroLocation, z1 );

			fill3DPlane( fillColor, createSeriesSource( ), bDoubleSided );
		}

		private void fillTopPlane( int pindex, int index,
				DataPointsSeeker seeker ) throws ChartException
		{
			// Different polygons of chart might be crossing with each other, in
			// order to avoid
			// polygon overlay, here must divide bottom polygon to multiple
			// polygons
			// according to every data point on series.
			Double pValue = Methods.asDouble( seeker.getDataPointHints( pindex )
					.getOrthogonalValue( ) );
			Double value = Methods.asDouble( seeker.getDataPointHints( index )
					.getOrthogonalValue( ) );

			if ( pValue != null
					&& value != null
					&& ( pValue.doubleValue( ) * value.doubleValue( ) ) < 0 )
			{
				double x0 = loa3d[pindex].getX( );
				double y0 = shear( plotBaseLocation,
						plotHeight,
						loa3d[pindex].getY( ) );
				double z0 = loa3d[pindex].getZ( );

				double x1 = loa3d[index].getX( );
				double y1 = shear( plotBaseLocation,
						plotHeight,
						loa3d[index].getY( ) );
				double z1 = loa3d[index].getZ( );

				double rate = ( 0 - pValue.doubleValue( ) )
						/ ( value.doubleValue( ) - pValue.doubleValue( ) );
				x1 = x0 + ( x1 - x0 ) * rate;
				y1 = y0 + ( y1 - y0 ) * rate;

				loaPlane3d[0].set( x0, y0, z0 );
				loaPlane3d[1].set( x1, y1, z1 );
				loaPlane3d[2].set( x1, y1, z1 - dTapeWidth );
				loaPlane3d[3].set( x0, y0, z0 - dTapeWidth );
				fill3DPlane( tapeColor, createDataPointSource( index ), true );

				x0 = x1;
				y0 = y1;
				z0 = loa3d[findex].getZ( );

				x1 = loa3d[index].getX( );
				y1 = shear( plotBaseLocation, plotHeight, loa3d[index].getY( ) );
				z1 = loa3d[index].getZ( );

				loaPlane3d[0].set( x0, y0, z0 );
				loaPlane3d[1].set( x1, y1, z1 );
				loaPlane3d[2].set( x1, y1, z1 - dTapeWidth );
				loaPlane3d[3].set( x0, y0, z0 - dTapeWidth );
				fill3DPlane( tapeColor, createDataPointSource( index ), true );
			}
			else
			{
				double x0 = loa3d[pindex].getX( );
				double y0 = shear( plotBaseLocation,
						plotHeight,
						loa3d[pindex].getY( ) );
				double z0 = loa3d[pindex].getZ( );

				double x1 = loa3d[index].getX( );
				double y1 = shear( plotBaseLocation,
						plotHeight,
						loa3d[index].getY( ) );
				double z1 = loa3d[index].getZ( );

				loaPlane3d[0].set( x0, y0, z0 );
				loaPlane3d[1].set( x1, y1, z1 );
				loaPlane3d[2].set( x1, y1, z1 - dTapeWidth );
				loaPlane3d[3].set( x0, y0, z0 - dTapeWidth );

				fill3DPlane( tapeColor, createDataPointSource( index ), false );
			}
		}
		
		private Object fill3DPlane( Fill fillColor, Object sourceObj,
				boolean bDoubleSided ) throws ChartException
		{
			pre3d.setDoubleSided( bDoubleSided );
			pre3d.setOutline( null );
			pre3d.setPoints3D( loaPlane3d );
			pre3d.setBackground( fillColor );
			pre3d.setSourceObject( sourceObj );
			Object event = dc.addPlane( pre3d, PrimitiveRenderEvent.FILL );
			pre3d.setDoubleSided( false );
			return event;
		}

		private Object fill3DPlane( Fill fillColor, Object sourceObj,
				Location3D[] loc, boolean bDoubleSided ) throws ChartException
		{
			pre3d.setDoubleSided( bDoubleSided );
			pre3d.setOutline( null );
			pre3d.setPoints3D( loc );
			pre3d.setBackground( fillColor );
			pre3d.setSourceObject( sourceObj );
			Object event = dc.addPlane( pre3d, PrimitiveRenderEvent.FILL );
			pre3d.setDoubleSided( false );
			return event;
		}
		
		private void drawFrontLine( int pindex, int index, Object eventFront )
		{
			if ( !lia.isVisible( ) )
			{
				return;
			}

			double x0 = loa3d[pindex].getX( );
			double y0 = shear( plotBaseLocation,
					plotHeight,
					loa3d[pindex].getY( ) );
			double z0 = loa3d[pindex].getZ( );

			double x1 = loa3d[index].getX( );
			double y1 = shear( plotBaseLocation,
					plotHeight,
					loa3d[index].getY( ) );
			double z1 = loa3d[index].getZ( );

			loStart.set( x0, y0, z0 );
			loEnd.set( x1, y1, z1 );

			lre3d.setStart3D( loStart );
			lre3d.setEnd3D( loEnd );
			AxesRenderHelper.addLine3DEvent( lre3d, eventFront, dc );
		}

		@Override
		protected double initZeroLocation( Line line, ISeriesRenderingHints isrh )
				throws ChartException
		{
			SeriesRenderingHints3D srh3d = (SeriesRenderingHints3D) isrh;
			return shear( srh3d.getPlotBaseLocation( ),
					srh3d.getPlotHeight( ),
					srh3d.getPlotZeroLocation( ) );
		}

		@Override
		protected void afterLoop( DataPointsSeeker seeker )
				throws ChartException
		{
			int lindex = seeker.getIndex( );
			fillRightSidePlane( lindex );
		}

		@Override
		protected void beforeLoop( DataPointsSeeker seeker )
				throws ChartException
		{
			findex = seeker.getIndex( );
			if ( findex >= 0 )
			{
				fillLeftSidePlane( findex );
			}
		}

		@Override
		protected void processDataPoint( DataPointsSeeker seeker )
				throws ChartException
		{
			int index = seeker.getIndex( );
			int pindex = seeker.getPrevIndex( );
			fillBackPlane( pindex, index, seeker );
			fillTopPlane( pindex, index, seeker );
			fillBottomPlane( pindex, index, seeker );
			Object eventFront = fillFrontPlane( pindex, index, seeker );
			drawFrontLine( pindex, index, eventFront );
		}

	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy