1 //-------------------------------------------------------------
2 // <copyright company=�Microsoft Corporation�>
3 //   Copyright � Microsoft Corporation. All Rights Reserved.
4 // </copyright>
5 //-------------------------------------------------------------
6 // @owner=alexgor, deliant
7 //=================================================================
8 //  File:		ChartArea3D.cs
9 //
10 //  Namespace:	System.Web.UI.WebControls[Windows.Forms].Charting
11 //
12 //	Classes:	ChartArea3DStyle, ChartArea3D
13 //
14 //  Purpose:	ChartArea3D class represents 3D chart area. It contains
15 //              methods for coordinates transformation, drawing the 3D
16 //              scene and many 3D related helper methods.
17 //
18 //	Reviewed:	AG - Microsoft 16, 2007
19 //
20 //===================================================================
21 
22 #region Used namespaces
23 using System;
24 using System.Drawing;
25 using System.Drawing.Drawing2D;
26 using System.ComponentModel;
27 using System.ComponentModel.Design;
28 using System.Collections;
29 using System.Globalization;
30 using System.Collections.Generic;
31 
32 #if WINFORMS_CONTROL
33 	using System.Windows.Forms.DataVisualization.Charting;
34 	using System.Windows.Forms.DataVisualization.Charting.Data;
35 	using System.Windows.Forms.DataVisualization.Charting.ChartTypes;
36 	using System.Windows.Forms.DataVisualization.Charting.Utilities;
37 	using System.Windows.Forms.DataVisualization.Charting.Borders3D;
38 #else
39 	using System.Web.UI.DataVisualization.Charting;
40 	using System.Web.UI.DataVisualization.Charting.ChartTypes;
41     using System.Web.UI.DataVisualization.Charting.Utilities;
42 	using System.Web.UI;
43 #endif
44 
45 
46 #endregion
47 
48 #if WINFORMS_CONTROL
49 	namespace System.Windows.Forms.DataVisualization.Charting
50 #else
51 namespace System.Web.UI.DataVisualization.Charting
52 
53 #endif
54 {
55 	#region 3D lightStyle style enumerations
56 
57 		/// <summary>
58 		/// A lighting style for a 3D chart area.
59 		/// </summary>
60 		public enum LightStyle
61 		{
62 			/// <summary>
63 			/// No lighting.
64 			/// </summary>
65 			None,
66             /// <summary>
67             /// Simplistic lighting.
68             /// </summary>
69 			Simplistic,
70             /// <summary>
71             /// Realistic lighting.
72             /// </summary>
73 			Realistic
74 		}
75 
76 	#endregion
77 
78 	#region 3D Center of Projetion coordinates enumeration
79 
80 		/// <summary>
81 		/// Coordinates of the Center Of Projection
82 		/// </summary>
83 		[Flags]
84 		internal enum COPCoordinates
85 		{
86 			/// <summary>
87 			/// Check X coordinate.
88 			/// </summary>
89 			X = 1,
90 			/// <summary>
91 			/// Check Y coordinate.
92 			/// </summary>
93 			Y = 2,
94 			/// <summary>
95 			/// Check Z coordinate.
96 			/// </summary>
97 			Z = 4
98 		}
99 
100 	#endregion
101 
102         /// <summary>
103         /// The ChartArea3DStyleClass class provides the functionality for 3D attributes of chart areas,
104         /// such as rotation angles and perspective.
105         /// </summary>
106 #if ASPPERM_35
107 	[AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
108     [AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
109 #endif
110     public class ChartArea3DStyle
111 	{
112 		#region Constructor and Initialization
113 
114 		/// <summary>
115         /// ChartArea3DStyle constructor.
116 		/// </summary>
ChartArea3DStyle()117 		public ChartArea3DStyle()
118 		{
119 		}
120 
121 		/// <summary>
122         /// ChartArea3DStyle constructor.
123 		/// </summary>
ChartArea3DStyle(ChartArea chartArea)124 		public ChartArea3DStyle(ChartArea chartArea)
125 		{
126             this._chartArea = chartArea;
127 		}
128 
129         /// <summary>
130         /// Initialize Chart area and axes
131         /// </summary>
132         /// <param name="chartArea">Chart area object.</param>
Initialize(ChartArea chartArea)133         internal void Initialize(ChartArea chartArea)
134         {
135             this._chartArea = chartArea;
136         }
137 
138         #endregion
139 
140 		#region Fields
141 
142 		// Reference to the chart area object
143 		private	ChartArea	_chartArea = null;
144 
145 		// Enables/disables 3D chart types in the area.
146 		private	bool		_enable3D	= false;
147 
148 		// Indicates that axes are set at the right angle independent of the rotation.
149 		private	bool		_isRightAngleAxes	= true;
150 
151 		// Indicates that series should be drawn as isClustered.
152 		private	bool		_isClustered	= false;
153 
154 		// 3D area lightStyle style.
155 		private LightStyle	_lightStyle = LightStyle.Simplistic;
156 
157 		// 3D area perspective which controls the scaleView of the chart depth.
158 		private int			_perspective = 0;
159 
160 		// Chart area rotation angle around the X axis.
161 		private int			_inclination = 30;
162 
163 		// Chart area rotation angle around the Y axis.
164 		private int			_rotation = 30;
165 
166 		// Chart area walls width.
167 		private int			_wallWidth = 7;
168 
169 		// Series points depth in percentages
170 		private int			_pointDepth = 100;
171 
172 		// Series points gap depth in percentages
173 		private int			_pointGapDepth = 100;
174 
175 		#endregion
176 
177 		#region Properties
178 
179         /// <summary>
180         /// Gets or sets a Boolean value that toggles 3D for a chart area on and off.
181         /// </summary>
182 		[
183 		SRCategory("CategoryAttribute3D"),
184 		Bindable(true),
185 		DefaultValue(false),
186 		SRDescription("DescriptionAttributeChartArea3DStyle_Enable3D"),
187 		ParenthesizePropertyNameAttribute(true)
188 		]
189 		public bool Enable3D
190 		{
191 			get
192 			{
193                 return this._enable3D;
194 			}
195 			set
196 			{
197                 if (this._enable3D != value)
198 				{
199                     this._enable3D = value;
200 
201                     if (this._chartArea != null)
202 					{
203 #if SUBAXES
204 						// If one of the axes has sub axis the scales has to be recalculated
205 						foreach(Axis axis in this._chartArea.Axes)
206 						{
207 							if(axis.SubAxes.Count > 0)
208 							{
209 								this._chartArea.ResetAutoValues();
210 								break;
211 							}
212 						}
213 #endif // SUBAXES
214 
215                         this._chartArea.Invalidate();
216 					}
217 				}
218 			}
219 		}
220 
221 
222         /// <summary>
223         /// Gets or sets a Boolean that determines if a chart area  is displayed using an isometric projection.
224         /// </summary>
225 		[
226         SRCategory("CategoryAttribute3D"),
227 		Bindable(true),
228 		DefaultValue(true),
229 		SRDescription("DescriptionAttributeChartArea3DStyle_RightAngleAxes"),
230 		RefreshPropertiesAttribute(RefreshProperties.All)
231 		]
232 		public bool IsRightAngleAxes
233 		{
234 			get
235 			{
236                 return _isRightAngleAxes;
237 			}
238 			set
239 			{
240                 _isRightAngleAxes = value;
241 
242 				// Adjust 3D properties values
243                 if (_isRightAngleAxes)
244 				{
245 					// Disable perspective if right angle axis are used
246                     this._perspective = 0;
247 				}
248 
249                 if (this._chartArea != null)
250 				{
251                     this._chartArea.Invalidate();
252 				}
253 			}
254 		}
255 
256 
257         /// <summary>
258         /// Gets or sets a Boolean value that determines if bar chart or column
259         /// chart data series are clustered (displayed along distinct rows).
260         /// </summary>
261 		[
262         SRCategory("CategoryAttribute3D"),
263 		Bindable(true),
264 		DefaultValue(false),
265 		SRDescription("DescriptionAttributeChartArea3DStyle_Clustered"),
266 		]
267 		public bool IsClustered
268 		{
269 			get
270 			{
271                 return _isClustered;
272 			}
273 			set
274 			{
275                 _isClustered = value;
276                 if (this._chartArea != null)
277 				{
278                     this._chartArea.Invalidate();
279 				}
280 			}
281 		}
282 
283 		/// <summary>
284         /// Gets or sets the style of lighting for a 3D chart area.
285 		/// </summary>
286 		[
287         SRCategory("CategoryAttribute3D"),
288 		Bindable(true),
289 		DefaultValue(typeof(LightStyle), "Simplistic"),
290 		SRDescription("DescriptionAttributeChartArea3DStyle_Light"),
291 		]
292 		public LightStyle LightStyle
293 		{
294 			get
295 			{
296                 return _lightStyle;
297 			}
298 			set
299 			{
300                 _lightStyle = value;
301                 if (this._chartArea != null)
302 				{
303                     this._chartArea.Invalidate();
304 				}
305 			}
306 		}
307 
308 		/// <summary>
309         /// Gets or sets the percent of perspective for a 3D chart area.
310 		/// </summary>
311 		[
312 		SRCategory("CategoryAttribute3D"),
313 		Bindable(true),
314 		DefaultValue(0),
315 		SRDescription("DescriptionAttributeChartArea3DStyle_Perspective"),
316 		RefreshPropertiesAttribute(RefreshProperties.All)
317 		]
318 		public int Perspective
319 		{
320 			get
321 			{
322                 return _perspective;
323 			}
324 			set
325 			{
326 				if(value < 0 || value > 100)
327 				{
328                     throw (new ArgumentOutOfRangeException("value", SR.ExceptionChartArea3DPerspectiveInvalid));
329 				}
330 
331                 _perspective = value;
332 
333 				// Adjust 3D properties values
334                 if (_perspective != 0)
335 				{
336 					// Disable right angle axes
337                     this._isRightAngleAxes = false;
338 				}
339 
340                 if (this._chartArea != null)
341 				{
342                     this._chartArea.Invalidate();
343 				}
344 			}
345 		}
346 
347 		/// <summary>
348         /// Gets or sets the inclination for a 3D chart area.
349 		/// </summary>
350 		[
351 		SRCategory("CategoryAttribute3D"),
352 		Bindable(true),
353 		DefaultValue(30),
354 		SRDescription("DescriptionAttributeChartArea3DStyle_Inclination"),
355 		RefreshPropertiesAttribute(RefreshProperties.All)
356 		]
357 		public int Inclination
358 		{
359 			get
360 			{
361                 return _inclination;
362 			}
363 			set
364 			{
365 				if(value < -90 || value > 90)
366 				{
367                     throw (new ArgumentOutOfRangeException("value", SR.ExceptionChartArea3DInclinationInvalid));
368 				}
369                 _inclination = value;
370 
371                 if (this._chartArea != null)
372 				{
373                     this._chartArea.Invalidate();
374 				}
375 			}
376 		}
377 
378 		/// <summary>
379         /// Gets or sets the rotation angle for a 3D chart area.
380 		/// </summary>
381 		[
382 		SRCategory("CategoryAttribute3D"),
383 		Bindable(true),
384 		DefaultValue(30),
385 		SRDescription("DescriptionAttributeChartArea3DStyle_Rotation"),
386 		RefreshPropertiesAttribute(RefreshProperties.All)
387 		]
388 		public int Rotation
389 		{
390 			get
391 			{
392                 return _rotation;
393 			}
394 			set
395 			{
396 				if(value < -180 || value > 180)
397 				{
398                     throw (new ArgumentOutOfRangeException("value", SR.ExceptionChartArea3DRotationInvalid));
399 				}
400                 _rotation = value;
401 
402                 if (this._chartArea != null)
403 				{
404                     this._chartArea.Invalidate();
405 				}
406 			}
407 		}
408 
409 		/// <summary>
410         /// Gets or sets the width of the walls displayed in 3D chart areas.
411 		/// </summary>
412 		[
413 		SRCategory("CategoryAttribute3D"),
414 		Bindable(true),
415 		DefaultValue(7),
416 		SRDescription("DescriptionAttributeChartArea3DStyle_WallWidth"),
417 		RefreshPropertiesAttribute(RefreshProperties.All)
418 		]
419 		public int WallWidth
420 		{
421 			get
422 			{
423                 return _wallWidth;
424 			}
425 			set
426 			{
427 				if(value < 0 || value > 30)
428 				{
429                     throw (new ArgumentOutOfRangeException("value", SR.ExceptionChartArea3DWallWidthInvalid));
430 				}
431 
432                 _wallWidth = value;
433                 if (this._chartArea != null)
434 				{
435                     this._chartArea.Invalidate();
436 				}
437 			}
438 		}
439 
440 		/// <summary>
441         /// Gets or sets the depth of data points displayed in 3D chart areas (0-1000%).
442 		/// </summary>
443 		[
444 		SRCategory("CategoryAttribute3D"),
445 		Bindable(true),
446 		DefaultValue(100),
447 		SRDescription("DescriptionAttributeChartArea3DStyle_PointDepth"),
448 		RefreshPropertiesAttribute(RefreshProperties.All)
449 		]
450 		public int PointDepth
451 		{
452 			get
453 			{
454                 return _pointDepth;
455 			}
456 			set
457 			{
458 				if(value < 0 || value > 1000)
459 				{
460                     throw (new ArgumentOutOfRangeException("value", SR.ExceptionChartArea3DPointsDepthInvalid));
461 				}
462 
463                 _pointDepth = value;
464                 if (this._chartArea != null)
465 				{
466                     this._chartArea.Invalidate();
467 				}
468 			}
469 		}
470 
471 		/// <summary>
472         /// Gets or sets the distance between series rows in 3D chart areas (0-1000%).
473 		/// </summary>
474 		[
475 		SRCategory("CategoryAttribute3D"),
476 		Bindable(true),
477 		DefaultValue(100),
478 		SRDescription("DescriptionAttributeChartArea3DStyle_PointGapDepth"),
479 		RefreshPropertiesAttribute(RefreshProperties.All)
480 		]
481 		public int PointGapDepth
482 		{
483 			get
484 			{
485                 return _pointGapDepth;
486 			}
487 			set
488 			{
489 				if(value < 0 || value > 1000)
490 				{
491                     throw (new ArgumentOutOfRangeException("value", SR.ExceptionChartArea3DPointsGapInvalid));
492 				}
493 
494                 _pointGapDepth = value;
495                 if (this._chartArea != null)
496 				{
497                     this._chartArea.Invalidate();
498 				}
499 			}
500 		}
501 
502 		#endregion
503 	}
504 
505 	/// <summary>
506     /// ChartArea3D class represents 3D chart area. It contains all the 3D
507     /// scene settings and methods for drawing the 3D plotting area, and calculating
508     /// the depth of chart elements.
509 	/// </summary>
510 	public partial class ChartArea
511 	{
512 		#region Fields
513 
514 		// Chart area 3D style attribuytes
515 		private		ChartArea3DStyle	_area3DStyle = new ChartArea3DStyle();
516 
517 		// Coordinate convertion matrix
518 		internal	Matrix3D			matrix3D = new Matrix3D();
519 
520 		// Chart area scene wall width in relative coordinates
521 		internal	SizeF				areaSceneWallWidth = SizeF.Empty;
522 
523 		// Chart area scene depth
524 		internal	float				areaSceneDepth = 0;
525 
526 		// Visible surfaces in plotting area
527 		private		SurfaceNames			_visibleSurfaces;
528 
529 		// Z axis depth of series points
530 		private		double				_pointsDepth = 0;
531 
532 		// Z axis depth of the gap between isClustered series
533 		private		double				_pointsGapDepth = 0;
534 
535 		/// <summary>
536 		/// Indicates that series order should be reversed to simulate Y axis rotation.
537 		/// </summary>
538 		private	bool				_reverseSeriesOrder = false;
539 
540 		/// <summary>
541 		/// Old X axis reversed flag
542 		/// </summary>
543 		internal	bool				oldReverseX = false;
544 
545 		/// <summary>
546 		/// Old Y axis reversed flag
547 		/// </summary>
548 		internal	bool				oldReverseY = false;
549 
550 		/// <summary>
551 		/// Old Y axis rotation angle
552 		/// </summary>
553 		internal	int					oldYAngle = 30;
554 
555 		/// <summary>
556 		/// List of all stack group names
557 		/// </summary>
558 		private	ArrayList			_stackGroupNames = null;
559 
560         /// <summary>
561         /// This list contains an array of series names for each 3D cluster
562         /// </summary>
563 		internal	List<List<string>>	seriesClusters = null;
564 
565         #endregion
566 
567 		#region 3D Style properties
568 
569 		/// <summary>
570         /// Gets or sets a ChartArea3DStyle object, used to draw all series in a chart area in 3D.
571 		/// </summary>
572 		[
573 		SRCategory("CategoryAttribute3D"),
574 		Bindable(true),
575 		DefaultValue(null),
576 		SRDescription("DescriptionAttributeArea3DStyle"),
577 		DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
578 		TypeConverter(typeof(NoNameExpandableObjectConverter)),
579 #if !WINFORMS_CONTROL
580 		PersistenceMode(PersistenceMode.InnerProperty),
581 #endif
582 		]
583 		public ChartArea3DStyle Area3DStyle
584 		{
585 			get
586 			{
587                 return _area3DStyle;
588 			}
589 			set
590 			{
591                 _area3DStyle = value;
592 
593 				// Initialize style object
594                 _area3DStyle.Initialize((ChartArea)this);
595 			}
596 		}
597 
598         /// <summary>
599         /// Indicates that series order should be reversed to simulate Y axis rotation.
600         /// </summary>
601         internal bool ReverseSeriesOrder
602         {
603             get { return _reverseSeriesOrder; }
604         }
605 
606         /// <summary>
607         /// Gets the list of all stack group names
608         /// </summary>
609         internal ArrayList StackGroupNames
610         {
611             get { return _stackGroupNames; }
612         }
613 
614 		#endregion
615 
616 		#region 3D Coordinates transfotmation methods
617 
618 		/// <summary>
619         /// Call this method to apply 3D transformations on an array of 3D points (must be done before calling GDI+ drawing methods).
620 		/// </summary>
621 		/// <param name="points">3D Points array.</param>
TransformPoints( Point3D[] points )622 		public void TransformPoints( Point3D[] points )
623 		{
624 			// Convert Z coordinates from 0-100% to axis values
625 			foreach(Point3D pt in points)
626 			{
627 				pt.Z = (pt.Z / 100f) * this.areaSceneDepth;
628 			}
629 
630 			// Transform points
631 			this.matrix3D.TransformPoints( points );
632 		}
633 
634 		#endregion
635 
636 		#region 3D Scene drawing methods
637 
638 		/// <summary>
639 		/// Draws chart area 3D scene, which consists of 3 or 2 walls.
640 		/// </summary>
641 		/// <param name="graph">Chart graphics object.</param>
642 		/// <param name="position">Chart area 2D position.</param>
DrawArea3DScene(ChartGraphics graph, RectangleF position)643 		internal void DrawArea3DScene(ChartGraphics graph, RectangleF position)
644 		{
645 			// Reference to the chart area class
646 			ChartArea chartArea = (ChartArea)this;
647 
648 			// Calculate relative size of the wall
649 			areaSceneWallWidth = graph.GetRelativeSize( new SizeF(this.Area3DStyle.WallWidth, this.Area3DStyle.WallWidth));
650 
651 			//***********************************************************
652 			//** Calculate the depth of the chart area scene
653 			//***********************************************************
654 			areaSceneDepth = GetArea3DSceneDepth();
655 
656 			//***********************************************************
657 			//** Initialize coordinate transformation matrix
658 			//***********************************************************
659 			this.matrix3D.Initialize(
660 				position,
661 				areaSceneDepth,
662 				this.Area3DStyle.Inclination,
663 				this.Area3DStyle.Rotation,
664 				this.Area3DStyle.Perspective,
665 				this.Area3DStyle.IsRightAngleAxes);
666 
667 			//***********************************************************
668 			//** Initialize Lighting
669 			//***********************************************************
670 			this.matrix3D.InitLight(
671 				this.Area3DStyle.LightStyle
672 			);
673 
674 			//***********************************************************
675 			//** Find chart area visible surfaces
676 			//***********************************************************
677             _visibleSurfaces = graph.GetVisibleSurfaces(
678 				position,
679 				0,
680 				areaSceneDepth,
681 				this.matrix3D);
682 
683 			//***********************************************************
684 			//** Chech if area scene should be drawn
685 			//***********************************************************
686 			Color	sceneBackColor = chartArea.BackColor;
687 
688 			// Do not draw the transparent walls
689 			if(sceneBackColor == Color.Transparent)
690 			{
691 				// Area wall is not visible
692 				areaSceneWallWidth = SizeF.Empty;
693 				return;
694 			}
695 
696 			// If color is not set (default) - use LightGray
697 			if(sceneBackColor == Color.Empty)
698 			{
699 				sceneBackColor = Color.LightGray;
700 			}
701 
702 			//***********************************************************
703 			//** Adjust scene 2D rectangle so that wall are drawn
704 			//** outside plotting area.
705 			//***********************************************************
706 			// If bottom wall is visible
707 			if(IsBottomSceneWallVisible())
708 			{
709 				position.Height += areaSceneWallWidth.Height;
710 			}
711 
712 			// Adjust for the left/right wall
713 			position.Width += areaSceneWallWidth.Width;
714 			if(this.Area3DStyle.Rotation > 0)
715 			{
716 				position.X -= areaSceneWallWidth.Width;
717 			}
718 
719 			//***********************************************************
720 			//** Draw scene walls
721 			//***********************************************************
722 
723 			// Draw back wall
724 			RectangleF	wallRect2D = new RectangleF(position.Location, position.Size);
725 			float		wallDepth = areaSceneWallWidth.Width;
726 			float		wallZPosition = -wallDepth;
727 
728 			// For isometric projection Front wall should be visible sometimes
729 			if( IsMainSceneWallOnFront())
730 			{
731 				wallZPosition = areaSceneDepth;
732 			}
733 
734 			graph.Fill3DRectangle(
735 				wallRect2D,
736 				wallZPosition,
737 				wallDepth,
738 				this.matrix3D,
739 				chartArea.Area3DStyle.LightStyle,
740 				sceneBackColor,
741 				chartArea.BorderColor,
742 				chartArea.BorderWidth,
743 				chartArea.BorderDashStyle,
744 				DrawingOperationTypes.DrawElement );
745 
746 			// Draw side wall on the left or right side
747 			wallRect2D = new RectangleF(position.Location, position.Size);
748 			wallRect2D.Width = areaSceneWallWidth.Width;
749 			if(!IsSideSceneWallOnLeft())
750 			{
751 				// Wall is on the right side
752 				wallRect2D.X = position.Right - areaSceneWallWidth.Width;
753 			}
754 			graph.Fill3DRectangle(
755 				wallRect2D,
756 				0f,
757 				areaSceneDepth,
758 				this.matrix3D,
759 				chartArea.Area3DStyle.LightStyle,
760 				sceneBackColor,
761 				chartArea.BorderColor,
762 				chartArea.BorderWidth,
763 				chartArea.BorderDashStyle,
764 				DrawingOperationTypes.DrawElement);
765 
766 			// Draw bottom wall
767 			if(IsBottomSceneWallVisible())
768 			{
769 				wallRect2D = new RectangleF(position.Location, position.Size);
770 				wallRect2D.Height = areaSceneWallWidth.Height;
771 				wallRect2D.Y = position.Bottom - areaSceneWallWidth.Height;
772 				wallRect2D.Width -= areaSceneWallWidth.Width;
773 				if(IsSideSceneWallOnLeft())
774 				{
775 					wallRect2D.X += areaSceneWallWidth.Width;
776 				}
777 
778 				wallZPosition = 0;
779 				graph.Fill3DRectangle(
780 					wallRect2D,
781 					0f,
782 					areaSceneDepth,
783 					this.matrix3D,
784 					chartArea.Area3DStyle.LightStyle,
785 					sceneBackColor,
786 					chartArea.BorderColor,
787 					chartArea.BorderWidth,
788 					chartArea.BorderDashStyle,
789 					DrawingOperationTypes.DrawElement );
790 			}
791 
792 		}
793 
794 		/// <summary>
795 		/// Helper method which return True if bottom wall of the
796 		/// chart area scene is visible.
797 		/// </summary>
798 		/// <returns>True if bottom wall is visible.</returns>
IsBottomSceneWallVisible()799 		internal bool IsBottomSceneWallVisible()
800 		{
801 			return (this.Area3DStyle.Inclination >= 0);
802 		}
803 
804         /// <summary>
805         /// Helper method which return True if main wall of the
806         /// chart area scene is displayed on the front side.
807         /// </summary>
808         /// <returns>True if front wall is visible.</returns>
IsMainSceneWallOnFront()809 		internal bool IsMainSceneWallOnFront()
810 		{
811 			// Note: Not used in this version!
812 			return false;
813 		}
814 
815         /// <summary>
816         /// Helper method which return True if side wall of the
817         /// chart area scene is displayed on the left side.
818         /// </summary>
819         /// <returns>True if bottom wall is visible.</returns>
IsSideSceneWallOnLeft()820 		internal bool IsSideSceneWallOnLeft()
821 		{
822 			return (this.Area3DStyle.Rotation > 0);
823 		}
824 
825 		#endregion
826 
827 		#region 3D Scene depth claculation methods
828 
829 		/// <summary>
830         /// Call this method to get the Z position of a series (useful for custom drawing).
831 		/// </summary>
832         /// <param name="series">The series to retrieve the Z position for.</param>
833 		/// <returns>The Z position of the specified series. Measured as a percentage of the chart area's depth.</returns>
GetSeriesZPosition(Series series)834 		public float GetSeriesZPosition(Series series)
835 		{
836 			float	positionZ, depth;
837 			GetSeriesZPositionAndDepth(series, out depth, out positionZ);
838 			return ((positionZ + depth/2f) / this.areaSceneDepth) * 100f;
839 		}
840 
841 		/// <summary>
842 		/// Call this method to get the depth of a series in a chart area.
843 		/// </summary>
844         /// <param name="series">The series to retrieve the depth for.</param>
845 		/// <returns>The depth of the specified series. Measured as a percentage of the chart area's depth.</returns>
GetSeriesDepth(Series series)846 		public float GetSeriesDepth(Series series)
847 		{
848 			float	positionZ, depth;
849 			GetSeriesZPositionAndDepth(series, out depth, out positionZ);
850 			return (depth / this.areaSceneDepth) * 100f;
851 		}
852 
853 		/// <summary>
854 		/// Calculates area 3D scene depth depending on the number of isClustered
855 		/// series and interval between points.
856 		/// </summary>
857 		/// <returns>Returns the depth of the chart area scene.</returns>
GetArea3DSceneDepth()858 		private float GetArea3DSceneDepth()
859 		{
860 			//***********************************************************
861 			//** Calculate the smallest interval between points
862 			//***********************************************************
863 
864 			// Check if any series attached to the area is indexed
865             bool indexedSeries = ChartHelper.IndexedSeries(this.Common, this._series.ToArray());
866 
867 			// Smallest interval series
868 			Series	smallestIntervalSeries = null;
869 			if(this._series.Count > 0)
870 			{
871 				smallestIntervalSeries = this.Common.DataManager.Series[(string)this._series[0]];
872 			}
873 
874 			// Get X axis
875 			Axis	xAxis = ((ChartArea)this).AxisX;
876 			if(this._series.Count > 0)
877 			{
878 				Series	firstSeries = this.Common.DataManager.Series[this._series[0]];
879 				if(firstSeries != null && firstSeries.XAxisType == AxisType.Secondary)
880 				{
881 					xAxis = ((ChartArea)this).AxisX2;
882 				}
883 			}
884 
885 			// Get smallest interval between points (use interval 1 for indexed series)
886 			double clusteredInterval = 1;
887 			if(!indexedSeries)
888 			{
889 				bool sameInterval;
890 				clusteredInterval = this.GetPointsInterval(this._series, xAxis.IsLogarithmic, xAxis.logarithmBase, false, out sameInterval, out smallestIntervalSeries);
891 			}
892 
893 			//***********************************************************
894 			//** Check if "DrawSideBySide" attribute is set.
895 			//***********************************************************
896 			bool	drawSideBySide = false;
897 			if(smallestIntervalSeries != null)
898 			{
899 				drawSideBySide = Common.ChartTypeRegistry.GetChartType(smallestIntervalSeries.ChartTypeName).SideBySideSeries;
900 				foreach(string seriesName in this._series)
901 				{
902 					if(this.Common.DataManager.Series[seriesName].IsCustomPropertySet(CustomPropertyName.DrawSideBySide))
903 					{
904 						string attribValue = this.Common.DataManager.Series[seriesName][CustomPropertyName.DrawSideBySide];
905 						if(String.Compare(attribValue, "False", StringComparison.OrdinalIgnoreCase) == 0)
906 						{
907 							drawSideBySide = false;
908 						}
909 						else if(String.Compare(attribValue, "True", StringComparison.OrdinalIgnoreCase) == 0)
910 						{
911 							drawSideBySide = true;
912 						}
913                         else if (String.Compare(attribValue, "Auto", StringComparison.OrdinalIgnoreCase) == 0)
914 						{
915 							// Do nothing
916 						}
917 						else
918 						{
919                             throw (new InvalidOperationException(SR.ExceptionAttributeDrawSideBySideInvalid));
920 						}
921 					}
922 				}
923 			}
924 
925 			// Get smallest interval cate----cal axis
926 			Axis	categoricalAxis = ((ChartArea)this).AxisX;
927 			if(smallestIntervalSeries != null && smallestIntervalSeries.XAxisType == AxisType.Secondary)
928 			{
929 				categoricalAxis = ((ChartArea)this).AxisX2;
930 			}
931 
932 			//***********************************************************
933 			//** If series with the smallest interval is displayed
934 			//** side-by-side - devide the interval by number of series
935 			//** of the same chart type.
936 			//***********************************************************
937 			double pointWidthSize = 0.8;
938 			int	seriesNumber = 1;
939 			if(smallestIntervalSeries != null)
940 			{
941 				// Check if series is side-by-side
942 				if(this.Area3DStyle.IsClustered && drawSideBySide)
943 				{
944 					// Count number of side-by-side series
945 					seriesNumber = 0;
946 					foreach(string seriesName in this._series)
947 					{
948 						// Get series object from name
949 						Series	curSeries = this.Common.DataManager.Series[seriesName];
950 						if(String.Compare(curSeries.ChartTypeName, smallestIntervalSeries.ChartTypeName, StringComparison.OrdinalIgnoreCase) == 0 )
951 						{
952 							++seriesNumber;
953 						}
954 					}
955 				}
956 			}
957 
958 
959 
960 			//***********************************************************
961 			//** Stacked column and bar charts can be drawn side-by-side
962 			//** using the StackGroupName custom properties. The code
963 			//** checks if multiple groups are used how many of these
964 			//** groups exsist.
965 			//**
966 			//** If isClustered mode enabled each stack group is drawn
967 			//** using it's own cluster.
968 			//***********************************************************
969 			if(smallestIntervalSeries != null && this.Area3DStyle.IsClustered)
970 			{
971 				// Check series support stack groups
972 				if(Common.ChartTypeRegistry.GetChartType(smallestIntervalSeries.ChartTypeName).SupportStackedGroups)
973 				{
974 					// Calculate how many stack groups exsist
975 					seriesNumber = 0;
976 					ArrayList stackGroupNames = new ArrayList();
977 					foreach(string seriesName in this._series)
978 					{
979 						// Get series object from name
980 						Series	curSeries = this.Common.DataManager.Series[seriesName];
981 						if(String.Compare(curSeries.ChartTypeName, smallestIntervalSeries.ChartTypeName, StringComparison.OrdinalIgnoreCase) == 0 )
982 						{
983 							string seriesStackGroupName = string.Empty;
984 							if(curSeries.IsCustomPropertySet(CustomPropertyName.StackedGroupName))
985 							{
986 								seriesStackGroupName = curSeries[CustomPropertyName.StackedGroupName];
987 							}
988 
989 							// Add group name if it do not already exsist
990 							if(!stackGroupNames.Contains(seriesStackGroupName))
991 							{
992 								stackGroupNames.Add(seriesStackGroupName);
993 							}
994 						}
995 					}
996 					seriesNumber = stackGroupNames.Count;
997 				}
998 			}
999 
1000 
1001 
1002 			//***********************************************************
1003 			//** Check if series provide custom value for point\gap depth
1004 			//***********************************************************
1005             _pointsDepth = clusteredInterval * pointWidthSize * this.Area3DStyle.PointDepth / 100.0;
1006             _pointsDepth = categoricalAxis.GetPixelInterval(_pointsDepth);
1007 			if(smallestIntervalSeries != null)
1008 			{
1009                 _pointsDepth = smallestIntervalSeries.GetPointWidth(
1010 					this.Common.graph,
1011 					categoricalAxis,
1012 					clusteredInterval,
1013 					0.8) / seriesNumber;
1014                 _pointsDepth *= this.Area3DStyle.PointDepth / 100.0;
1015 			}
1016             _pointsGapDepth = (_pointsDepth * 0.8) * this.Area3DStyle.PointGapDepth / 100.0;
1017 
1018 			// Get point depth and gap from series
1019 			if(smallestIntervalSeries != null)
1020 			{
1021                 smallestIntervalSeries.GetPointDepthAndGap(
1022 					this.Common.graph,
1023 					categoricalAxis,
1024                     ref _pointsDepth,
1025                     ref _pointsGapDepth);
1026 			}
1027 
1028 
1029 			//***********************************************************
1030 			//** Calculate scene depth
1031 			//***********************************************************
1032             return (float)((_pointsGapDepth + _pointsDepth) * GetNumberOfClusters());
1033 		}
1034 
1035 		/// <summary>
1036 		/// Calculates the depth and Z position for specified series.
1037 		/// </summary>
1038 		/// <param name="series">Series object.</param>
1039 		/// <param name="depth">Returns series depth.</param>
1040 		/// <param name="positionZ">Returns series Z position.</param>
GetSeriesZPositionAndDepth(Series series, out float depth, out float positionZ)1041 		internal void GetSeriesZPositionAndDepth(Series series, out float depth, out float positionZ)
1042 		{
1043             // Check arguments
1044             if (series == null)
1045                 throw new ArgumentNullException("series");
1046 
1047 			// Get series cluster index
1048 			int seriesIndex = GetSeriesClusterIndex(series);
1049 
1050 			// Initialize the output parameters
1051             depth = (float)_pointsDepth;
1052             positionZ = (float)(_pointsGapDepth / 2.0 + (_pointsDepth + _pointsGapDepth) * seriesIndex);
1053 		}
1054 
1055 
1056 
1057 		/// <summary>
1058 		/// Returns number of clusters on the Z axis.
1059 		/// </summary>
1060 		/// <returns>Number of clusters on the Z axis.</returns>
GetNumberOfClusters()1061 		internal int GetNumberOfClusters()
1062 		{
1063 			if(this.seriesClusters == null)
1064 			{
1065 				// Lists that hold processed chart types and stacked groups
1066 				ArrayList	processedChartTypes = new ArrayList();
1067 				ArrayList	processedStackedGroups = new ArrayList();
1068 
1069 				// Reset series cluster list
1070 				this.seriesClusters = new List<List<string>>();
1071 
1072 				// Iterate through all series that belong to this chart area
1073 				int clusterIndex = -1;
1074 				foreach(string seriesName in this._series)
1075 				{
1076 					// Get series object by name
1077 					Series	curSeries = this.Common.DataManager.Series[seriesName];
1078 
1079 					// Check if stacked chart type is using multiple groups that
1080 					// can be displayed in individual clusters
1081 					if(!this.Area3DStyle.IsClustered &&
1082 						Common.ChartTypeRegistry.GetChartType(curSeries.ChartTypeName).SupportStackedGroups)
1083 					{
1084 						// Get group name
1085 						string stackGroupName = StackedColumnChart.GetSeriesStackGroupName(curSeries);
1086 
1087 						// Check if group was already counted
1088 						if(processedStackedGroups.Contains(stackGroupName))
1089 						{
1090 							// Find in which cluster this stacked group is located
1091 							bool found = false;
1092 							for(int index = 0; !found && index < this.seriesClusters.Count; index++)
1093 							{
1094 								foreach(string name in this.seriesClusters[index])
1095 								{
1096 									// Get series object by name
1097 									Series	ser = this.Common.DataManager.Series[name];
1098 									if(stackGroupName == StackedColumnChart.GetSeriesStackGroupName(ser))
1099 									{
1100 										clusterIndex = index;
1101 										found = true;
1102 									}
1103 								}
1104 							}
1105 						}
1106 						else
1107 						{
1108 							// Increase cluster index
1109 							clusterIndex = this.seriesClusters.Count;
1110 
1111 							// Add processed group name
1112 							processedStackedGroups.Add(stackGroupName);
1113 						}
1114 					}
1115 
1116 
1117 						// Chech if series is displayed in the same cluster than other series
1118 					else if( Common.ChartTypeRegistry.GetChartType(curSeries.ChartTypeName).Stacked ||
1119 						(this.Area3DStyle.IsClustered && Common.ChartTypeRegistry.GetChartType(curSeries.ChartTypeName).SideBySideSeries) )
1120 					{
1121 						// Check if this chart type is already in the list
1122 						if(processedChartTypes.Contains(curSeries.ChartTypeName.ToUpper(System.Globalization.CultureInfo.InvariantCulture)))
1123 						{
1124 							// Find in which cluster this chart type is located
1125 							bool found = false;
1126 							for(int index = 0; !found && index < this.seriesClusters.Count; index++)
1127 							{
1128 								foreach(string name in this.seriesClusters[index])
1129 								{
1130 									// Get series object by name
1131 									Series	ser = this.Common.DataManager.Series[name];
1132 									if(ser.ChartTypeName.ToUpper(System.Globalization.CultureInfo.InvariantCulture) ==
1133 										curSeries.ChartTypeName.ToUpper(System.Globalization.CultureInfo.InvariantCulture))
1134 									{
1135 										clusterIndex = index;
1136 										found = true;
1137 									}
1138 								}
1139 							}
1140 						}
1141 						else
1142 						{
1143 							// Increase cluster index
1144 							clusterIndex = this.seriesClusters.Count;
1145 
1146 							// Add new chart type into the collection
1147 							processedChartTypes.Add(curSeries.ChartTypeName.ToUpper(System.Globalization.CultureInfo.InvariantCulture));
1148 						}
1149 					}
1150 					else
1151 					{
1152 						// Create New cluster
1153 						clusterIndex = this.seriesClusters.Count;
1154 					}
1155 
1156 					// Create an item in the cluster list that will hold all series names
1157 					if(this.seriesClusters.Count <= clusterIndex)
1158 					{
1159 						this.seriesClusters.Add(new List<string>());
1160 					}
1161 
1162 					// Add series name into the current cluster
1163 					this.seriesClusters[clusterIndex].Add(seriesName);
1164 				}
1165 			}
1166 
1167 			return this.seriesClusters.Count;
1168 		}
1169 
1170 		/// <summary>
1171 		/// Get series cluster index.
1172 		/// </summary>
1173 		/// <param name="series">Series object.</param>
1174 		/// <returns>Series cluster index.</returns>
GetSeriesClusterIndex(Series series)1175 		internal int GetSeriesClusterIndex(Series series)
1176 		{
1177 			// Fill list of clusters
1178 			if(this.seriesClusters == null)
1179 			{
1180 				this.GetNumberOfClusters();
1181 			}
1182 
1183 			// Iterate through all clusters
1184 			for(int clusterIndex = 0; clusterIndex < this.seriesClusters.Count; clusterIndex++)
1185 			{
1186 				List<string> seriesNames = this.seriesClusters[clusterIndex];
1187 
1188 				// Iterate through all series names
1189 				foreach(string seriesName in seriesNames)
1190 				{
1191 					if(seriesName == series.Name)
1192 					{
1193 						// Check if series are drawn in reversed order
1194 						if(this._reverseSeriesOrder)
1195 						{
1196 							clusterIndex = (this.seriesClusters.Count - 1) - clusterIndex;
1197 						}
1198 						return clusterIndex;
1199 					}
1200 				}
1201 			}
1202 			return 0;
1203 		}
1204 
1205 
1206 
1207 		#endregion
1208 
1209 		#region 3D Scene helper methods
1210 
1211 		/// <summary>
1212 		/// This method is used to calculate estimated scene
1213 		/// depth. Regular scene depth method can not be used
1214 		/// because Plot area position is zero. Instead, Chart
1215 		/// area position is used to find depth of the scene.
1216 		/// Algorithm which draws axis labels will decide what
1217 		/// should be size and position of plotting area.
1218 		/// </summary>
1219 		/// <returns>Returns estimated scene depth</returns>
GetEstimatedSceneDepth()1220 		private float GetEstimatedSceneDepth()
1221 		{
1222 			float sceneDepth;
1223 
1224 			ChartArea area = (ChartArea) this;
1225 
1226 
1227 			// Reset current list of clusters
1228 			this.seriesClusters = null;
1229 
1230 
1231 			ElementPosition plottingAreaRect = area.InnerPlotPosition;
1232 
1233 			area.AxisX.PlotAreaPosition = area.Position;
1234 			area.AxisY.PlotAreaPosition = area.Position;
1235 			area.AxisX2.PlotAreaPosition = area.Position;
1236 			area.AxisY2.PlotAreaPosition = area.Position;
1237 
1238 			sceneDepth = GetArea3DSceneDepth();
1239 
1240 			area.AxisX.PlotAreaPosition = plottingAreaRect;
1241 			area.AxisY.PlotAreaPosition = plottingAreaRect;
1242 			area.AxisX2.PlotAreaPosition = plottingAreaRect;
1243 			area.AxisY2.PlotAreaPosition = plottingAreaRect;
1244 
1245 			return sceneDepth;
1246 		}
1247 
1248 		/// <summary>
1249 		/// Estimate Interval for 3D Charts. When scene is rotated the
1250 		/// number of labels should be changed.
1251 		/// </summary>
1252 		/// <param name="graph">Chart graphics object.</param>
Estimate3DInterval(ChartGraphics graph )1253 		internal void Estimate3DInterval(ChartGraphics graph )
1254 		{
1255 			// Reference to the chart area class
1256             ChartArea area = (ChartArea)this;
1257 
1258 			// Calculate relative size of the wall
1259 			areaSceneWallWidth = graph.GetRelativeSize( new SizeF(this.Area3DStyle.WallWidth, this.Area3DStyle.WallWidth));
1260 
1261 			//***********************************************************
1262 			//** Calculate the depth of the chart area scene
1263 			//***********************************************************
1264 			areaSceneDepth = GetEstimatedSceneDepth();
1265 
1266 			RectangleF plottingRect = area.Position.ToRectangleF();
1267 
1268 			// Check if plot area position was recalculated.
1269 			// If not and non-auto InnerPlotPosition & Position were
1270 			// specified - do all needed calculations
1271 			if(PlotAreaPosition.Width == 0 &&
1272 				PlotAreaPosition.Height == 0 &&
1273 				!area.InnerPlotPosition.Auto
1274 				&& !area.Position.Auto)
1275 			{
1276 				// Initialize plotting area position
1277 
1278 				if(!area.InnerPlotPosition.Auto)
1279 				{
1280 					plottingRect.X += (area.Position.Width / 100F) * area.InnerPlotPosition.X;
1281 					plottingRect.Y += (area.Position.Height / 100F) * area.InnerPlotPosition.Y;
1282 					plottingRect.Width = (area.Position.Width / 100F) * area.InnerPlotPosition.Width;
1283 					plottingRect.Height = (area.Position.Height / 100F) * area.InnerPlotPosition.Height;
1284 				}
1285 
1286 			}
1287 
1288 			int yAngle = GetRealYAngle( );
1289 
1290 			//***********************************************************
1291 			//** Initialize coordinate transformation matrix
1292 			//***********************************************************
1293 			Matrix3D intervalMatrix3D = new Matrix3D();
1294 			intervalMatrix3D.Initialize(
1295 				plottingRect,
1296 				areaSceneDepth,
1297 				this.Area3DStyle.Inclination,
1298 				yAngle,
1299 				this.Area3DStyle.Perspective,
1300 				this.Area3DStyle.IsRightAngleAxes);
1301 			bool notUsed;
1302 			float zPosition;
1303 			double size;
1304 
1305 			Point3D [] points = new Point3D[8];
1306 
1307 			if( area.switchValueAxes )
1308 			{
1309 
1310 				// X Axis
1311 				zPosition = axisX.GetMarksZPosition( out notUsed );
1312 
1313 				points[0] = new Point3D( plottingRect.X, plottingRect.Y, zPosition );
1314 				points[1] = new Point3D( plottingRect.X, plottingRect.Bottom, zPosition );
1315 
1316 				// Y Axis
1317 				zPosition = axisY.GetMarksZPosition( out notUsed );
1318 
1319 				points[2] = new Point3D( plottingRect.X, plottingRect.Bottom, zPosition );
1320 				points[3] = new Point3D( plottingRect.Right, plottingRect.Bottom, zPosition );
1321 
1322 				// X2 Axis
1323 				zPosition = axisX2.GetMarksZPosition( out notUsed );
1324 
1325 				points[4] = new Point3D( plottingRect.X, plottingRect.Y, zPosition );
1326 				points[5] = new Point3D( plottingRect.X, plottingRect.Bottom, zPosition );
1327 
1328 				// Y2 Axis
1329 				zPosition = axisY2.GetMarksZPosition( out notUsed );
1330 
1331 				points[6] = new Point3D( plottingRect.X, plottingRect.Y, zPosition );
1332 				points[7] = new Point3D( plottingRect.Right, plottingRect.Y, zPosition );
1333 			}
1334 			else
1335 			{
1336 				// X Axis
1337 				zPosition = axisX.GetMarksZPosition( out notUsed );
1338 
1339 				points[0] = new Point3D( plottingRect.X, plottingRect.Bottom, zPosition );
1340 				points[1] = new Point3D( plottingRect.Right, plottingRect.Bottom, zPosition );
1341 
1342 				// Y Axis
1343 				zPosition = axisY.GetMarksZPosition( out notUsed );
1344 
1345 				points[2] = new Point3D( plottingRect.X, plottingRect.Y, zPosition );
1346 				points[3] = new Point3D( plottingRect.X, plottingRect.Bottom, zPosition );
1347 
1348 				// X2 Axis
1349 				zPosition = axisX2.GetMarksZPosition( out notUsed );
1350 
1351 				points[4] = new Point3D( plottingRect.X, plottingRect.Y, zPosition );
1352 				points[5] = new Point3D( plottingRect.Right, plottingRect.Y, zPosition );
1353 
1354 				// Y2 Axis
1355 				zPosition = axisY2.GetMarksZPosition( out notUsed );
1356 
1357 				points[6] = new Point3D( plottingRect.X, plottingRect.Y, zPosition );
1358 				points[7] = new Point3D( plottingRect.X, plottingRect.Bottom, zPosition );
1359 			}
1360 
1361 			// Crossing has to be reset because interval and
1362 			// sometimes minimum and maximum are changed.
1363 			foreach( Axis axis in area.Axes )
1364 			{
1365 				axis.crossing = axis.tempCrossing;
1366 			}
1367 
1368 			// Transform all points
1369 			intervalMatrix3D.TransformPoints( points );
1370 
1371 			int axisIndx = 0;
1372 			foreach( Axis axis in area.Axes )
1373 			{
1374 				// Find size of projected axis
1375 				size = Math.Sqrt(
1376 					( points[axisIndx].X - points[axisIndx+1].X ) * ( points[axisIndx].X - points[axisIndx+1].X ) +
1377 					( points[axisIndx].Y - points[axisIndx+1].Y ) * ( points[axisIndx].Y - points[axisIndx+1].Y ) );
1378 
1379 				// At the beginning plotting area chart is not calculated because
1380 				// algorithm for labels calculates plotting area position. To
1381 				// calculate labels position we need interval and interval
1382 				// need this correction. Because of that Chart area is used
1383 				// instead of plotting area position. If secondary label is
1384 				// enabled error for using chart area position instead of
1385 				// plotting area position is much bigger. This value
1386 				// corrects this error.
1387 				float plottingChartAreaCorrection = 1;
1388 				if( !area.switchValueAxes )
1389 				{
1390 					plottingChartAreaCorrection = 0.5F;
1391 				}
1392 
1393 				// Set correction for axis size
1394 				if( axis.AxisName == AxisName.X || axis.AxisName == AxisName.X2 )
1395 				{
1396 					if( area.switchValueAxes )
1397 						axis.interval3DCorrection =  size / plottingRect.Height;
1398 					else
1399 						axis.interval3DCorrection =  size / plottingRect.Width;
1400 				}
1401 				else
1402 				{
1403 					if( area.switchValueAxes )
1404 						axis.interval3DCorrection =  size / plottingRect.Width;
1405 					else
1406 						axis.interval3DCorrection =  size / plottingRect.Height * plottingChartAreaCorrection;
1407 				}
1408 
1409 				// There is a limit for correction
1410 				if( axis.interval3DCorrection < 0.15 )
1411 					axis.interval3DCorrection = 0.15;
1412 
1413 				// There is a limit for correction
1414 				if( axis.interval3DCorrection > 0.8 )
1415 					axis.interval3DCorrection = 1.0;
1416 
1417 				axisIndx += 2;
1418 
1419 			}
1420 		}
1421 
1422 
1423 		/// <summary>
1424 		/// Calculates real Y angle from Y angle and reverseSeriesOrder field
1425 		/// </summary>
1426 		/// <returns>Real Y angle</returns>
GetRealYAngle( )1427 		internal int GetRealYAngle( )
1428 		{
1429 			int yAngle;
1430 
1431 			// Case from -90 to 90
1432 			yAngle = this.Area3DStyle.Rotation;
1433 
1434 			// Case from 90 to 180
1435 			if( this._reverseSeriesOrder && this.Area3DStyle.Rotation >= 0 )
1436 				yAngle = this.Area3DStyle.Rotation - 180;
1437 
1438 			// Case from -90 to -180
1439 			if( this._reverseSeriesOrder && this.Area3DStyle.Rotation <= 0 )
1440 				yAngle = this.Area3DStyle.Rotation + 180;
1441 
1442 			return yAngle;
1443 		}
1444 
1445         /// <summary>
1446         /// Check if surface element should be drawn on the Back or Front layer.
1447         /// </summary>
1448         /// <param name="surfaceName">Surface name.</param>
1449         /// <param name="backLayer">Back/front layer.</param>
1450         /// <param name="onEdge">Indicates that element is on the edge (drawn on the back layer).</param>
1451         /// <returns>True if element should be drawn.</returns>
ShouldDrawOnSurface(SurfaceNames surfaceName, bool backLayer, bool onEdge)1452 		internal bool ShouldDrawOnSurface(SurfaceNames surfaceName, bool backLayer, bool onEdge)
1453 		{
1454 			// Check if surface element should be drawn on the Back or Front layer.
1455             bool isVisible = ((this._visibleSurfaces & surfaceName) == surfaceName);
1456 
1457 			// Elements on the edge are drawn on the back layer
1458 			if(onEdge)
1459 			{
1460 				return backLayer;
1461 			}
1462 
1463 			return (backLayer == (!isVisible) );
1464 		}
1465 
1466 		/// <summary>
1467 		/// Indicates that data points in all series of this
1468 		/// chart area should be drawn in reversed order.
1469 		/// </summary>
1470 		/// <returns>True if series points should be drawn in reversed order.</returns>
DrawPointsInReverseOrder()1471 		internal bool DrawPointsInReverseOrder()
1472 		{
1473 			return (this.Area3DStyle.Rotation <= 0);
1474 		}
1475 
1476         /// <summary>
1477         /// Checks if points should be drawn from sides to center.
1478         /// </summary>
1479         /// <param name="coord">Which coordinate of COP (X, Y or Z) to test for surface overlapping</param>
1480         /// <returns>True if points should be drawn from sides to center.</returns>
DrawPointsToCenter(ref COPCoordinates coord)1481 		internal bool DrawPointsToCenter(ref COPCoordinates coord)
1482 		{
1483 			bool			result = false;
1484 			COPCoordinates	resultCoordinates = 0;
1485 
1486 			// Check only if perspective is set
1487 			if(this.Area3DStyle.Perspective != 0)
1488 			{
1489 				if( (coord & COPCoordinates.X) == COPCoordinates.X )
1490 				{
1491 					// Only when Left & Right sides of plotting area are invisible
1492                     if ((this._visibleSurfaces & SurfaceNames.Left) == 0 &&
1493                         (this._visibleSurfaces & SurfaceNames.Right) == 0)
1494 					{
1495 						result = true;
1496 					}
1497 					resultCoordinates = resultCoordinates | COPCoordinates.X;
1498 				}
1499 				if( (coord & COPCoordinates.Y) == COPCoordinates.Y )
1500 				{
1501 					// Only when Top & Bottom sides of plotting area are invisible
1502                     if ((this._visibleSurfaces & SurfaceNames.Top) == 0 &&
1503                         (this._visibleSurfaces & SurfaceNames.Bottom) == 0)
1504 					{
1505 						result = true;
1506 					}
1507 					resultCoordinates = resultCoordinates | COPCoordinates.Y;
1508 				}
1509 				if( (coord & COPCoordinates.Z) == COPCoordinates.Z )
1510 				{
1511 					// Only when Front & Back sides of plotting area are invisible
1512                     if ((this._visibleSurfaces & SurfaceNames.Front) == 0 &&
1513                         (this._visibleSurfaces & SurfaceNames.Back) == 0)
1514 					{
1515 						result = true;
1516 					}
1517 					resultCoordinates = resultCoordinates | COPCoordinates.Z;
1518 				}
1519 			}
1520 
1521 			return result;
1522 		}
1523 
1524 		/// <summary>
1525 		/// Checks if series should be drawn from sides to center.
1526 		/// </summary>
1527 		/// <returns>True if series should be drawn from sides to center.</returns>
DrawSeriesToCenter()1528 		internal bool DrawSeriesToCenter()
1529 		{
1530 			// Check only if perspective is set
1531 			if(this.Area3DStyle.Perspective != 0)
1532 			{
1533 				// Only when Left & Right sides of plotting area are invisible
1534                 if ((this._visibleSurfaces & SurfaceNames.Front) == 0 &&
1535                     (this._visibleSurfaces & SurfaceNames.Back) == 0)
1536 				{
1537 					return true;
1538 				}
1539 			}
1540 
1541 			return false;
1542 		}
1543 
1544 		#endregion
1545 
1546 		#region 3D Series drawing and selection methods
1547 
1548 		/// <summary>
1549 		/// Draws 3D series in the chart area.
1550 		/// </summary>
1551 		/// <param name="graph">Chart graphics object.</param>
PaintChartSeries3D( ChartGraphics graph )1552 		internal void PaintChartSeries3D( ChartGraphics graph )
1553 		{
1554 			// Reference to the chart area object
1555 			ChartArea	area = (ChartArea)this;
1556 
1557 			// Get order of series drawing
1558 			List<Series>	seriesDrawingOrder = GetSeriesDrawingOrder(_reverseSeriesOrder);
1559 
1560 			// Loop through all series in the order of drawing
1561 			IChartType type;
1562 			foreach( object seriesObj in seriesDrawingOrder)
1563 			{
1564 				Series series = (Series)seriesObj;
1565 				type = Common.ChartTypeRegistry.GetChartType(series.ChartTypeName);
1566 				type.Paint( graph, Common, area, series );
1567 			}
1568 		}
1569 
1570 		#endregion
1571 
1572 		#region 3D Series & Points drawing order methods
1573 
1574 		/// <summary>
1575 		/// Gets a list of series names that belong to the same 3D cluster.
1576 		/// </summary>
1577 		/// <param name="seriesName">One of the series names that belongs to the cluster.</param>
1578 		/// <returns>List of all series names that belong to the same cluster as specified series.</returns>
GetClusterSeriesNames(string seriesName)1579 		internal List<string> GetClusterSeriesNames(string seriesName)
1580 		{
1581 			// Iterate through all clusters
1582 			foreach(List<string> seriesNames in this.seriesClusters)
1583 			{
1584 				if(seriesNames.Contains(seriesName))
1585 				{
1586 					return seriesNames;
1587 				}
1588 			}
1589 			return new List<string>();
1590 		}
1591 
1592         /// <summary>
1593         /// Gets the series list in drawing order.
1594         /// </summary>
1595         /// <param name="reverseSeriesOrder">Series order should be reversed because of the Y axis angle.</param>
1596         /// <returns>Gets the series list in drawing order.</returns>
GetSeriesDrawingOrder(bool reverseSeriesOrder)1597 		private List<Series> GetSeriesDrawingOrder(bool reverseSeriesOrder)
1598 		{
1599 			// Create list of series
1600             List<Series> seriesList = new List<Series>();
1601 
1602 			// Iterate through all clusters
1603 			foreach(List<string> seriesNames in this.seriesClusters)
1604 			{
1605 				// Make sure there is at least one series
1606 				if(seriesNames.Count > 0)
1607 				{
1608 					// Get first series object in the current cluster
1609 					Series series = Common.DataManager.Series[seriesNames[0]];
1610 
1611 					// Add series into the drawing list
1612 					seriesList.Add(series);
1613 				}
1614 			}
1615 
1616 			// Reversed series list
1617 			if(reverseSeriesOrder)
1618 			{
1619 				seriesList.Reverse();
1620 			}
1621 
1622 			// Check if series should be drawn from sides into the center
1623 			if(DrawSeriesToCenter() &&
1624 				this.matrix3D.IsInitialized())
1625 			{
1626 				// Get Z coordinate of Center Of Projection
1627 				Point3D		areaProjectionCenter = new Point3D(float.NaN, float.NaN, float.NaN);
1628 				areaProjectionCenter = this.GetCenterOfProjection(COPCoordinates.Z);
1629 				if(!float.IsNaN(areaProjectionCenter.Z))
1630 				{
1631 					// Loop through all series
1632 					for(int seriesIndex = 0; seriesIndex < seriesList.Count; seriesIndex++)
1633 					{
1634 						// Check if series is not empty
1635 						if(((Series)seriesList[seriesIndex]).Points.Count == 0)
1636 						{
1637 							continue;
1638 						}
1639 
1640 						// Get series Z position
1641 						float seriesDepth, seriesZPosition;
1642 						this.GetSeriesZPositionAndDepth((Series)seriesList[seriesIndex], out seriesDepth, out seriesZPosition);
1643 
1644 						// Check if series passes the Z coordinate of Center of Projection
1645 						if(seriesZPosition >= areaProjectionCenter.Z)
1646 						{
1647 							// Reversed all series order staring from previous series
1648 							--seriesIndex;
1649 							if(seriesIndex < 0)
1650 								seriesIndex = 0;
1651 							seriesList.Reverse(seriesIndex, seriesList.Count - seriesIndex);
1652 							break;
1653 						}
1654 					}
1655 				}
1656 			}
1657 
1658 			return seriesList;
1659 		}
1660 
1661 
1662 		/// <summary>
1663 		/// Gets number of stack groups in specified array of series names.
1664 		/// </summary>
1665 		/// <param name="seriesNamesList">Array of series names.</param>
1666 		/// <returns>Number of stack groups. One by default.</returns>
GetNumberOfStackGroups(IList<string> seriesNamesList)1667 		private int GetNumberOfStackGroups(IList<string> seriesNamesList)
1668 		{
1669 			this._stackGroupNames = new ArrayList();
1670 			foreach( object seriesName in seriesNamesList )
1671 			{
1672 				// Get series object
1673 				Series	ser = this.Common.DataManager.Series[(string)seriesName];
1674 
1675 				// Get stack group name from the series
1676 				string stackGroupName = string.Empty;
1677 				if(ser.IsCustomPropertySet(CustomPropertyName.StackedGroupName))
1678 				{
1679 					stackGroupName = ser[CustomPropertyName.StackedGroupName];
1680 				}
1681 
1682 				// Add group name if it do not already exsist
1683 				if(!this._stackGroupNames.Contains(stackGroupName))
1684 				{
1685 					this._stackGroupNames.Add(stackGroupName);
1686 				}
1687 			}
1688 
1689 			return this._stackGroupNames.Count;
1690 		}
1691 
1692 		/// <summary>
1693 		/// Gets index of the series stack group.
1694 		/// </summary>
1695 		/// <param name="series">Series to get the index for.</param>
1696 		/// <param name="stackGroupName">Group name this series belongs to.</param>
1697 		/// <returns>Index of series stack group.</returns>
GetSeriesStackGroupIndex(Series series, ref string stackGroupName)1698 		internal int GetSeriesStackGroupIndex(Series series, ref string stackGroupName)
1699 		{
1700 			stackGroupName = string.Empty;
1701 			if(this._stackGroupNames != null)
1702 			{
1703 				// Get stack group name from the series
1704 				if(series.IsCustomPropertySet(CustomPropertyName.StackedGroupName))
1705 				{
1706 					stackGroupName = series[CustomPropertyName.StackedGroupName];
1707 				}
1708 				return this._stackGroupNames.IndexOf(stackGroupName);
1709 			}
1710 			return 0;
1711 		}
1712 
1713 
1714 
1715         /// <summary>
1716         /// Determine the order of points drawing from one or several series of the same type.
1717         /// </summary>
1718         /// <param name="seriesNamesList">List of series names.</param>
1719         /// <param name="chartType">Chart type.</param>
1720         /// <param name="selection">If True selection mode is active (points order should be reversed).</param>
1721         /// <param name="coord">Which coordinate of COP (X, Y or Z) to test for surface overlapping</param>
1722         /// <param name="comparer">Points comparer class. Can be Null.</param>
1723         /// <param name="mainYValueIndex">Index of the main Y value.</param>
1724         /// <param name="sideBySide">Series should be drawn side by side.</param>
1725         /// <returns>Array list of points in drawing order.</returns>
GetDataPointDrawingOrder( List<string> seriesNamesList, IChartType chartType, bool selection, COPCoordinates coord, IComparer comparer, int mainYValueIndex, bool sideBySide)1726 		internal ArrayList GetDataPointDrawingOrder(
1727 			List<string> seriesNamesList,
1728 			IChartType chartType,
1729 			bool selection,
1730 			COPCoordinates coord,
1731 			IComparer comparer,
1732 			int mainYValueIndex,
1733 			bool sideBySide)
1734 		{
1735 			ChartArea area = (ChartArea)this;
1736 
1737 			// Array of points in all series
1738 			ArrayList pointsList = new ArrayList();
1739 
1740 			//************************************************************
1741 			//** Analyse input series
1742 			//************************************************************
1743 
1744 			// Find the number of data series for side-by-side drawing
1745 			double	numOfSeries = 1;
1746 			if(area.Area3DStyle.IsClustered && !chartType.Stacked && sideBySide)
1747 			{
1748 				numOfSeries = seriesNamesList.Count;
1749 			}
1750 
1751 
1752 			// Check stacked series group names
1753 			if(chartType.SupportStackedGroups)
1754 			{
1755 				// Fill the list of group names and get the number of unique groups
1756 				int numberOfGroups = this.GetNumberOfStackGroups(seriesNamesList);
1757 
1758 				// If series are not isClustered set series number to the stacked group number
1759 				if(this.Area3DStyle.IsClustered &&
1760 					seriesNamesList.Count > 0)
1761 				{
1762 					numOfSeries = numberOfGroups;
1763 				}
1764 			}
1765 
1766 
1767 			// Check if chart series are indexed
1768             bool indexedSeries = ChartHelper.IndexedSeries(this.Common, seriesNamesList.ToArray());
1769 
1770 			//************************************************************
1771 			//** Loop through all series and fill array of points
1772 			//************************************************************
1773 			int	seriesIndx = 0;
1774 			foreach( object seriesName in seriesNamesList )
1775 			{
1776 				// Get series object
1777 				Series	ser = this.Common.DataManager.Series[(string)seriesName];
1778 
1779 
1780 				// Check if stacked groups present
1781 				if(chartType.SupportStackedGroups &&
1782 					this._stackGroupNames != null)
1783 				{
1784 					// Get index of the series using stacked group
1785 					string groupName = string.Empty;
1786 					seriesIndx = this.GetSeriesStackGroupIndex(ser, ref groupName);
1787 
1788 					// Set current group name
1789                     StackedColumnChart stackedColumnChart = chartType as StackedColumnChart;
1790                     if (stackedColumnChart != null)
1791 					{
1792                         stackedColumnChart.currentStackGroup = groupName;
1793 					}
1794 					else
1795 					{
1796                         StackedBarChart stackedBarChart = chartType as StackedBarChart;
1797                         if (stackedBarChart != null)
1798                         {
1799                             stackedBarChart.currentStackGroup = groupName;
1800                         }
1801 					}
1802 				}
1803 
1804 
1805 				// Set active vertical/horizontal axis and their max/min values
1806 				Axis	vAxis = (ser.YAxisType == AxisType.Primary) ? area.AxisY : area.AxisY2;
1807 				Axis	hAxis = (ser.XAxisType == AxisType.Primary) ? area.AxisX : area.AxisX2;
1808 
1809 				// Get points interval:
1810 				//  - set interval to 1 for indexed series
1811 				//  - if points are not equaly spaced, the minimum interval between points is selected.
1812 				//  - if points have same interval bars do not overlap each other.
1813 				bool	sameInterval = true;
1814 				double	interval = 1;
1815 				if(!indexedSeries)
1816 				{
1817 					interval = area.GetPointsInterval( seriesNamesList, hAxis.IsLogarithmic, hAxis.logarithmBase, true, out sameInterval );
1818 				}
1819 
1820 				// Get column width
1821 				double	width = ser.GetPointWidth(area.Common.graph, hAxis, interval, 0.8) / numOfSeries;
1822 
1823 				// Get series depth and Z position
1824 				float seriesDepth, seriesZPosition;
1825 				this.GetSeriesZPositionAndDepth(ser, out seriesDepth, out seriesZPosition);
1826 
1827 				//************************************************************
1828 				//** Loop through all points in series
1829 				//************************************************************
1830 				int	index = 0;
1831 				foreach( DataPoint point in ser.Points )
1832 				{
1833 					// Increase point index
1834 					index++;
1835 
1836 					// Set x position
1837 					double	xCenterVal;
1838 					double	xPosition;
1839 					if( indexedSeries )
1840 					{
1841 						// The formula for position is based on a distance
1842 						//from the grid line or nPoints position.
1843 						xPosition = hAxis.GetPosition( (double)index ) - width * ((double) numOfSeries) / 2.0 + width/2 + seriesIndx * width;
1844 						xCenterVal = hAxis.GetPosition( (double)index );
1845 
1846 					}
1847 					else if( sameInterval )
1848 					{
1849 						xPosition = hAxis.GetPosition( point.XValue ) - width * ((double) numOfSeries) / 2.0 + width/2 + seriesIndx * width;
1850 						xCenterVal = hAxis.GetPosition( point.XValue );
1851 					}
1852 					else
1853 					{
1854 						xPosition = hAxis.GetPosition( point.XValue );
1855 						xCenterVal = hAxis.GetPosition( point.XValue );
1856 					}
1857 
1858 
1859 					//************************************************************
1860 					//** Create and add new DataPoint3D object
1861 					//************************************************************
1862 					DataPoint3D pointEx = new DataPoint3D();
1863 					pointEx.indexedSeries = indexedSeries;
1864 					pointEx.dataPoint = point;
1865 					pointEx.index = index;
1866 					pointEx.xPosition = xPosition;
1867 					pointEx.xCenterVal = xCenterVal;
1868 					pointEx.width = ser.GetPointWidth(area.Common.graph, hAxis, interval, 0.8) / numOfSeries;
1869 					pointEx.depth = seriesDepth;
1870 					pointEx.zPosition = seriesZPosition;
1871 
1872 					// Set Y value and height
1873                     double yValue = chartType.GetYValue(Common, area, ser, point, index - 1, mainYValueIndex);
1874                     if (point.IsEmpty && Double.IsNaN(yValue))
1875                     {
1876                         yValue = 0.0;
1877                     }
1878 					pointEx.yPosition = vAxis.GetPosition(yValue);
1879 					pointEx.height = vAxis.GetPosition(yValue - chartType.GetYValue(Common, area, ser, point, index - 1, -1));
1880 
1881 
1882 					pointsList.Add(pointEx);
1883 				}
1884 
1885 				// Data series index
1886 				if(numOfSeries > 1 && sideBySide)
1887 				{
1888 					seriesIndx++;
1889 				}
1890 			}
1891 
1892 			//************************************************************
1893 			//** Sort points in drawing order
1894 			//************************************************************
1895 			if(comparer == null)
1896 			{
1897 				comparer = new PointsDrawingOrderComparer((ChartArea)this, selection, coord);
1898 			}
1899 			pointsList.Sort(comparer);
1900 
1901 			return pointsList;
1902 		}
1903 
1904 		#endregion
1905 
1906 		#region Points drawing order comparer class
1907 
1908 		/// <summary>
1909 		/// Used to compare points in array and sort them by drawing order.
1910 		/// </summary>
1911 		internal class PointsDrawingOrderComparer : IComparer
1912 		{
1913 			/// <summary>
1914 			/// Chart area object reference.
1915 			/// </summary>
1916 			private	ChartArea	_area = null;
1917 
1918 			/// <summary>
1919 			/// Area X position where visible sides are switched.
1920 			/// </summary>
1921 			private	Point3D		_areaProjectionCenter = new Point3D(float.NaN, float.NaN, float.NaN);
1922 
1923 			/// <summary>
1924 			/// Selection mode. Points order should be reversed.
1925 			/// </summary>
1926 			private bool		_selection = false;
1927 
1928             /// <summary>
1929             /// Public constructor.
1930             /// </summary>
1931             /// <param name="area">Chart area.</param>
1932             /// <param name="selection">Selection indicator.</param>
1933             /// <param name="coord">Which coordinate of COP (X, Y or Z) to test for surface overlapping</param>
PointsDrawingOrderComparer(ChartArea area, bool selection, COPCoordinates coord)1934 			public PointsDrawingOrderComparer(ChartArea	area, bool selection, COPCoordinates coord)
1935 			{
1936 				this._area = area;
1937 				this._selection = selection;
1938 
1939 				// Get center of projection
1940 				if(area.DrawPointsToCenter(ref coord))
1941 				{
1942 					_areaProjectionCenter = area.GetCenterOfProjection(coord);
1943 				}
1944 			}
1945 
1946             /// <summary>
1947             /// Comparer method.
1948             /// </summary>
1949             /// <param name="o1">First object.</param>
1950             /// <param name="o2">Second object.</param>
1951             /// <returns>Comparison result.</returns>
Compare(object o1, object o2)1952 			public int Compare(object o1, object o2)
1953 			{
1954 				DataPoint3D point1 = (DataPoint3D) o1;
1955 				DataPoint3D point2 = (DataPoint3D) o2;
1956 
1957 				int	result = 0;
1958 				if(point1.xPosition < point2.xPosition)
1959 				{
1960 					result = -1;
1961 				}
1962 				else if(point1.xPosition > point2.xPosition)
1963 				{
1964 					result = 1;
1965 				}
1966 				else
1967 				{
1968 
1969 					// If X coordinate is the same - filter by Y coordinate
1970 					if(point1.yPosition < point2.yPosition)
1971 					{
1972 						result = 1;
1973 					}
1974 					else if(point1.yPosition > point2.yPosition)
1975 					{
1976 						result = -1;
1977 					}
1978 
1979 					// Order points from sides to center
1980 					if(!float.IsNaN(_areaProjectionCenter.Y))
1981 					{
1982 						double yMin1 = Math.Min(point1.yPosition, point1.height);
1983 						double yMax1 = Math.Max(point1.yPosition, point1.height);
1984 						double yMin2 = Math.Min(point2.yPosition, point2.height);
1985 						double yMax2 = Math.Max(point2.yPosition, point2.height);
1986 
1987 						if(_area.IsBottomSceneWallVisible())
1988 						{
1989 							if( yMin1 <= _areaProjectionCenter.Y && yMin2 <= _areaProjectionCenter.Y )
1990 							{
1991 								result *= -1;
1992 							}
1993 							else if( yMin1 <= _areaProjectionCenter.Y)
1994 							{
1995 								result = 1;
1996 							}
1997 
1998 						}
1999 						else
2000 						{
2001 
2002 							if( yMax1 >= _areaProjectionCenter.Y && yMax2 >= _areaProjectionCenter.Y )
2003 							{
2004 								result *= 1;
2005 							}
2006 							else if( yMax1 >= _areaProjectionCenter.Y)
2007 							{
2008 								result = 1;
2009 							}
2010 							else
2011 							{
2012 								result *= -1;
2013 							}
2014 						}
2015 					}
2016 
2017 						// Reversed order if looking from the bottom
2018 					else if(!_area.IsBottomSceneWallVisible())
2019 					{
2020 						result *= -1;
2021 					}
2022 
2023 				}
2024 
2025 				if(point1.xPosition != point2.xPosition)
2026 				{
2027 					// Order points from sides to center
2028 					if (!float.IsNaN(_areaProjectionCenter.X))
2029 					{
2030                         if ((point1.xPosition + point1.width / 2f) >= _areaProjectionCenter.X &&
2031                             (point2.xPosition + point2.width / 2f) >= _areaProjectionCenter.X)
2032 						{
2033 							result *= -1;
2034 						}
2035 					}
2036 
2037 					// Reversed order of points by X value
2038                     else if (_area.DrawPointsInReverseOrder())
2039 					{
2040 						result *= -1;
2041 					}
2042 				}
2043 
2044                 return (_selection) ? -result : result;
2045 			}
2046 		}
2047 
2048 #endregion
2049 
2050 		#region Center of Projection calculation methods
2051 
2052 		/// <summary>
2053 		/// Returns one or many (X, Y, Z) coordinates of the center of projection.
2054 		/// </summary>
2055 		/// <param name="coord">Defines coordinates to return.</param>
2056 		/// <returns>Center of projection. Value can be set to float.NaN if not defined or outside plotting area.</returns>
GetCenterOfProjection(COPCoordinates coord)2057 		internal Point3D GetCenterOfProjection(COPCoordinates coord)
2058 		{
2059 			// Define 2 points in the opposite corners of the plotting area
2060 			Point3D[]	points = new Point3D[2];
2061 			points[0] = new Point3D(this.PlotAreaPosition.X, this.PlotAreaPosition.Bottom, 0f);
2062 			points[1] = new Point3D(this.PlotAreaPosition.Right, this.PlotAreaPosition.Y, this.areaSceneDepth);
2063 
2064 			// Check if surfaces (points 1 & 2) has same orientation
2065 			bool	xSameOrientation, ySameOrientation, zSameOrientation;
2066 			CheckSurfaceOrientation(
2067 				coord,
2068 				points[0],
2069 				points[1],
2070 				out xSameOrientation,
2071 				out ySameOrientation,
2072 				out zSameOrientation);
2073 
2074 			// If orientation of all surfaces is the same - no futher processing is required (COP is outside of plotting area)
2075 			Point3D		resultPoint = new Point3D(
2076 				(xSameOrientation) ? float.NaN : 0f,
2077 				(ySameOrientation) ? float.NaN : 0f,
2078 				(zSameOrientation) ? float.NaN : 0f);
2079 			if( ( ((coord & COPCoordinates.X) != COPCoordinates.X) || xSameOrientation ) &&
2080 				( ((coord & COPCoordinates.Y) != COPCoordinates.Y) || ySameOrientation ) &&
2081 				( ((coord & COPCoordinates.Z) != COPCoordinates.Z) || zSameOrientation ) )
2082 			{
2083 				return resultPoint;
2084 			}
2085 
2086 			// Calculate the smallest interval (0.5 pixels) in relative coordinates
2087 			SizeF	interval = new SizeF(0.5f, 0.5f);
2088 #if WINFORMS_CONTROL
2089 			interval.Width = interval.Width * 100F / ((float)(this.Common.Chart.Width - 1));
2090 			interval.Height = interval.Height * 100F / ((float)(this.Common.Chart.Height - 1));
2091 #else
2092 			interval.Width = interval.Width * 100F / ((float)(this.Common.Chart.Width.Value - 1));
2093 			interval.Height = interval.Height * 100F / ((float)(this.Common.Chart.Height.Value - 1));
2094 #endif	//#if WINFORMS_CONTROL
2095 
2096 			// Find middle point and check it's surface orientation
2097 			bool	doneFlag = false;
2098 			while(!doneFlag)
2099 			{
2100 				// Find middle point
2101 				Point3D	middlePoint = new Point3D(
2102 					(points[0].X + points[1].X) / 2f,
2103 					(points[0].Y + points[1].Y) / 2f,
2104 					(points[0].Z + points[1].Z) / 2f);
2105 
2106 				// Check if surfaces (points 1 & middle) has same orientation
2107 				CheckSurfaceOrientation(
2108 					coord,
2109 					points[0],
2110 					middlePoint,
2111 					out xSameOrientation,
2112 					out ySameOrientation,
2113 					out zSameOrientation);
2114 
2115 				// Calculate points 1 & 2 depending on surface orientation of the middle point
2116 				points[(xSameOrientation) ? 0 : 1].X = middlePoint.X;
2117 				points[(ySameOrientation) ? 0 : 1].Y = middlePoint.Y;
2118 				points[(zSameOrientation) ? 0 : 1].Z = middlePoint.Z;
2119 
2120 				// Check if no more calculations required
2121 				doneFlag = true;
2122 				if( (coord & COPCoordinates.X) == COPCoordinates.X &&
2123 					Math.Abs(points[1].X - points[0].X) >= interval.Width)
2124 				{
2125 					doneFlag = false;
2126 				}
2127 				if( (coord & COPCoordinates.Y) == COPCoordinates.Y &&
2128 					Math.Abs(points[1].Y - points[0].Y) >= interval.Height)
2129 				{
2130 					doneFlag = false;
2131 				}
2132 				if( (coord & COPCoordinates.Z) == COPCoordinates.Z &&
2133 					Math.Abs(points[1].Z - points[0].Z) >= interval.Width)
2134 				{
2135 					doneFlag = false;
2136 				}
2137 			}
2138 
2139 			// Calculate result point
2140 			if(!float.IsNaN(resultPoint.X))
2141 				resultPoint.X = (points[0].X + points[1].X) / 2f;
2142 			if(!float.IsNaN(resultPoint.Y))
2143 				resultPoint.Y = (points[0].Y + points[1].Y) / 2f;
2144 			if(!float.IsNaN(resultPoint.Z))
2145 				resultPoint.Z = (points[0].Z + points[1].Z) / 2f;
2146 			return resultPoint;
2147 		}
2148 
2149 		/// <summary>
2150 		/// Checks orientations of two normal surfaces for each coordinate X, Y and Z.
2151 		/// </summary>
2152 		/// <param name="coord">Defines coordinates to return.</param>
2153 		/// <param name="point1">First point.</param>
2154 		/// <param name="point2">Second point.</param>
2155 		/// <param name="xSameOrientation">X surfaces orientation is the same.</param>
2156 		/// <param name="ySameOrientation">Y surfaces orientation is the same.</param>
2157 		/// <param name="zSameOrientation">Z surfaces orientation is the same.</param>
CheckSurfaceOrientation( COPCoordinates coord, Point3D point1, Point3D point2, out bool xSameOrientation, out bool ySameOrientation, out bool zSameOrientation)2158 		private void CheckSurfaceOrientation(
2159 			COPCoordinates coord,
2160 			Point3D point1,
2161 			Point3D point2,
2162 			out bool xSameOrientation,
2163 			out bool ySameOrientation,
2164 			out bool zSameOrientation)
2165 		{
2166 			Point3D[]	pointsSurface = new Point3D[3];
2167 			bool		surf1, surf2;
2168 
2169 			// Initialize returned values
2170 			xSameOrientation = true;
2171 			ySameOrientation = true;
2172 			zSameOrientation = true;
2173 
2174 			// Check X axis coordinates (ledt & right surfaces)
2175 			if( (coord & COPCoordinates.X) == COPCoordinates.X )
2176 			{
2177 				// Define Left surface coordinates, transform them and check visibility
2178 				pointsSurface[0] = new Point3D(point1.X, this.PlotAreaPosition.Y, 0f);
2179 				pointsSurface[1] = new Point3D(point1.X, this.PlotAreaPosition.Bottom, 0f);
2180 				pointsSurface[2] = new Point3D(point1.X, this.PlotAreaPosition.Bottom, this.areaSceneDepth);
2181 				this.matrix3D.TransformPoints( pointsSurface );
2182 				surf1 = ChartGraphics.IsSurfaceVisible(pointsSurface[0], pointsSurface[1], pointsSurface[2]);
2183 
2184 				// Define Right surface coordinates, transform them and check visibility
2185 				pointsSurface[0] = new Point3D(point2.X, this.PlotAreaPosition.Y, 0f);
2186 				pointsSurface[1] = new Point3D(point2.X, this.PlotAreaPosition.Bottom, 0f);
2187 				pointsSurface[2] = new Point3D(point2.X, this.PlotAreaPosition.Bottom, this.areaSceneDepth);
2188 				this.matrix3D.TransformPoints( pointsSurface );
2189 				surf2 = ChartGraphics.IsSurfaceVisible(pointsSurface[0], pointsSurface[1], pointsSurface[2]);
2190 
2191 				// Check if surfaces have same visibility
2192 				xSameOrientation = (surf1 == surf2);
2193 			}
2194 
2195 			// Check Y axis coordinates (top & bottom surfaces)
2196 			if( (coord & COPCoordinates.Y) == COPCoordinates.Y )
2197 			{
2198 				// Define Bottom surface coordinates, transform them and check visibility
2199 				pointsSurface[0] = new Point3D(this.PlotAreaPosition.X, point1.Y, this.areaSceneDepth);
2200 				pointsSurface[1] = new Point3D(this.PlotAreaPosition.X, point1.Y, 0f);
2201 				pointsSurface[2] = new Point3D(this.PlotAreaPosition.Right, point1.Y, 0f);
2202 				this.matrix3D.TransformPoints( pointsSurface );
2203 				surf1 = ChartGraphics.IsSurfaceVisible(pointsSurface[0], pointsSurface[1], pointsSurface[2]);
2204 
2205 				// Define Top surface coordinates, transform them and check visibility
2206 				pointsSurface[0] = new Point3D(this.PlotAreaPosition.X, point2.Y, this.areaSceneDepth);
2207 				pointsSurface[1] = new Point3D(this.PlotAreaPosition.X, point2.Y, 0f);
2208 				pointsSurface[2] = new Point3D(this.PlotAreaPosition.Right, point2.Y, 0f);
2209 				this.matrix3D.TransformPoints( pointsSurface );
2210 				surf2 = ChartGraphics.IsSurfaceVisible(pointsSurface[0], pointsSurface[1], pointsSurface[2]);
2211 
2212 				// Check if surfaces have same visibility
2213 				ySameOrientation = (surf1 == surf2);
2214 			}
2215 
2216 			// Check Y axis coordinates (front & back surfaces)
2217 			if( (coord & COPCoordinates.Z) == COPCoordinates.Z )
2218 			{
2219 				// Define Front surface coordinates, transform them and check visibility
2220 				pointsSurface[0] = new Point3D(this.PlotAreaPosition.X, this.PlotAreaPosition.Y, point1.Z);
2221 				pointsSurface[1] = new Point3D(this.PlotAreaPosition.X, this.PlotAreaPosition.Bottom, point1.Z);
2222 				pointsSurface[2] = new Point3D(this.PlotAreaPosition.Right, this.PlotAreaPosition.Bottom, point1.Z);
2223 				this.matrix3D.TransformPoints( pointsSurface );
2224 				surf1 = ChartGraphics.IsSurfaceVisible(pointsSurface[0], pointsSurface[1], pointsSurface[2]);
2225 
2226 				// Define Back surface coordinates, transform them and check visibility
2227 				pointsSurface[0] = new Point3D(this.PlotAreaPosition.X, this.PlotAreaPosition.Y, point2.Z);
2228 				pointsSurface[1] = new Point3D(this.PlotAreaPosition.X, this.PlotAreaPosition.Bottom, point2.Z);
2229 				pointsSurface[2] = new Point3D(this.PlotAreaPosition.Right, this.PlotAreaPosition.Bottom, point2.Z);
2230 				this.matrix3D.TransformPoints( pointsSurface );
2231 				surf2 = ChartGraphics.IsSurfaceVisible(pointsSurface[0], pointsSurface[1], pointsSurface[2]);
2232 
2233 				// Check if surfaces have same visibility
2234 				zSameOrientation = (surf1 == surf2);
2235 			}
2236 		}
2237 #endregion
2238 	}
2239 }
2240