
package jp.riken.brain.ni.samuraigraph.figure.java2d;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;

import jp.riken.brain.ni.samuraigraph.base.SGAxis;
import jp.riken.brain.ni.samuraigraph.base.SGData;
import jp.riken.brain.ni.samuraigraph.base.SGDrawingElement;
import jp.riken.brain.ni.samuraigraph.base.SGIAxisElement;
import jp.riken.brain.ni.samuraigraph.base.SGIConstants;
import jp.riken.brain.ni.samuraigraph.base.SGIData;
import jp.riken.brain.ni.samuraigraph.base.SGIGraphElement;
import jp.riken.brain.ni.samuraigraph.base.SGIVXYGraphElement;
import jp.riken.brain.ni.samuraigraph.base.SGProperties;
import jp.riken.brain.ni.samuraigraph.base.SGPropertyDialog;
import jp.riken.brain.ni.samuraigraph.base.SGTuple2f;
import jp.riken.brain.ni.samuraigraph.base.SGUtilityNumber;
import jp.riken.brain.ni.samuraigraph.base.SGUtilityText;
import jp.riken.brain.ni.samuraigraph.data.SGIVXYTypeData;
import jp.riken.brain.ni.samuraigraph.data.SGVXYData;
import jp.riken.brain.ni.samuraigraph.figure.SGDrawingElementArrow;
import jp.riken.brain.ni.samuraigraph.figure.SGElementGroup;
import jp.riken.brain.ni.samuraigraph.figure.SGIVXYDataConstants;
import jp.riken.brain.ni.samuraigraph.figure.SGUtilityForFigureElement;

import org.w3c.dom.Element;
import org.w3c.dom.NodeList;



/**
 * Class of two-dimensional vector graph.
 */
public class SGVXYGraphElement extends SGGraphElement
	implements SGIVXYGraphElement, SGIVXYDataConstants
{


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


	/**
	 * 
	 * @return
	 */
	public String getClassDescription()
	{
		return "VXY Graph";
	}


	/**
	 * 
	 * @param data
	 * @param name
	 * @param p
	 * @return
	 */
	public boolean addData( final SGData data, final String name, final SGProperties p )
	{
		if( !(data instanceof SGIVXYTypeData) )
		{
			return false;
		}

		if( super.addData(data,name,p) == false )
		{
			return false;
		}

		return true;
	}


	/**
	 * 
	 * @param data
	 * @param name
	 * @return
	 */
	public boolean addData( final SGData data, final String name )
	{
		if( !(data instanceof SGIVXYTypeData) )
		{
			return false;
		}

		if( super.addData(data,name) == false )
		{
			return false;
		}

		return true;
	}


	//
	protected ElementGroupSetInGraph createGroupSet( SGData data, String name )
	{
		// get axes list
		final SGAxis bAxis = this.mAxisElement.getAxisInPlane( SGAxisElement.AXIS_HORIZONTAL_1 );
		final SGAxis tAxis = this.mAxisElement.getAxisInPlane( SGAxisElement.AXIS_HORIZONTAL_2 );
		final SGAxis lAxis = this.mAxisElement.getAxisInPlane( SGAxisElement.AXIS_PERPENDICULAR_1 );
		final SGAxis rAxis = this.mAxisElement.getAxisInPlane( SGAxisElement.AXIS_PERPENDICULAR_2 );
		SGAxis axisX = null;
		SGAxis axisY = null;
		if( DEFAULT_SCALE_REFERENCE.equals( SGIAxisElement.LEFT_BOTTOM ) )
		{
			axisX = bAxis;
			axisY = lAxis;
		}
		else if( DEFAULT_SCALE_REFERENCE.equals( SGIAxisElement.LEFT_TOP ) )
		{
			axisX = tAxis;
			axisY = lAxis;
		}
		else if( DEFAULT_SCALE_REFERENCE.equals( SGIAxisElement.RIGHT_BOTTOM ) )
		{
			axisX = bAxis;
			axisY = rAxis;
		}
		else if( DEFAULT_SCALE_REFERENCE.equals( SGIAxisElement.RIGHT_TOP ) )
		{
			axisX = tAxis;
			axisY = rAxis;
		}
		else
		{
			return null;
		}


		//
		ElementGroupSetInGraph groupSet = null;
		if( data instanceof SGIVXYTypeData )
		{
			groupSet = this.createSingleGroupSet( (SGIVXYTypeData)data, axisX, axisY, name );
		}
		else
		{
			return null;
		}

		return groupSet;		
	}



	/**
	 * 
	 */
	private ElementGroupSetInVXYGraph createSingleGroupSet(
		final SGIVXYTypeData data, final SGAxis axisX, final SGAxis axisY,
		final String name )
	{
		ElementGroupSetInVXYGraph groupSet
			= this.createGroupSetVXYInstance( data, axisX, axisY );

		groupSet.setName(name);

		return groupSet;
	}


	//
	private ElementGroupSetInVXYGraph createGroupSetVXYInstance(
		final SGIVXYTypeData data, final SGAxis axisX, final SGAxis axisY )
	{

		final ElementGroupSetInVXYGraph groupSet
			= new ElementGroupSetInVXYGraph();

		// set axes
		groupSet.setXAxis(axisX);
		groupSet.setYAxis(axisY);

		// create instances of the points
		final int num = data.getPointsNumber();
		groupSet.initPointsArray(num);

		// add drawing element groups of arrows
		if( groupSet.addDrawingElementGroup( SGElementGroup.ARROW_GROUP ) == false )
		{
			return null;
		}

		// set the magnitude per cm
		final float percmReduced
			= SGUtilityForFigureElement.getInitialMagnitudePerCM( data, this );
		groupSet.setMagnitudePerCM( percmReduced );
		groupSet.setDirectionInvariant( data.isPolar() );

		// set the location of the drawing elements of data
		if( groupSet.updateDrawingElementsLocation( (SGData)data ) == false )
		{
			return null;
		}

		// set magnification
		groupSet.setMagnification( this.getMagnification() );

		return groupSet;
	}


	protected float getInitialMagnitudePerCM(
		SGIVXYTypeData data )
	{
		final double[] mArray = data.getMagnitudeArray();
		final float max = (float)SGUtilityNumber.max( mArray );
		final float min = (float)SGUtilityNumber.min( mArray );

		final float size = Math.max( this.getGraphRectWidth(), this.getGraphRectHeight() );
		final float len = 0.10f*size;

		final float factor = len/max;
		final float percm = max/(len*SGIConstants.CM_POINT_RATIO);
		final float percmReduced = (float)SGUtilityNumber.getNumberInRangeOrder( percm, min, max, 3 );

		return percmReduced;
	}

	/**
	 * プロパティダイアログの作成
	 */
	protected boolean createDataDialog()
	{
		final SGPropertyDialogVXYData dg
		 = new SGPropertyDialogVXYData( mDialogOwner, true );

		mDialog = dg;

		return true;
	}

	
	/**
	 * 
	 */
	public void paintGraphics( Graphics g, boolean clip )
	{
		Graphics2D g2d = (Graphics2D)g;

		List list = this.mChildList;

		// clip the rectangle
		if( clip )
		{
			SGUtilityForFigureElementJava2D.clipGraphRect(this,g2d);
		}

		// draw the graph
		for( int ii=0; ii<list.size(); ii++ )
		{
			final ElementGroupSetInGraph groupSet
				= (ElementGroupSetInGraph)list.get(ii);
			if( groupSet.isVisible() )
			{
				groupSet.setClipFlag( clip );
				groupSet.paintGraphics2D(g2d);
			}
		}

		if( clip )
		{
			g2d.setClip( this.getViewBounds() );
		}

	}



	/**
	 * 
	 */
	private ElementGroupArrow getGroupArrow( final ElementGroupSetInVXYGraph groupSet )
	{
		ArrayList groupList = groupSet.getElementGroupList();
		for( int ii=0; ii<groupList.size(); ii++ )
		{
			final SGElementGroup group = (SGElementGroup)groupList.get(ii);
			if( group instanceof ElementGroupArrow )
			{
				return (ElementGroupArrow)group;
			}
		}

		return null;
	}


	/**
	 * 
	 */
	protected ElementGroupSetInGraph getGroupSetNewInstance( final SGData data )
	{
		ElementGroupSetInGraph groupSet = null;

		if( data instanceof SGIVXYTypeData )
		{
			groupSet = new ElementGroupSetInVXYGraph();
		}

		return groupSet;
	}



	/**
	 * 
	 */
	public boolean createDataObject( final Element el, final SGData data )
	{
		if( !( data instanceof SGIVXYTypeData ) )
		{
			return false;
		}

		if( super.createDataObject(el,data) == false )
		{
			return false;
		}

		// construct a SGElementGroupSet object
		ElementGroupSetInGraph groupSet = this.getGroupSetNewInstance(data);
		if( groupSet==null )
		{
			return false;
		}

		// add to the child list
		this.addToList( groupSet );

		//
		int ret = this.setDataProperties( el, groupSet, data );
		if( ret==SGIConstants.PROPERTY_FILE_INCORRECT )
		{
			return false;
		}

		this.createDataDialog();
		
		return true;
	}


	
	/**
	 * 
	 */
	protected int setDataProperties(
		final Element el, final ElementGroupSetInGraph groupSet, final SGData data )
	{
		final int ic = SGIConstants.PROPERTY_FILE_INCORRECT;
		if( super.setDataProperties( el, groupSet, data ) == ic )
		{
			return ic;
		}

		if( data instanceof SGIVXYTypeData )
		{
			SGIVXYTypeData data_ = (SGIVXYTypeData)data;
			ElementGroupSetInVXYGraph gs = (ElementGroupSetInVXYGraph)groupSet;

			if( this.setPropertyOfElementGroupSetInVXYGraph(el,gs,data_) == ic )
			{
				return ic;
			}
		}

		return SGIConstants.SUCCESSFUL_COMPLETION;
	}

	
	
	/**
	 * 
	 */
	private int setPropertyOfElementGroupSetInVXYGraph(
		final Element el,
		final ElementGroupSetInVXYGraph groupSet,
		final SGIVXYTypeData data )
	{

		final int ic = SGIConstants.PROPERTY_FILE_INCORRECT;
		SGIAxisElement aElement = this.mAxisElement;
		String str = null;
		Number num = null;
		Boolean b = null;

		//
		// create drawing element groups
		//
		
		SGElementGroup group = null;
		NodeList nList = null;

		// create instances of the points
		final int pointsNum = data.getPointsNumber();
		groupSet.initPointsArray( pointsNum );

		// arrow
		nList = el.getElementsByTagName( SGElementGroupArrow.TAG_NAME_ARROW );
		if( nList.getLength()!=1 )
		{
			return ic;
		}
		if( groupSet.addDrawingElementGroup( SGElementGroup.ARROW_GROUP ) == false )
		{
			return ic;
		}
		Element arrow = (Element)nList.item(0);
		group = groupSet.getArrowGroup();
		if( group.readProperty(arrow) == false )
		{
			return ic;
		}

		// magnitude
		str = el.getAttribute( SGIGraphElement.KEY_MAGNITUDE_PER_CM );
		if( str.length()!=0 )
		{
			num = SGUtilityText.getFloat(str);
			if( num==null )
			{
				return ic;
			}
			groupSet.setMagnitudePerCM( num.floatValue() );
		}

		// direction invariance
		str = el.getAttribute( SGIGraphElement.KEY_DIRECTION_INVARIANT );
		if( str.length()!=0 )
		{
			b = SGUtilityText.getBoolean(str);
			if( b==null )
			{
				return ic;
			}
			groupSet.setDirectionInvariant( b.booleanValue() );
		}

		groupSet.updateDrawingElementsLocation( data );

		//
		groupSet.initPropertiesHistory();
		
		return SGIConstants.SUCCESSFUL_COMPLETION;
	}


	//
	private ElementGroupSetInVXYGraph getVXYGroupSet( final int id )
	{
		return (ElementGroupSetInVXYGraph)this.getGroupSet(id);
	}

	public boolean setMagnitudePerCMDirectly( final int id, final float value )
	{
		ElementGroupSetInVXYGraph arrow = this.getVXYGroupSet(id);
		if( arrow==null ) return false;
		if( this.setDirectlyBefore(arrow) == false ) return false;
		if( arrow.setMagnitudePerCM( value ) == false ) return false;
		if( this.setDirectlyAfter(arrow) == false ) return false;
		return true;
	}

	public boolean setDirectionInvariantDirectly( final int id, final boolean value )
	{
		ElementGroupSetInVXYGraph arrow = this.getVXYGroupSet(id);
		if( arrow==null ) return false;
		if( this.setDirectlyBefore(arrow) == false ) return false;
		if( arrow.setDirectionInvariant(value) == false ) return false;
		if( this.setDirectlyAfter(arrow) == false ) return false;
		return true;
	}

	public boolean setLineWidthDirectly( final int id, final float value, final String unit )
	{
		ElementGroupSetInVXYGraph arrow = this.getVXYGroupSet(id);
		if( arrow==null ) return false;
		if( this.setDirectlyBefore(arrow) == false ) return false;
		if( arrow.setLineWidth( value, unit ) == false ) return false;
		if( this.setDirectlyAfter(arrow) == false ) return false;
		return true;
	}

	public boolean setLineTypeDirectly( final int id, final int value )
	{
		ElementGroupSetInVXYGraph arrow = this.getVXYGroupSet(id);
		if( arrow==null ) return false;
		if( this.setDirectlyBefore(arrow) == false ) return false;
		if( arrow.setLineType( value ) == false ) return false;
		if( this.setDirectlyAfter(arrow) == false ) return false;
		return true;
	}

	public boolean setHeadSizeDirectly( final int id, final float value, final String unit )
	{
		ElementGroupSetInVXYGraph arrow = this.getVXYGroupSet(id);
		if( arrow==null ) return false;
		if( this.setDirectlyBefore(arrow) == false ) return false;
		if( arrow.setHeadSize( value, unit ) == false ) return false;
		if( this.setDirectlyAfter(arrow) == false ) return false;
		return true;
	}

	public boolean setColorDirectly( final int id, final Color value )
	{
		ElementGroupSetInVXYGraph arrow = this.getVXYGroupSet(id);
		if( arrow==null ) return false;
		if( this.setDirectlyBefore(arrow) == false ) return false;
		if( arrow.setColor( value ) == false ) return false;
		if( this.setDirectlyAfter(arrow) == false ) return false;
		return true;
	}

	public boolean setStartTypeDirectly( final int id, final int value )
	{
		ElementGroupSetInVXYGraph arrow = this.getVXYGroupSet(id);
		if( arrow==null ) return false;
		if( this.setDirectlyBefore(arrow) == false ) return false;
		if( arrow.setStartHeadType( value ) == false ) return false;
		if( this.setDirectlyAfter(arrow) == false ) return false;
		return true;
	}

	public boolean setEndTypeDirectly( final int id, final int value )
	{
		ElementGroupSetInVXYGraph arrow = this.getVXYGroupSet(id);
		if( arrow==null ) return false;
		if( this.setDirectlyBefore(arrow) == false ) return false;
		if( arrow.setEndHeadType( value ) == false ) return false;
		if( this.setDirectlyAfter(arrow) == false ) return false;
		return true;
	}

	public boolean setOpenAngleDirectly( final int id, final float value )
	{
		ElementGroupSetInVXYGraph arrow = this.getVXYGroupSet(id);
		if( arrow==null ) return false;
		if( this.setDirectlyBefore(arrow) == false ) return false;
		if( arrow.setHeadOpenAngle( value ) == false ) return false;
		if( this.setDirectlyAfter(arrow) == false ) return false;
		return true;
	}

	public boolean setCloseAngleDirectly( final int id, final float value )
	{
		ElementGroupSetInVXYGraph arrow = this.getVXYGroupSet(id);
		if( arrow==null ) return false;
		if( this.setDirectlyBefore(arrow) == false ) return false;
		if( arrow.setHeadCloseAngle( value ) == false ) return false;
		if( this.setDirectlyAfter(arrow) == false ) return false;
		return true;
	}


	/**
	 * 
	 */
	class ElementGroupSetInVXYGraph extends ElementGroupSetInGraph
		implements SGIVXYDataDialogObserver
	{

		// term points of arrows
		private SGTuple2f[] mStartPointsArray = null;
		private SGTuple2f[] mEndPointsArray = null;

		// initialize the points
		private void initPointsArray( final int num )
		{
			SGTuple2f[] sArray = new SGTuple2f[num];
			SGTuple2f[] eArray = new SGTuple2f[num];
			for( int ii=0; ii<num; ii++ )
			{
				sArray[ii] = new SGTuple2f();
				eArray[ii] = new SGTuple2f();
			}
			this.mStartPointsArray = sArray;
			this.mEndPointsArray = eArray;
		}


		// scaling factor for the magnitude of vectors
		private float mMagnitudePerCM = 1.0f;


		/**
		 * 
		 * @return
		 */
		public float getMagnitudePerCM()
		{
			return this.mMagnitudePerCM;
		}


		/**
		 * 
		 * @param ratio
		 * @return
		 */
		public boolean setMagnitudePerCM( final float factor )
		{
			this.mMagnitudePerCM = factor;
			this.updateDrawingElementsLocation( getData(this) );

			return true;
		}


		/**
		 * 
		 */
		protected ElementGroupSetInVXYGraph()
		{
			super();
			this.mPopupMenu = createGroupSetPopupMenu(this);
		}

		
		/**
		 * 
		 * @return
		 */
		public String getClassDescription()
		{
			return "";
		}


		/**
		 * 
		 */
		protected boolean createDrawingElements()
		{
			return true;
		}


		/**
		 * 
		 */
		public boolean addDrawingElementGroup( final int type )
		{
			SGElementGroupVXY group = null;
			if( type == SGElementGroup.ARROW_GROUP )
			{
				group = new ElementGroupArrow();
			}
			else
			{
				return false;
			}

			// add a group
			this.addElementGroup(group);

			return true;
		}


		/**
		 * 
		 */
		private boolean addElementGroup( final SGElementGroupVXY group )
		{
			// set the group set
			IElementGroupInGraph iGroup = (IElementGroupInGraph)group;
			iGroup.setElementGroupSet(this);

			// initialize the drawing element
			if( group.initDrawingElement( this.mStartPointsArray, this.mEndPointsArray ) == false )
			{
				throw new Error();
			}

			// set the properties to drawing elements
			if( group.setPropertiesOfDrawingElements() == false )
			{
				return false;
			}

			// add the new group to the list
			this.mDrawingElementGroupList.add( group );

			return true;
		}


		// a flag whether to fix the direction of each vector
		private boolean mDirectionFixedFlag;

		public boolean isDirectionInvariant()
		{
			return this.mDirectionFixedFlag;
		}

		public boolean setDirectionInvariant( final boolean b )
		{
			this.mDirectionFixedFlag = b;
			return true;
		}


		/**
		 * Called when the location of data points have changed.
		 */
		public boolean updateDrawingElementsLocation( final SGIData data )
		{
			if( ( data instanceof SGIVXYTypeData ) == false )
			{
				return false;
			}

			SGIVXYTypeData dataVXY = (SGIVXYTypeData)data;
			final int num = dataVXY.getPointsNumber();

			SGAxis xAxis = this.getXAxis();
			SGAxis yAxis = this.getYAxis();

			// set the location of the start points
			if( SGVXYGraphElement.this.calcLocationOfPoints(
				dataVXY.getXCoordinateArray(),
				dataVXY.getYCoordinateArray(),
				xAxis,
				yAxis,
				this.mStartPointsArray ) == false )
			{
				return false;
			}


			// calculate the location of the end points and set to the groups

			final double[] xArray = dataVXY.getXComponentArray();
			final double[] yArray = dataVXY.getYComponentArray();
			final double[] magArray = dataVXY.getMagnitudeArray();
			final float mag = this.getMagnification();
			SGTuple2f[] startArray = this.mStartPointsArray;
			SGTuple2f[] endArray = this.mEndPointsArray;
			final float ratio = 1.0f/(SGIConstants.CM_POINT_RATIO*this.getMagnitudePerCM());

			final double xRange = xAxis.getMaxValue() - xAxis.getMinValue();
			final double yRange = yAxis.getMaxValue() - yAxis.getMinValue();
			final double xyRatio = (mGraphRectHeight/yRange)/(mGraphRectWidth/xRange);
//System.out.println("xyRatio="+xyRatio);
			ArrayList gList = this.mDrawingElementGroupList;
			for( int ii=0; ii<gList.size(); ii++ )
			{
				SGElementGroup group = (SGElementGroup)gList.get(ii);
				if( group.isVisible() )
				{
					if( group instanceof ElementGroupArrow )
					{
						ElementGroupArrow vGroup = (ElementGroupArrow)group;
						final float factor = mag*ratio;

						if( this.isDirectionInvariant() )
						{
							for( int jj=0; jj<startArray.length; jj++ )
							{
								final float endX = startArray[jj].x + factor*(float)(xArray[jj]);
								final float endY = startArray[jj].y - factor*(float)(yArray[jj]);
								endArray[jj].setValues( endX, endY );
							}
						}
						else
						{
							for( int jj=0; jj<startArray.length; jj++ )
							{
								final double tanValue = xyRatio*yArray[jj]/xArray[jj];
								double angle = Math.atan(tanValue);
								if( xArray[jj]<0.0 )
								{
									angle += Math.PI;
								}
								final double cosValue = Math.cos(angle);
								final double sinValue = Math.sin(angle);
								final float vx = factor*(float)(magArray[jj]*cosValue);
								final float vy = - factor*(float)(magArray[jj]*sinValue);
								final float endX = startArray[jj].x + vx;
								final float endY = startArray[jj].y + vy;
								endArray[jj].setValues( endX, endY );
							}
						}

						vGroup.setLocation( startArray, endArray );
					}
				}
			}

			return true;
		}


		/**
		 * 
		 */
		public boolean synchronizeDrawingElement( ArrayList sList )
		{
			ArrayList groupList = new ArrayList();
			for( int ii=0; ii<sList.size(); ii++ )
			{
				SGDrawingElement el = (SGDrawingElement)sList.get(ii);

				SGElementGroup group = null;
				if( el instanceof SGDrawingElementArrow )
				{
					group = this.getArrowGroup();
				}

				if( group==null )
				{
					continue;
				}

				groupList.add(group);
			}


			boolean diffFlag = false;
			for( int ii=0; ii<sList.size(); ii++ )
			{
				SGDrawingElement el = (SGDrawingElement)sList.get(ii);
				SGElementGroup group = (SGElementGroup)groupList.get(ii);
				SGDrawingElement elOld = group.getDrawingElement();
				SGProperties p = el.getProperties();
				SGProperties pOld = elOld.getProperties();
				if( p.equals(pOld) == false )
				{
					diffFlag = true;
				}
				group.setProperty(el);
			}

			return diffFlag;
		}


		/**
		 * 
		 */
		public void paintGraphics2D(
			final Graphics2D g2d )
		{
			Rectangle2D gRect = null;
			if( !this.getClipFlag() )
			{
				gRect = SGVXYGraphElement.this.getGraphRect();
			}

			ArrayList list = this.mDrawingElementGroupList;
			for( int ii=0; ii<list.size(); ii++ )
			{
				final ElementGroupArrow group = (ElementGroupArrow)list.get(ii);
				if( group.isVisible() )
				{
					group.paintElement( g2d, gRect );
				}
			}

		}



		/**
		 * 
		 */
		public boolean getLegendVisibleFlag()
		{
			return this.isVisibleInLegend();
		}


		/**
		 * 
		 */
		public int getXAxisLocation()
		{
			return mAxisElement.getLocationInPlane( this.getXAxis() );
		}


		/**
		 * 
		 */
		public int getYAxisLocation()
		{
			return mAxisElement.getLocationInPlane( this.getYAxis() );
		}


		/**
		 * 
		 */
		public boolean prepare()
		{
			this.mTemporaryProperties = this.getWholeProperties();
			return true;
		}


		/**
		 * Returns a property dialog.
		 * @return property dialog
		 */
		public SGPropertyDialog getPropertyDialog()
		{
			return SGVXYGraphElement.this.mDialog;
		}


		/**
		 * 
		 */
		public boolean writeProperty( final Element el )
		{
			if( super.writeProperty(el) == false )
			{
				return false;
			}

			final String strX = mAxisElement.getLocationName( this.getXAxis() );
			final String strY = mAxisElement.getLocationName( this.getYAxis() );

			el.setAttribute( KEY_X_AXIS_POSITION, strX );
			el.setAttribute( KEY_Y_AXIS_POSITION, strY );

			// write polar flag
			SGVXYData data = (SGVXYData)getData( this );
			final boolean polar = data.isPolar();
			el.setAttribute( KEY_POLAR, Boolean.toString(polar) );

			// write magnification
			final float factor = this.getMagnitudePerCM();
			el.setAttribute( KEY_MAGNITUDE_PER_CM, Float.toString(factor) );

			final boolean invariant = this.isDirectionInvariant();
			el.setAttribute( KEY_DIRECTION_INVARIANT, Boolean.toString(invariant) );

			return true;
		}


		/**
		 * 
		 */
		public SGProperties getProperties()
		{
			ElementGroupSetInVXYGraphProperties ep
				= new ElementGroupSetInVXYGraphProperties();
			if( this.getProperties(ep) == false )
			{
				return null;
			}

			return ep;
		}


		/**
		 * 
		 */
		public boolean getProperties( final SGProperties p )
		{
			if( ( p instanceof ElementGroupSetInVXYGraphProperties ) == false ) return false;

			if( super.getProperties(p) == false ) return false;

			ElementGroupSetInVXYGraphProperties ep = (ElementGroupSetInVXYGraphProperties)p;

			ep.mMagnitudeScalingFactor = this.getMagnitudePerCM();
			ep.mDirectionInvariant = this.isDirectionInvariant();

			return true;
		}


		/**
		 * 
		 */
		public boolean setProperties( final SGProperties p )
		{
			if( ( p instanceof ElementGroupSetInVXYGraphProperties ) == false ) return false;

			if( super.setProperties(p) == false ) return false;

			ElementGroupSetInVXYGraphProperties ep = (ElementGroupSetInVXYGraphProperties)p;

			this.setMagnitudePerCM( ep.mMagnitudeScalingFactor );
			this.setDirectionInvariant( ep.mDirectionInvariant );

			return true;
		}


		public float getLineWidth( final String unit )
		{
			return this.getArrowGroup().getLineWidth( unit );
		}
	
		public int getLineType()
		{
			return this.getArrowGroup().getLineType();
		}
	
		public Color getColor()
		{
			return this.getArrowGroup().getColor();
		}

		public float getHeadSize( final String unit )
		{
			return this.getArrowGroup().getHeadSize( unit );
		}

		public float getHeadOpenAngle()
		{
			return this.getArrowGroup().getHeadOpenAngle();
		}
	
		public float getHeadCloseAngle()
		{
			return this.getArrowGroup().getHeadCloseAngle();
		}

		public int getStartHeadType()
		{
			return this.getArrowGroup().getStartHeadType();
		}
	
		public int getEndHeadType()
		{
			return this.getArrowGroup().getEndHeadType();
		}


		public boolean setLineWidth( final float width, final String unit )
		{
			return this.getArrowGroup().setLineWidth( width, unit );
		}
	
		public boolean setLineType( final int type )
		{
			return this.getArrowGroup().setLineType( type );
		}
	
		public boolean setColor( final Color cl )
		{
			return this.getArrowGroup().setColor( cl );
		}

		public boolean setHeadSize( final float size, final String unit )
		{
			return this.getArrowGroup().setHeadSize( size, unit );
		}
	
		public boolean setHeadOpenAngle( final float angle )
		{
			return this.getArrowGroup().setHeadOpenAngle( angle );
		}
	
		public boolean setHeadCloseAngle( final float angle )
		{
			return this.getArrowGroup().setHeadCloseAngle( angle );
		}
	
		public boolean setStartHeadType( final int type )
		{
			return this.getArrowGroup().setStartHeadType( type );
		}

		public boolean setEndHeadType( final int type )
		{
			return this.getArrowGroup().setEndHeadType( type );
		}


		public boolean hasValidAngle( final Number open, final Number close )
		{
			final float openAngle = (open!=null) ? open.floatValue() : this.getHeadOpenAngle();
			final float closeAngle = (close!=null) ? close.floatValue() : this.getHeadCloseAngle();
			return ( openAngle < closeAngle );
		}

	}


	/**
	 *
	 */
	class ElementGroupArrow extends SGElementGroupArrow
		implements IElementGroupInGraph, SGIVXYDataConstants
	{

		protected ElementGroupSetInGraph mGroupSet = null;
		public boolean setElementGroupSet( ElementGroupSetInGraph gs )
		{
			this.mGroupSet = gs;
			return true;
		}

		private boolean mFocusedFlag = false;


		public void setFocused( final boolean b )
		{
			this.mFocusedFlag = b;
		}

		public boolean isFocused()
		{
			return this.mFocusedFlag;
		}

		/**
		 *
		 */
		protected ElementGroupArrow()
		{
			super();
			this.init();
		}

		/**
		 * 
		 */
		private boolean init()
		{
			this.setLineWidth( DEFAULT_LINE_WIDTH, pt );
			this.setLineType( DEFAULT_LINE_TYPE );
			this.setColor( DEFAULT_COLOR );
			this.setStartHeadType( DEFAULT_START_HEAD_TYPE );
			this.setEndHeadType( DEFAULT_END_HEAD_TYPE );
			this.setHeadSize( DEFAULT_HEAD_SIZE, cm );
			this.setHeadOpenAngle( DEFAULT_HEAD_OPEN_ANGLE );
			this.setHeadCloseAngle( DEFAULT_HEAD_CLOSE_ANGLE );

			return true;
		}


		/**
		 * 
		 */
		public boolean paintElement( final Graphics2D g2d, final Rectangle2D rect )
		{
			super.paintElement(g2d,rect);

			ElementGroupSetInGraph gs = (ElementGroupSetInGraph)this.mGroupSet;
			SGDrawingElement[] array = this.mDrawingElementArray;

			if( this.isFocused() & mSymbolsVisibleFlagAroundFocusedObjects )
			{
//				final int num = array.length;
//				if( num <= MAX_NUMBER_OF_ANCHORS )
//				{
//					for( int ii=0; ii<num; ii++ )
//					{
//						SGDrawingElementArrow2D arrow
//							= (SGDrawingElementArrow2D)array[ii];
//						emphasisArrow( arrow, g2d );
//					}
//				}
//				else
//				{
//					int div = num/MAX_NUMBER_OF_ANCHORS;
//					int cnt = 0;
//					while( true )
//					{
//						SGDrawingElementArrow2D arrow
//							= (SGDrawingElementArrow2D)array[cnt];
//						emphasisArrow( arrow, g2d );
//						cnt += div;
//						if( cnt >= num )
//						{
//							break;
//						}
//					}
//				}

				for( int ii=0; ii<array.length; ii++ )
				{
					SGDrawingElementArrow2D arrow
						= (SGDrawingElementArrow2D)array[ii];
					emphasisArrow( arrow, g2d );
				}
				
			}


			return true;
		}


		/**
		 * 
		 * @param g2d
		 * @param symbol
		 * @return
		 */
		private boolean emphasisArrow(
			final SGDrawingElementArrow2D arrow,
			final Graphics2D g2d )
		{
			SGTuple2f start = arrow.getStart();
			SGTuple2f end = arrow.getEnd();
			drawAnchorsForFocusedObjects( new Point2D.Float( start.x, start.y ), g2d );
			drawAnchorsForFocusedObjects( new Point2D.Float( end.x, end.y ), g2d );
			return true;
		}

	}


}

