/*******************************************************************************
 * 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.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.withaxes.SeriesRenderingHints;
import org.eclipse.birt.chart.computation.withaxes.SeriesRenderingHints3D;
import org.eclipse.birt.chart.device.IPrimitiveRenderer;
import org.eclipse.birt.chart.engine.extension.i18n.Messages;
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.WrappedStructureSource;
import org.eclipse.birt.chart.exception.ChartException;
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.Gradient;
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.attribute.impl.ColorDefinitionImpl;
import org.eclipse.birt.chart.model.attribute.impl.LineAttributesImpl;
import org.eclipse.birt.chart.model.attribute.impl.Location3DImpl;
import org.eclipse.birt.chart.model.attribute.impl.LocationImpl;
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.plugin.ChartEngineExtensionPlugin;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.util.EcoreUtil;

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

	/**
	 * 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 = (Fill) EcoreUtil.copy( (EObject) ePalette.get( iThisSeriesIndex
						% ePalette.size( ) ) );

				updateTranslucency( fPaletteEntry, se );
			}
		}

		return fPaletteEntry;
	}

	/**
	 * Return the index of first non-null value.
	 * 
	 * @param dpha
	 * @return
	 */
	private int getFirstNonNullIndex( DataPointHints[] dpha )
	{
		for ( int i = 0; i < dpha.length; i++ )
		{
			if ( dpha[i].getOrthogonalValue( ) != null )
			{
				return i;
			}
		}

		return -1;
	}

	/**
	 * Returns the index of last non-null value.
	 * 
	 * @param dpha
	 * @return
	 */
	private int getLastNonNullIndex( DataPointHints[] dpha )
	{
		for ( int i = dpha.length - 1; i >= 0; i-- )
		{
			if ( dpha[i].getOrthogonalValue( ) != null )
			{
				return i;
			}
		}

		return -1;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.birt.chart.render.Line#checkShowAsTape()
	 */
	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 )
			{
				return false;
			}
			if ( getSeriesCount( ) > 2 && !isDimension3D( ) )
			{
				for ( Iterator itr = axaOrthogonal[0].getSeriesDefinitions( )
						.iterator( ); itr.hasNext( ); )
				{
					SeriesDefinition sd = (SeriesDefinition) itr.next( );
					for ( Iterator sitr = sd.getRunTimeSeries( ).iterator( ); sitr.hasNext( ); )
					{
						Series se = (Series) sitr.next( );

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

		return true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.birt.chart.render.Line#renderAsCurve(org.eclipse.birt.chart.device.IPrimitiveRenderer,
	 *      org.eclipse.birt.chart.model.attribute.LineAttributes, float[],
	 *      float[], boolean)
	 */
	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( );
				}
			}
		}

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

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.birt.chart.render.Line#renderDataPoints(org.eclipse.birt.chart.device.IPrimitiveRenderer,
	 *      org.eclipse.birt.chart.model.layout.Plot,
	 *      org.eclipse.birt.chart.computation.withaxes.SeriesRenderingHints,
	 *      org.eclipse.birt.chart.computation.DataPointHints[],
	 *      org.eclipse.birt.chart.model.attribute.LineAttributes, float[],
	 *      float[], boolean)
	 */
	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
	{
		// **********************************************
		// consider 3 policies for rendering area chart
		// 1. connect intermedia null points
		// 2. skip null points at left and right
		// 3. normal(treat all null points as 0)
		// **********************************************

		// TODO we use 2+3 currently,
		// need supporting other combination later.

		Fill seriesPalette = getSeriesPaletteEntry( );
		if ( seriesPalette != null )
		{
			paletteEntry = seriesPalette;
		}

		Location[] loaPlane = null;
		Location3D[] loaPlane3d = null;

		double[] lastX = null;
		double[] lastY = null;
		double[] lastFixedX = null;
		double[] lastFixedY = null;

		Location3D[] loa3d = null;

		boolean isTransposed = ( (ChartWithAxes) getModel( ) ).isTransposed( );
		boolean bRendering3d = isDimension3D( );
		double zeroLocation = 0;
		double plotBaseLocation = 0, plotHeight = 0;
		Series as = getSeries( );

		SeriesRenderingHints srh = null;
		SeriesRenderingHints3D srh3d = null;
		if ( bRendering3d )
		{
			srh3d = (SeriesRenderingHints3D) isrh;
			loa3d = (Location3D[]) loa;
			plotBaseLocation = srh3d.getPlotBaseLocation( );
			plotHeight = srh3d.getPlotHeight( );

			zeroLocation = shear( plotBaseLocation,
					plotHeight,
					srh3d.getPlotZeroLocation( ) );
		}
		else
		{
			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 ( 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( );
				}
			}
		}

		LineRenderEvent lre = bRendering3d
				? null
				: (LineRenderEvent) ( (EventObjectCache) ipr ).getEventObject( StructureSource.createSeries( as ),
						LineRenderEvent.class );
		PolygonRenderEvent pre = bRendering3d
				? null
				: (PolygonRenderEvent) ( (EventObjectCache) ipr ).getEventObject( StructureSource.createSeries( as ),
						PolygonRenderEvent.class );
		Line3DRenderEvent lre3d = bRendering3d
				? (Line3DRenderEvent) ( (EventObjectCache) ipr ).getEventObject( StructureSource.createSeries( as ),
						Line3DRenderEvent.class ) : null;
		Polygon3DRenderEvent pre3d = bRendering3d
				? (Polygon3DRenderEvent) ( (EventObjectCache) ipr ).getEventObject( StructureSource.createSeries( as ),
						Polygon3DRenderEvent.class ) : null;

		boolean isUseLastState = getSeries( ).isStacked( )
				|| getAxis( ).isPercent( );
		DeferredCache dc = getDeferredCache( );

		if ( paletteEntry instanceof ColorDefinition && usePaletteLineColor )
		{
			lia = LineAttributesImpl.copyInstance( lia );
			lia.setColor( ColorDefinitionImpl.copyInstance( (ColorDefinition) paletteEntry ) );
		}

		if ( !bRendering3d )
		{
			dTapeWidth = srh.getSeriesThickness( );
		}

		List points = new ArrayList( );
		List fixedPoints = new ArrayList( );

		if ( isUseLastState )
		{
			Object obj = getRunTimeContext( ).getState( AreaSeries.class );
			lastX = null;
			lastY = null;

			if ( obj instanceof List && ( (List) obj ).size( ) > 0 )
			{
				List lst = (List) obj;

				for ( int i = 0; i < lst.size( ); i++ )
				{
					Object o = lst.get( i );

					if ( o instanceof double[] )
					{
						if ( lastX == null )
						{
							lastX = new double[lst.size( )];
							lastY = new double[lastX.length];
						}

						lastX[i] = ( (double[]) o )[0];
						lastY[i] = ( (double[]) o )[1];
					}
					else
					{
						lastX = null;
						lastY = null;
						break;
					}
				}
			}

			// check last fixed_stacked values.
			obj = getRunTimeContext( ).getState( FIXED_STACKED_SERIES_INDEX_KEY );
			if ( obj instanceof Integer
					&& ( (Integer) obj ).intValue( ) == getPrevVisibleSiblingSeriesIndex( iSeriesIndex ) )
			{
				// only search very previous values.

				obj = getRunTimeContext( ).getState( FIXED_STACKED_SERIES_LOCATION_KEY );
				lastFixedX = null;
				lastFixedY = null;

				if ( obj instanceof List && ( (List) obj ).size( ) > 0 )
				{
					List lst = (List) obj;

					for ( int i = 0; i < lst.size( ); i++ )
					{
						Object o = lst.get( i );

						if ( o instanceof double[] )
						{
							if ( lastFixedX == null )
							{
								lastFixedX = new double[lst.size( )];
								lastFixedY = new double[lastFixedX.length];
							}

							lastFixedX[i] = ( (double[]) o )[0];
							lastFixedY[i] = ( (double[]) o )[1];
						}
						else
						{
							lastFixedX = null;
							lastFixedY = null;
							break;
						}
					}
				}
			}
		}

		if ( bRendering3d )
		{
			lre3d.setLineAttributes( lia );
		}
		else
		{
			lre.setLineAttributes( lia );
		}

		ColorDefinition tapeColor;
		ColorDefinition sideColor;
		Fill fillColor;

		if ( paletteEntry instanceof ColorDefinition )
		{
			fillColor = ColorDefinitionImpl.copyInstance( (ColorDefinition) paletteEntry );
			tapeColor = ( (ColorDefinition) fillColor ).brighter( );
			sideColor = ( (ColorDefinition) fillColor ).darker( );
		}
		else
		{
			fillColor = paletteEntry;
			if ( fillColor instanceof Gradient )
			{
				tapeColor = ( (Gradient) fillColor ).getStartColor( )
						.brighter( );
				sideColor = ( (Gradient) fillColor ).getStartColor( ).darker( );
			}
			else
			{
				tapeColor = lia.getColor( ).brighter( );
				sideColor = lia.getColor( ).darker( );
			}
		}

		if ( getSeries( ).isSetTranslucent( ) && getSeries( ).isTranslucent( ) )
		{
			if ( fillColor instanceof ColorDefinition )
			{
				fillColor = ( (ColorDefinition) fillColor ).translucent( );
			}
			tapeColor = tapeColor.translucent( );
			sideColor = sideColor.translucent( );
		}

		int findex = getFirstNonNullIndex( dpha );
		int lindex = getLastNonNullIndex( dpha );
		
		if ( findex < 0 )
		{
			// No data points to render
			return;
		}

		if ( bRendering3d )
		{
			loaPlane3d = new Location3D[4];
			loaPlane3d[0] = Location3DImpl.create( 0, 0, 0 );
			loaPlane3d[1] = Location3DImpl.create( 0, 0, 0 );
			loaPlane3d[2] = Location3DImpl.create( 0, 0, 0 );
			loaPlane3d[3] = Location3DImpl.create( 0, 0, 0 );

			double y;

			// back face
			for ( int i = 1; i < dpha.length; i++ )
			{
				if ( isNaN( dpha[i].getOrthogonalValue( ) ) )
				{
					continue;
				}

				int pindex = getPreviousNonNullIndex( i, dpha );
				if ( pindex == -1 )
				{
					continue;
				}

				y = shear( plotBaseLocation, plotHeight, loa3d[pindex].getY( ) );

				if ( zeroLocation <= y )
				{
					loaPlane3d[0].set( loa3d[pindex].getX( ),
							y,
							loa3d[pindex].getZ( ) - dTapeWidth );
					loaPlane3d[1].set( loa3d[i].getX( ),
							shear( plotBaseLocation,
									plotHeight,
									loa3d[i].getY( ) ),
							loa3d[i].getZ( ) - dTapeWidth );
					loaPlane3d[2].set( loa3d[i].getX( ),
							zeroLocation,
							loa3d[i].getZ( ) - dTapeWidth );
					loaPlane3d[3].set( loa3d[pindex].getX( ),
							zeroLocation,
							loa3d[pindex].getZ( ) - dTapeWidth );
				}
				else
				{
					loaPlane3d[0].set( loa3d[pindex].getX( ),
							zeroLocation,
							loa3d[pindex].getZ( ) - dTapeWidth );
					loaPlane3d[1].set( loa3d[i].getX( ),
							zeroLocation,
							loa3d[i].getZ( ) - dTapeWidth );
					loaPlane3d[2].set( loa3d[i].getX( ),
							shear( plotBaseLocation,
									plotHeight,
									loa3d[i].getY( ) ),
							loa3d[i].getZ( ) - dTapeWidth );
					loaPlane3d[3].set( loa3d[pindex].getX( ),
							shear( plotBaseLocation,
									plotHeight,
									loa3d[pindex].getY( ) ),
							loa3d[pindex].getZ( ) - dTapeWidth );
				}
				pre3d.setOutline( null );
				pre3d.setPoints3D( loaPlane3d );
				pre3d.setBackground( fillColor );
				pre3d.setSourceObject( WrappedStructureSource.createSeriesDataPoint( as,
						dpha[i] ) );
				dc.addPlane( pre3d, PrimitiveRenderEvent.FILL );
			}

			if ( findex != -1 )
			{
				// left side plane
				if ( zeroLocation < loa3d[findex].getY( ) )
				{
					loaPlane3d[0] = (Location3D) EcoreUtil.copy( loa3d[findex] );
					loaPlane3d[0].setY( shear( plotBaseLocation,
							plotHeight,
							loaPlane3d[0].getY( ) ) );
					loaPlane3d[1] = Location3DImpl.create( loa3d[findex].getX( ),
							shear( plotBaseLocation,
									plotHeight,
									loa3d[findex].getY( ) ),
							loa3d[findex].getZ( ) - dTapeWidth );
					loaPlane3d[2] = Location3DImpl.create( loa3d[findex].getX( ),
							zeroLocation,
							loa3d[findex].getZ( ) - dTapeWidth );
					loaPlane3d[3] = Location3DImpl.create( loa3d[findex].getX( ),
							zeroLocation,
							loa3d[findex].getZ( ) );
				}
				else
				{
					loaPlane3d[0] = (Location3D) EcoreUtil.copy( loa3d[findex] );
					loaPlane3d[0].setY( shear( plotBaseLocation,
							plotHeight,
							loaPlane3d[0].getY( ) ) );
					loaPlane3d[1] = Location3DImpl.create( loa3d[findex].getX( ),
							zeroLocation,
							loa3d[findex].getZ( ) );
					loaPlane3d[2] = Location3DImpl.create( loa3d[findex].getX( ),
							zeroLocation,
							loa3d[findex].getZ( ) - dTapeWidth );
					loaPlane3d[3] = Location3DImpl.create( loa3d[findex].getX( ),
							shear( plotBaseLocation,
									plotHeight,
									loa3d[findex].getY( ) ),
							loa3d[findex].getZ( ) - dTapeWidth );
				}
				pre3d.setOutline( null );
				pre3d.setBackground( tapeColor );
				pre3d.setPoints3D( loaPlane3d );
				pre3d.setSourceObject( StructureSource.createSeries( as ) );
				dc.addPlane( pre3d, PrimitiveRenderEvent.FILL );

				if ( lindex != -1 && findex != lindex )
				{
					// bottom face
					loaPlane3d[0].set( loa3d[findex].getX( ),
							zeroLocation,
							loa3d[findex].getZ( ) );
					loaPlane3d[1].set( loa3d[findex].getX( ),
							zeroLocation,
							loa3d[findex].getZ( ) - dTapeWidth );
					loaPlane3d[2].set( loa3d[lindex].getX( ),
							zeroLocation,
							loa3d[lindex].getZ( ) - dTapeWidth );
					loaPlane3d[3].set( loa3d[lindex].getX( ),
							zeroLocation,
							loa3d[lindex].getZ( ) );
					pre3d.setOutline( null );
					pre3d.setDoubleSided( true );
					pre3d.setPoints3D( loaPlane3d );
					pre3d.setBackground( fillColor );
					pre3d.setSourceObject( StructureSource.createSeries( as ) );
					dc.addPlane( pre3d, PrimitiveRenderEvent.FILL );
					pre3d.setDoubleSided( false );
				}
			}
		}

		for ( int i = 0; i < dpha.length; i++ )
		{		
			if ( bRendering3d )
			{
				points.add( new double[]{
						loa3d[i].getX( ),
						shear( plotBaseLocation, plotHeight, loa3d[i].getY( ) ),
						loa3d[i].getZ( )
				} );
			}
			else
			{
				points.add( new double[]{
						loa[i].getX( ), loa[i].getY( )
				} );

				if ( isTransposed )
				{
					if ( i == findex && i > 0 )
					{
						fixedPoints.add( new double[]{
								fixNaN( zeroLocation,
										Double.NaN,
										i - 1,
										findex,
										lindex,
										loa,
										lastX,
										false ),
								loa[i - 1].getY( )
						} );
					}

					fixedPoints.add( new double[]{
							fixNaN( zeroLocation,
									loa[i].getX( ),
									i,
									findex,
									lindex,
									loa,
									lastX,
									false ),
							loa[i].getY( )
					} );

					if ( i == lindex && i != dpha.length - 1 )
					{
						fixedPoints.add( new double[]{
								fixNaN( zeroLocation,
										Double.NaN,
										i + 1,
										findex,
										lindex,
										loa,
										lastX,
										false ),
								loa[i + 1].getY( )
						} );
					}

				}
				else
				{
					if ( i == findex && i > 0 )
					{
						fixedPoints.add( new double[]{
								loa[i - 1].getX( ),
								fixNaN( zeroLocation,
										Double.NaN,
										i - 1,
										findex,
										lindex,
										loa,
										lastY,
										true )
						} );
					}

					fixedPoints.add( new double[]{
							loa[i].getX( ),
							fixNaN( zeroLocation,
									loa[i].getY( ),
									i,
									findex,
									lindex,
									loa,
									lastY,
									true )
					} );

					if ( i == lindex && i != dpha.length - 1 )
					{
						fixedPoints.add( new double[]{
								loa[i + 1].getX( ),
								fixNaN( zeroLocation,
										Double.NaN,
										i + 1,
										findex,
										lindex,
										loa,
										lastY,
										true )
						} );
					}

				}
			}

			int pindex = getPreviousNonNullIndex( i, dpha );
			// skip very left empty data points
			if ( i == 0 || pindex < 0 && dpha[i].getOrthogonalValue( ) == null )
			{
				continue;
			}

			if ( pindex < 0 )
			{
				pindex = i - 1;
			}

			if ( bShowAsTape )
			{
				if ( bRendering3d )
				{
					if ( isNaN( dpha[i].getOrthogonalValue( ) ) )
					{
						continue;
					}

					// top tape plane
					loaPlane3d[0].set( loa3d[pindex].getX( ),
							shear( plotBaseLocation,
									plotHeight,
									loa3d[pindex].getY( ) ),
							loa3d[pindex].getZ( ) );
					loaPlane3d[1].set( loa3d[i].getX( ),
							shear( plotBaseLocation,
									plotHeight,
									loa3d[i].getY( ) ),
							loa3d[i].getZ( ) );
					loaPlane3d[2].set( loa3d[i].getX( ),
							shear( plotBaseLocation,
									plotHeight,
									loa3d[i].getY( ) ),
							loa3d[i].getZ( ) - dTapeWidth );
					loaPlane3d[3].set( loa3d[pindex].getX( ),
							shear( plotBaseLocation,
									plotHeight,
									loa3d[pindex].getY( ) ),
							loa3d[pindex].getZ( ) - dTapeWidth );
					pre3d.setOutline( null );
					pre3d.setBackground( tapeColor );
					pre3d.setPoints3D( loaPlane3d );
					pre3d.setSourceObject( WrappedStructureSource.createSeriesDataPoint( as,
							dpha[i] ) );
					dc.addPlane( pre3d, PrimitiveRenderEvent.FILL );

					if ( i == lindex )
					{
						// right side plane
						if ( shear( plotBaseLocation,
								plotHeight,
								loa3d[i].getY( ) ) > zeroLocation )
						{
							loaPlane3d[0].set( loa3d[i].getX( ),
									shear( plotBaseLocation,
											plotHeight,
											loa3d[i].getY( ) ),
									loa3d[i].getZ( ) );
							loaPlane3d[1].set( loa3d[i].getX( ),
									zeroLocation,
									loa3d[i].getZ( ) );
							loaPlane3d[2].set( loa3d[i].getX( ),
									zeroLocation,
									loa3d[i].getZ( ) - dTapeWidth );
							loaPlane3d[3].set( loa3d[i].getX( ),
									shear( plotBaseLocation,
											plotHeight,
											loa3d[i].getY( ) ),
									loa3d[i].getZ( ) - dTapeWidth );
						}
						else
						{
							loaPlane3d[0].set( loa3d[i].getX( ),
									shear( plotBaseLocation,
											plotHeight,
											loa3d[i].getY( ) ),
									loa3d[i].getZ( ) );
							loaPlane3d[1].set( loa3d[i].getX( ),
									shear( plotBaseLocation,
											plotHeight,
											loa3d[i].getY( ) ),
									loa3d[i].getZ( ) - dTapeWidth );
							loaPlane3d[2].set( loa3d[i].getX( ),
									zeroLocation,
									loa3d[i].getZ( ) - dTapeWidth );
							loaPlane3d[3].set( loa3d[i].getX( ),
									zeroLocation,
									loa3d[i].getZ( ) );
						}

						pre3d.setOutline( null );
						pre3d.setBackground( sideColor );
						pre3d.setPoints3D( loaPlane3d );
						pre3d.setSourceObject( WrappedStructureSource.createSeriesDataPoint( as,
								dpha[i] ) );
						dc.addPlane( pre3d, PrimitiveRenderEvent.FILL );
					}
				}
				else
				{
					double loX = loa[i].getX( );
					double loY = loa[i].getY( );
					double loXp = loa[i - 1].getX( );
					double loYp = loa[i - 1].getY( );

					if ( isTransposed )
					{
						loX = fixNaN( zeroLocation,
								loX,
								i,
								findex,
								lindex,
								loa,
								lastX,
								false );
						loXp = fixNaN( zeroLocation,
								loXp,
								i - 1,
								findex,
								lindex,
								loa,
								lastX,
								false );
					}
					else
					{
						loY = fixNaN( zeroLocation,
								loY,
								i,
								findex,
								lindex,
								loa,
								lastY,
								true );
						loYp = fixNaN( zeroLocation,
								loYp,
								i - 1,
								findex,
								lindex,
								loa,
								lastY,
								true );
					}

					if ( loaPlane == null )
					{
						loaPlane = new Location[4];
						loaPlane[0] = LocationImpl.create( loXp, loYp );
						loaPlane[1] = LocationImpl.create( loX, loY );
						loaPlane[2] = LocationImpl.create( loX + dTapeWidth,
								loY - dTapeWidth );
						loaPlane[3] = LocationImpl.create( loXp + dTapeWidth,
								loYp - dTapeWidth );
					}
					else
					{
						loaPlane[0].set( loXp, loYp );
						loaPlane[1].set( loX, loY );
						loaPlane[2].set( loX + dTapeWidth, loY - dTapeWidth );
						loaPlane[3].set( loXp + dTapeWidth, loYp - dTapeWidth );
					}

					pre.setOutline( null );
					pre.setBackground( tapeColor );
					pre.setPoints( loaPlane );
					pre.setSourceObject( WrappedStructureSource.createSeriesDataPoint( as,
							dpha[i] ) );
					dc.addPlane( pre, PrimitiveRenderEvent.FILL );

					if ( i == lindex && !isRightToLeft( ) )
					{
						loaPlane[0].set( loX, loY );
						loaPlane[1].set( loX + dTapeWidth, loY - dTapeWidth );
						if ( isTransposed )
						{
							double lastLocation = getLastFixedValue( zeroLocation,
									loX,
									loY,
									lastFixedX,
									lastFixedY,
									false );
							loaPlane[2].set( lastLocation + dTapeWidth, loY
									- dTapeWidth );
							loaPlane[3].set( lastLocation, loY );
						}
						else
						{
							double lastLocation = getLastFixedValue( zeroLocation,
									loX,
									loY,
									lastFixedX,
									lastFixedY,
									true );
							loaPlane[2].set( loX + dTapeWidth, lastLocation
									- dTapeWidth );
							loaPlane[3].set( loX, lastLocation );
						}
						pre.setOutline( null );
						pre.setBackground( sideColor );
						pre.setPoints( loaPlane );
						pre.setSourceObject( WrappedStructureSource.createSeriesDataPoint( as,
								dpha[i] ) );
						dc.addPlane( pre, PrimitiveRenderEvent.FILL );
					}
					else if ( pindex == findex && isRightToLeft( ) )
					{
						loaPlane[0].set( loa[pindex].getX( ),
								loa[pindex].getY( ) );
						loaPlane[1].set( loa[pindex].getX( ) + dTapeWidth,
								loa[pindex].getY( ) - dTapeWidth );
						if ( isTransposed )
						{
							double lastLocation = getLastFixedValue( zeroLocation,
									loa[pindex].getX( ),
									loa[pindex].getY( ),
									lastFixedX,
									lastFixedY,
									false );
							loaPlane[2].set( lastLocation + dTapeWidth,
									loa[pindex].getY( ) - dTapeWidth );
							loaPlane[3].set( lastLocation, loa[pindex].getY( ) );
						}
						else
						{
							double lastLocation = getLastFixedValue( zeroLocation,
									loa[pindex].getX( ),
									loa[pindex].getY( ),
									lastFixedX,
									lastFixedY,
									true );
							loaPlane[2].set( loa[pindex].getX( ) + dTapeWidth,
									lastLocation - dTapeWidth );
							loaPlane[3].set( loa[pindex].getX( ), lastLocation );
						}
						pre.setOutline( null );
						pre.setBackground( sideColor );
						pre.setPoints( loaPlane );
						dc.addPlane( pre, PrimitiveRenderEvent.FILL );
					}
				}
			}

		}

		if ( bRendering3d )
		{
			for ( int i = 1; i < dpha.length; i++ )
			{
				if ( isNaN( dpha[i].getOrthogonalValue( ) ) )
				{
					continue;
				}

				int pindex = getPreviousNonNullIndex( i, dpha );
				if ( pindex == -1 )
				{
					continue;
				}

				// front face
				if ( zeroLocation <= shear( plotBaseLocation,
						plotHeight,
						loa3d[pindex].getY( ) ) )
				{
					loaPlane3d[0].set( loa3d[pindex].getX( ),
							shear( plotBaseLocation,
									plotHeight,
									loa3d[pindex].getY( ) ),
							loa3d[pindex].getZ( ) );
					loaPlane3d[1].set( loa3d[pindex].getX( ),
							zeroLocation,
							loa3d[pindex].getZ( ) );
					loaPlane3d[2].set( loa3d[i].getX( ),
							zeroLocation,
							loa3d[i].getZ( ) );
					loaPlane3d[3].set( loa3d[i].getX( ),
							shear( plotBaseLocation,
									plotHeight,
									loa3d[i].getY( ) ),
							loa3d[i].getZ( ) );
				}
				else
				{
					loaPlane3d[0].set( loa3d[pindex].getX( ),
							zeroLocation,
							loa3d[pindex].getZ( ) );
					loaPlane3d[1].set( loa3d[pindex].getX( ),
							shear( plotBaseLocation,
									plotHeight,
									loa3d[pindex].getY( ) ),
							loa3d[pindex].getZ( ) );
					loaPlane3d[2].set( loa3d[i].getX( ),
							shear( plotBaseLocation,
									plotHeight,
									loa3d[i].getY( ) ),
							loa3d[i].getZ( ) );
					loaPlane3d[3].set( loa3d[i].getX( ),
							zeroLocation,
							loa3d[i].getZ( ) );
				}
				pre3d.setOutline( null );
				pre3d.setPoints3D( loaPlane3d );
				pre3d.setBackground( fillColor );
				pre3d.setSourceObject( WrappedStructureSource.createSeriesDataPoint( as,
						dpha[i] ) );
				dc.addPlane( pre3d, PrimitiveRenderEvent.FILL );

				if ( lia.isSetVisible( ) && lia.isVisible( ) )
				{
					Location3D lstart = (Location3D) EcoreUtil.copy( loa3d[pindex] );
					Location3D lend = (Location3D) EcoreUtil.copy( loa3d[i] );

					lstart.setY( shear( plotBaseLocation,
							plotHeight,
							lstart.getY( ) ) );
					lend.setY( shear( plotBaseLocation, plotHeight, lend.getY( ) ) );

					lre3d.setStart3D( lstart );
					lre3d.setEnd3D( lend );
					dc.addLine( lre3d );
				}
			}
		}

		if ( lastX != null )
		{
			// only render when have non-null values
			if ( findex != -1 )
			{
				List lst = new ArrayList( );

				for ( int i = 0; i < fixedPoints.size( ); i++ )
				{
					double[] pt = (double[]) fixedPoints.get( i );
					lst.add( LocationImpl.create( pt[0], pt[1] ) );
				}

				if ( lastFixedY != null )
				{
					for ( int i = lastFixedY.length - 1; i >= 0; i-- )
					{
						lst.add( LocationImpl.create( lastFixedX[i],
								lastFixedY[i] ) );
					}
				}
				else
				{
					for ( int i = lastY.length - 1; i >= 0; i-- )
					{
						lst.add( LocationImpl.create( lastX[i], lastY[i] ) );
					}
				}

				Location[] pa = (Location[]) lst.toArray( new Location[lst.size( )] );

				pa = filterNull( pa );

				if ( pa.length > 3 )
				{
					pre.setOutline( null );
					pre.setPoints( pa );
					pre.setBackground( fillColor );
					pre.setSourceObject( StructureSource.createSeries( as ) );
					dc.addPlane( pre, PrimitiveRenderEvent.FILL );
				}

				if ( lia.isSetVisible( ) && lia.isVisible( ) )
				{
					List flst = fixedPoints;

					Location start = LocationImpl.create( 0, 0 );
					Location end = LocationImpl.create( 0, 0 );

					for ( int i = 0; i < flst.size( ) - 1; i++ )
					{
						int rdx = getRealIndexFromFixedIndex( fixedPoints,
								i,
								isTransposed );

						if ( rdx < findex - 1
								|| rdx > lindex
								|| isFirstDupIndex( fixedPoints,
										i,
										isTransposed ) )
						{
							continue;
						}

						double[] pt = (double[]) flst.get( i );
						start.set( pt[0], pt[1] );
						pt = (double[]) flst.get( i + 1 );
						end.set( pt[0], pt[1] );

						lre.setStart( start );
						lre.setEnd( end );
						lre.setSourceObject( WrappedStructureSource.createSeriesDataPoint( as,
								dpha[rdx] ) );
						dc.addLine( lre );
					}
				}
			}
		}
		else
		{
			if ( !bRendering3d )
			{
				List lst = new ArrayList( );

				for ( int i = 0; i < fixedPoints.size( ); i++ )
				{
					double[] pt = (double[]) fixedPoints.get( i );
					lst.add( LocationImpl.create( pt[0], pt[1] ) );
				}

				if ( fixedPoints.size( ) > 0 )
				{
					if ( isTransposed )
					{
						lst.add( LocationImpl.create( zeroLocation,
								( (double[]) fixedPoints.get( fixedPoints.size( ) - 1 ) )[1] ) );
						lst.add( LocationImpl.create( zeroLocation,
								( (double[]) fixedPoints.get( 0 ) )[1] ) );
					}
					else
					{
						lst.add( LocationImpl.create( ( (double[]) fixedPoints.get( fixedPoints.size( ) - 1 ) )[0],
								zeroLocation ) );
						lst.add( LocationImpl.create( ( (double[]) fixedPoints.get( 0 ) )[0],
								zeroLocation ) );
					}
				}

				Location[] pa = (Location[]) lst.toArray( new Location[lst.size( )] );

				pre.setOutline( null );
				pre.setPoints( pa );
				pre.setBackground( fillColor );
				pre.setSourceObject( StructureSource.createSeries( as ) );
				dc.addPlane( pre, PrimitiveRenderEvent.FILL );

				if ( lia.isSetVisible( ) && lia.isVisible( ) )
				{
					List flst = fixedPoints;

					Location start = LocationImpl.create( 0, 0 );
					Location end = LocationImpl.create( 0, 0 );

					for ( int i = 0; i < flst.size( ) - 1; i++ )
					{
						int rdx = getRealIndexFromFixedIndex( fixedPoints,
								i,
								isTransposed );

						if ( rdx < findex - 1
								|| rdx > lindex
								|| isFirstDupIndex( fixedPoints,
										i,
										isTransposed ) )
						{
							continue;
						}

						double[] pt = (double[]) flst.get( i );
						start.set( pt[0], pt[1] );
						pt = (double[]) flst.get( i + 1 );
						end.set( pt[0], pt[1] );

						lre.setStart( start );
						lre.setEnd( end );
						lre.setSourceObject( WrappedStructureSource.createSeriesDataPoint( as,
								dpha[rdx] ) );
						dc.addLine( lre );
					}
				}
			}
		}

		if ( isLastRuntimeSeriesInAxis( ) )
		{
			// clean stack state.
			getRunTimeContext( ).putState( AreaSeries.class, null );
			getRunTimeContext( ).putState( FIXED_STACKED_SERIES_LOCATION_KEY,
					null );
			getRunTimeContext( ).putState( FIXED_STACKED_SERIES_INDEX_KEY, null );
		}
		else
		{
			getRunTimeContext( ).putState( AreaSeries.class, points );
			getRunTimeContext( ).putState( FIXED_STACKED_SERIES_LOCATION_KEY,
					fixedPoints );
			getRunTimeContext( ).putState( FIXED_STACKED_SERIES_INDEX_KEY,
					new Integer( iSeriesIndex ) );
		}
		
		if ( !bRendering3d )
		{
			// Always flush render events to avoid bottom graphics appearing
			// over the top
			dc.flushPlaneAndLine( );
		}
	}

	/**
	 * checks if this is the first duplicate point index, e.g first duplicate
	 * non-null value index.
	 * 
	 * @param fixedPoints
	 * @param fixedIndex
	 * @param checkX
	 * @return
	 */
	private boolean isFirstDupIndex( List fixedPoints, int fixedIndex,
			boolean checkX )
	{
		double lastValue = Double.NaN;
		for ( int i = 0; i < fixedPoints.size( ) - 1; i++ )
		{
			double[] pt = (double[]) fixedPoints.get( i );

			if ( Double.isNaN( lastValue ) )
			{
				lastValue = checkX ? pt[1] : pt[0];
			}
			else
			{
				double currentValue = checkX ? pt[1] : pt[0];

				if ( currentValue == lastValue )
				{
					return ( fixedIndex == i - 1 );
				}

				lastValue = currentValue;
			}
		}

		return false;
	}

	/**
	 * Return the value in same position of last series.
	 * 
	 * @param zeroLocation
	 * @param x
	 * @param y
	 * @param lastX
	 * @param lastY
	 * @param checkY
	 * @return
	 */
	private double getLastFixedValue( double zeroLocation, double x, double y,
			double[] lastX, double[] lastY, boolean checkY )
	{
		if ( lastX == null )
		{
			return zeroLocation;
		}

		if ( checkY )
		{
			for ( int i = 0; i < lastX.length; i++ )
			{
				if ( x == lastX[i] )
				{
					return lastY[i];
				}
			}
		}
		else
		{
			for ( int i = 0; i < lastY.length; i++ )
			{
				if ( y == lastY[i] )
				{
					return lastX[i];
				}
			}
		}

		return zeroLocation;
	}

	/**
	 * Translats the index from fixedPoints to real points.
	 * 
	 * @param fixedPoints
	 * @param fixedIndex
	 * @param checkX
	 * @return
	 */
	private int getRealIndexFromFixedIndex( List fixedPoints, int fixedIndex,
			boolean checkX )
	{
		double lastValue = Double.NaN;
		for ( int i = fixedIndex; i >= 0; i-- )
		{
			double[] pt = (double[]) fixedPoints.get( i );

			if ( Double.isNaN( lastValue ) )
			{
				lastValue = checkX ? pt[1] : pt[0];
			}
			else
			{
				double currentValue = checkX ? pt[1] : pt[0];

				if ( currentValue == lastValue )
				{
					fixedIndex--;
				}

				lastValue = currentValue;
			}
		}

		return fixedIndex;
	}

	/**
	 * Fix the NaN value for stacked case
	 * 
	 * @param baseValue
	 * @param currentValue
	 * @param currentIndex
	 * @param length
	 * @param lastValues
	 * @return
	 */
	private double fixNaN( double baseValue, double currentValue,
			int currentIndex, int firstNonNullIndex, int lastNonNullIndex,
			Location[] currentXY, double[] lastValues, boolean fixY )
	{
		// only fix NaN values
		if ( !Double.isNaN( currentValue ) )
		{
			return currentValue;
		}

		// // extrapolate intermedia null vlaue
		// if ( currentIndex > firstNonNullIndex
		// && currentIndex < lastNonNullIndex )
		// {
		// // search first previous non-NaN index
		// int pindex = -1;
		// for ( int i = currentIndex - 1; i >= 0; i-- )
		// {
		// if ( !Double.isNaN( fixY ? currentXY[i].getY( )
		// : currentXY[i].getX( ) ) )
		// {
		// pindex = i;
		// break;
		// }
		// }
		//
		// assert ( pindex == -1 );
		//
		// // search first next non-NaN index
		// int nindex = -1;
		// for ( int i = currentIndex + 1; i < currentXY.length; i++ )
		// {
		// if ( !Double.isNaN( fixY ? currentXY[i].getY( )
		// : currentXY[i].getX( ) ) )
		// {
		// nindex = i;
		// break;
		// }
		// }
		//
		// assert ( nindex == -1 );
		//
		// if ( fixY )
		// {
		// return currentXY[pindex].getY( )
		// + ( currentXY[nindex].getY( ) - currentXY[pindex].getY( ) )
		// * ( currentIndex - pindex )
		// / ( nindex - pindex );
		// }
		// else
		// {
		// return currentXY[pindex].getX( )
		// + ( currentXY[nindex].getX( ) - currentXY[pindex].getX( ) )
		// * ( currentIndex - pindex )
		// / ( nindex - pindex );
		// }
		// }

		// use baseValue if this is the first series
		if ( lastValues == null )
		{
			return baseValue;
		}

		// use last value if it's not NaN
		if ( !Double.isNaN( lastValues[currentIndex] ) )
		{
			return lastValues[currentIndex];
		}

		// search first previous non-NaN index
		int pindex = -1;
		for ( int i = currentIndex - 1; i >= 0; i-- )
		{
			if ( !Double.isNaN( lastValues[i] ) )
			{
				pindex = i;
				break;
			}
		}

		if ( pindex == -1 )
		{
			return baseValue;
		}

		// search first next non-NaN index
		int nindex = -1;
		for ( int i = currentIndex + 1; i < currentXY.length; i++ )
		{
			if ( !Double.isNaN( lastValues[i] ) )
			{
				nindex = i;
				break;
			}
		}

		if ( nindex == -1 )
		{
			return baseValue;
		}

		// extrapolate intermedia last value.
		return lastValues[pindex]
				+ ( lastValues[nindex] - lastValues[pindex] )
				* ( currentIndex - pindex ) / ( nindex - pindex );
	}

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

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.birt.chart.render.Line#renderShadow(org.eclipse.birt.chart.device.IPrimitiveRenderer,
	 *      org.eclipse.birt.chart.model.layout.Plot,
	 *      org.eclipse.birt.chart.model.attribute.LineAttributes, float[],
	 *      float[], boolean)
	 */
	protected void renderShadow( IPrimitiveRenderer ipr, Plot p,
			LineAttributes lia, Location[] loa, boolean bShowAsTape )
	{
		// AREA DONT RENDER A SHADOW
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.birt.chart.render.BaseRenderer#renderLegendGraphic(org.eclipse.birt.chart.device.IPrimitiveRenderer,
	 *      org.eclipse.birt.chart.model.layout.Legend,
	 *      org.eclipse.birt.chart.model.attribute.Fill,
	 *      org.eclipse.birt.chart.model.attribute.Bounds)
	 */
	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 = ColorDefinitionImpl.RED( );
		}

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

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

		Location[] loa = new Location[5];
		loa[0] = LocationImpl.create( bo.getLeft( ) + 1 * getDeviceScale( ),
				bo.getTop( ) + bo.getHeight( ) - 2 * getDeviceScale( ) );
		loa[1] = LocationImpl.create( bo.getLeft( )
				+ bo.getWidth( ) - 1 * getDeviceScale( ), bo.getTop( )
				+ bo.getHeight( ) - 2 * getDeviceScale( ) );
		loa[2] = LocationImpl.create( bo.getLeft( ) + bo.getWidth( ) * 5 / 6,
				bo.getTop( ) + bo.getHeight( ) / 3 );
		loa[3] = LocationImpl.create( bo.getLeft( ) + bo.getWidth( ) * 2 / 3,
				bo.getTop( ) + bo.getHeight( ) / 2 );
		loa[4] = LocationImpl.create( 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.isSetVisible( ) )
		{
			throw new ChartException( ChartEngineExtensionPlugin.ID,
					ChartException.RENDERING,
					"exception.unspecified.marker.linestyle.visibility", //$NON-NLS-1$
					Messages.getResourceBundle( getRunTimeContext( ).getULocale( ) ) );
		}
		if ( liaMarker.isVisible( ) )
		{
			if ( fPaletteEntry instanceof ColorDefinition
					&& ( ls.isSetPaletteLineColor( ) && ls.isPaletteLineColor( ) ) )
			{
				liaMarker = LineAttributesImpl.copyInstance( liaMarker );
				liaMarker.setColor( ColorDefinitionImpl.copyInstance( (ColorDefinition) fPaletteEntry ) );
			}

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

	}
}
