1 //-------------------------------------------------------------
2 // <copyright company=�Microsoft Corporation�>
3 //   Copyright � Microsoft Corporation. All Rights Reserved.
4 // </copyright>
5 //-------------------------------------------------------------
6 // @owner=alexgor, deliant
7 //=================================================================
8 //  File:		ChartAreaAxes.cs
9 //
10 //  Namespace:	System.Web.UI.WebControls[Windows.Forms].Charting
11 //
12 //	Classes:	ChartAreaAxes
13 //
14 //  Purpose:	ChartAreaAxes is base class of Chart Area class.
15 //				This class searches for all series, which belongs
16 //				to this chart area and sets axes minimum and
17 //				maximum values using data. This class also checks
18 //				for chart types, which belong to this chart area
19 //				and prepare axis scale according to them (Stacked
20 //				chart types have different max and min values).
21 //				This class recognizes indexed values and prepares
22 //				axes for them.
23 //
24 //	Reviewed:	GS - Jul 31, 2002
25 //				AG - August 7, 2002
26 //
27 //===================================================================
28 
29 #region Used namespaces
30 
31 using System;
32 using System.Collections;
33 using System.Collections.Generic;
34 
35 #if Microsoft_CONTROL
36 	using System.Windows.Forms.DataVisualization.Charting;
37 	using System.Windows.Forms.DataVisualization.Charting.Data;
38 	using System.Windows.Forms.DataVisualization.Charting.ChartTypes;
39 	using System.Windows.Forms.DataVisualization.Charting.Utilities;
40 	using System.Windows.Forms.DataVisualization.Charting.Borders3D;
41 #else
42 	using System.Web.UI.DataVisualization.Charting.Data;
43 	using System.Web.UI.DataVisualization.Charting.ChartTypes;
44 #endif
45 
46 #endregion
47 
48 #if Microsoft_CONTROL
49 namespace System.Windows.Forms.DataVisualization.Charting
50 #else
51 namespace System.Web.UI.DataVisualization.Charting
52 #endif
53 {
54 	/// <summary>
55     /// ChartAreaAxes class represents axes (X, Y, X2 and Y2) in the chart area.
56     /// It contains methods that collect statistical information on the series data and
57     /// other axes related methods.
58 	/// </summary>
59 	public partial class ChartArea
60 	{
61 		#region Fields
62 
63 		// Axes which belong to this Chart Area
64 		internal Axis					axisY = null;
65 		internal Axis					axisX = null;
66 		internal Axis					axisX2 = null;
67 		internal Axis					axisY2 = null;
68 
69 		// Array of series which belong to this chart area
70 		private List<string>		    _series =		new List<string>();
71 
72 		// Array of chart types which belong to this chart area
73 		internal ArrayList				chartTypes =	new ArrayList();
74 
75 		/// <summary>
76 		/// List of series names that last interval numbers where cashed for
77 		/// </summary>
78 		private	 string					_intervalSeriesList = "";
79 
80 		// Minimum interval between two data points for all
81 		// series which belong to this chart area.
82 		internal double					intervalData = double.NaN;
83 
84 		// Minimum interval between two data points for all
85 		// series which belong to this chart area.
86 		// IsLogarithmic version of the interval.
87 		internal double					intervalLogData = double.NaN;
88 
89 		// Series with minimum interval between two data points for all
90 		// series which belong to this chart area.
91 		private Series					_intervalSeries = null;
92 
93 		// Indicates that points are located through equal X intervals
94 		internal bool					intervalSameSize = false;
95 
96 		// Indicates that points alignment checked
97 		internal bool					diffIntervalAlignmentChecked = false;
98 
99 		// Chart Area contains stacked chart types
100 		internal bool					stacked = false;
101 
102 		// Chart type with two y values used for scale ( bubble chart type )
103 		internal bool					secondYScale = false;
104 
105 		// The X and Y axes are switched
106 		internal bool					switchValueAxes = false;
107 
108 		// True for all chart types, which have axes. False for doughnut and pie chart.
109 		internal bool					requireAxes = true;
110 
111 		// Indicates that chart area has circular shape (like in radar or polar chart)
112 		internal bool					chartAreaIsCurcular = false;
113 
114 		// Chart Area contains 100 % stacked chart types
115 		internal bool					hundredPercent = false;
116 
117 		// Chart Area contains 100 % stacked chart types
118 		internal bool					hundredPercentNegative = false;
119 
120 		#endregion
121 
122 		#region Internal properties
123 
124 		/// <summary>
125 		/// True if sub axis supported on this chart area
126 		/// </summary>
127 		internal bool IsSubAxesSupported
128 		{
129 			get
130 			{
131 				if(((ChartArea)this).Area3DStyle.Enable3D ||
132 					((ChartArea)this).chartAreaIsCurcular)
133 				{
134 					return false;
135 				}
136 				return true;
137 			}
138 		}
139 
140 		/// <summary>
141 		/// Data series which belongs to this chart area.
142 		/// </summary>
143 		internal List<string> Series
144 		{
145 			get
146 			{
147 				return _series;
148 			}
149 		}
150 
151 		/// <summary>
152 		/// Chart types which belongs to this chart area.
153 		/// </summary>
154 		internal ArrayList ChartTypes
155 		{
156 			get
157 			{
158 				return chartTypes;
159 			}
160 		}
161 
162 		#endregion
163 
164 		#region Methods
165 
166 		/// <summary>
167 		/// Gets main or sub axis from the chart area.
168 		/// </summary>
169 		/// <param name="axisName">Axis name. NOTE: This parameter only defines X or Y axis.
170         /// Second axisType parameter is used to select primary or secondary axis. </param>
171 		/// <param name="axisType">Axis type.</param>
172 		/// <param name="subAxisName">Sub-axis name or empty string.</param>
173 		/// <returns>Main or sub axis of the chart area.</returns>
GetAxis(AxisName axisName, AxisType axisType, string subAxisName)174 		internal Axis GetAxis(AxisName axisName, AxisType axisType, string subAxisName)
175 		{
176 			// Ignore sub axis in 3D
177 			if( ((ChartArea)this).Area3DStyle.Enable3D)
178 			{
179 				subAxisName = string.Empty;
180 			}
181 
182 			if(axisName == AxisName.X || axisName == AxisName.X2)
183 			{
184 				if(axisType == AxisType.Primary)
185 				{
186 					return ((ChartArea)this).AxisX.GetSubAxis(subAxisName);
187 				}
188 				return ((ChartArea)this).AxisX2.GetSubAxis(subAxisName);
189 			}
190 			else
191 			{
192 				if(axisType == AxisType.Primary)
193 				{
194 					return ((ChartArea)this).AxisY.GetSubAxis(subAxisName);
195 				}
196 				return ((ChartArea)this).AxisY2.GetSubAxis(subAxisName);
197 			}
198 		}
199 
200 		/// <summary>
201 		/// Sets default axis values for all different chart type
202 		/// groups. Chart type groups are sets of chart types.
203 		/// </summary>
SetDefaultAxesValues( )204 		internal void SetDefaultAxesValues( )
205 		{
206 			// The X and Y axes are switched ( Bar chart, stacked bar ... )
207 			if( switchValueAxes )
208 			{
209 				// Set axis positions
210 				axisY.AxisPosition = AxisPosition.Bottom;
211 				axisX.AxisPosition = AxisPosition.Left;
212 				axisX2.AxisPosition = AxisPosition.Right;
213 				axisY2.AxisPosition = AxisPosition.Top;
214 			}
215 			else
216 			{
217 				// Set axis positions
218 				axisY.AxisPosition = AxisPosition.Left;
219 				axisX.AxisPosition = AxisPosition.Bottom;
220 				axisX2.AxisPosition = AxisPosition.Top;
221 				axisY2.AxisPosition = AxisPosition.Right;
222 			}
223 
224 			// Reset opposite Axes field. This cashing
225 			// value is used for optimization.
226 			foreach( Axis axisItem in ((ChartArea)this).Axes )
227 			{
228                 axisItem.oppositeAxis = null;
229 #if SUBAXES
230 				foreach( SubAxis subAxisItem in axisItem.SubAxes )
231 				{
232 					subAxisItem.m_oppositeAxis = null;
233 				}
234 #endif // SUBAXES
235             }
236 
237 			// ***********************
238 			// Primary X Axes
239 			// ***********************
240 			// Find the  number  of series which belong to this axis
241             if (this.chartAreaIsCurcular)
242             {
243                 // Set axis Maximum/Minimum and Interval for circular chart
244                 axisX.SetAutoMaximum(360.0);
245                 axisX.SetAutoMinimum(0.0);
246                 axisX.SetInterval = Math.Abs(axisX.maximum - axisX.minimum) / 12.0;
247             }
248             else
249             {
250                 SetDefaultFromIndexesOrData(axisX, AxisType.Primary);
251             }
252 
253 #if SUBAXES
254 			// ***********************
255 			// Primary X Sub-Axes
256 			// ***********************
257 			foreach(SubAxis subAxis in axisX.SubAxes)
258 			{
259                 SetDefaultFromIndexesOrData(subAxis, AxisType.Primary);
260 			}
261 #endif // SUBAXES
262 
263             // ***********************
264 			// Secondary X Axes
265 			// ***********************
266             SetDefaultFromIndexesOrData(axisX2, AxisType.Secondary);
267 
268 #if SUBAXES
269 			// ***********************
270 			// Secondary X Sub-Axes
271 			// ***********************
272 			foreach(SubAxis subAxis in axisX2.SubAxes)
273 			{
274                 SetDefaultFromIndexesOrData(subAxis, AxisType.Secondary);
275 			}
276 #endif // SUBAXES
277 
278             // ***********************
279 			// Primary Y axis
280 			// ***********************
281 			if( GetYAxesSeries( AxisType.Primary, string.Empty ).Count != 0 )
282 			{
283 				// Find minimum and maximum from Y values.
284 				SetDefaultFromData( axisY );
285 				axisY.EstimateAxis();
286             }
287 
288 #if SUBAXES
289 			// ***********************
290 			// Primary Y Sub-Axes
291 			// ***********************
292 			foreach(SubAxis subAxis in axisY.SubAxes)
293 			{
294 				// Find the  number  of series which belong to this axis
295 				if( GetYAxesSeries( AxisType.Primary, subAxis.SubAxisName ).Count != 0 )
296 				{
297 					// Find minimum and maximum from Y values.
298 					SetDefaultFromData( subAxis );
299 					subAxis.EstimateAxis();
300 				}
301 			}
302 #endif // SUBAXES
303 
304             // ***********************
305 			// Secondary Y axis
306 			// ***********************
307 			if( GetYAxesSeries( AxisType.Secondary, string.Empty ).Count != 0 )
308 			{
309 				// Find minimum and maximum from Y values.
310 				SetDefaultFromData( axisY2 );
311 				axisY2.EstimateAxis();
312             }
313 
314 #if SUBAXES
315 			// ***********************
316 			// Secondary Y Sub-Axes
317 			// ***********************
318 			foreach(SubAxis subAxis in axisY2.SubAxes)
319 			{
320 				// Find the  number  of series which belong to this axis
321 				if( GetYAxesSeries( AxisType.Secondary, subAxis.SubAxisName ).Count != 0 )
322 				{
323 					// Find minimum and maximum from Y values.
324 					SetDefaultFromData( subAxis );
325 					subAxis.EstimateAxis();
326 				}
327 			}
328 #endif // SUBAXES
329 
330             // Sets axis position. Axis position depends
331 			// on crossing and reversed value.
332 			axisX.SetAxisPosition();
333 			axisX2.SetAxisPosition();
334 			axisY.SetAxisPosition();
335 			axisY2.SetAxisPosition();
336 
337 			// Enable axes, which are
338 			// used in data series.
339 			this.EnableAxes();
340 
341 
342 
343 
344 			// Get scale break segments
345 			Axis[] axesYArray = new Axis[] { axisY, axisY2 };
346 			foreach(Axis currentAxis in axesYArray)
347 			{
348 				// Get automatic scale break segments
349 				currentAxis.ScaleBreakStyle.GetAxisSegmentForScaleBreaks(currentAxis.ScaleSegments);
350 
351 				// Make sure axis scale do not exceed segments scale
352 				if(currentAxis.ScaleSegments.Count > 0)
353 				{
354 					// Save flag that scale segments are used
355 					currentAxis.scaleSegmentsUsed = true;
356 
357 					if(currentAxis.minimum < currentAxis.ScaleSegments[0].ScaleMinimum)
358 					{
359 						currentAxis.minimum = currentAxis.ScaleSegments[0].ScaleMinimum;
360 					}
361 					if(currentAxis.minimum > currentAxis.ScaleSegments[currentAxis.ScaleSegments.Count - 1].ScaleMaximum)
362 					{
363 						currentAxis.minimum = currentAxis.ScaleSegments[currentAxis.ScaleSegments.Count - 1].ScaleMaximum;
364 					}
365 				}
366 			}
367 
368 
369 
370 			bool useScaleSegments = false;
371 
372 			// Fill Labels
373 			Axis[] axesArray = new Axis[] { axisX, axisX2, axisY, axisY2 };
374 			foreach(Axis currentAxis in axesArray)
375 			{
376 
377 				useScaleSegments = (currentAxis.ScaleSegments.Count > 0);
378 
379 				if(!useScaleSegments)
380 				{
381 					currentAxis.FillLabels(true);
382 				}
383 
384 				else
385 				{
386 					bool removeLabels = true;
387 					int segmentIndex = 0;
388 					foreach(AxisScaleSegment scaleSegment in currentAxis.ScaleSegments)
389 					{
390 						scaleSegment.SetTempAxisScaleAndInterval();
391 
392 						currentAxis.FillLabels(removeLabels);
393 						removeLabels = false;
394 
395 						scaleSegment.RestoreAxisScaleAndInterval();
396 
397 						// Remove last label for all segments except of the last
398 						if(segmentIndex < (currentAxis.ScaleSegments.Count - 1) &&
399 							currentAxis.CustomLabels.Count > 0)
400 						{
401 							currentAxis.CustomLabels.RemoveAt(currentAxis.CustomLabels.Count - 1);
402 						}
403 
404 						++segmentIndex;
405 					}
406 				}
407 
408 			}
409             foreach (Axis currentAxis in axesArray)
410             {
411                 currentAxis.PostFillLabels();
412             }
413 		}
414 
415         /// <summary>
416         /// Sets the axis defaults.
417         /// If the at least one of the series bound to this axis is Indexed then the defaults are set using the SetDefaultsFromIndexes().
418         /// Otherwise the SetDefaultFromData() is used.
419         /// </summary>
420         /// <param name="axis">Axis to process</param>
421         /// <param name="axisType">Axis type</param>
SetDefaultFromIndexesOrData(Axis axis, AxisType axisType)422         private void SetDefaultFromIndexesOrData(Axis axis, AxisType axisType)
423         {
424             //Get array of the series that are linked to this axis
425             List<string> axisSeriesNames = GetXAxesSeries(axisType, axis.SubAxisName);
426             // VSTS: 196381
427             // before this change: If we find one indexed series we will treat all series as indexed.
428             // after this change : We will assume that all series are indexed.
429             // If we find one non indexed series we will treat all series as non indexed.
430             bool indexedSeries = true;
431             // DT comments 1:
432             // If we have mix of indexed with non-indexed series
433             // enforce  all indexed series as non-indexed;
434             // The result of mixed type of series will be more natural
435             // and easy to detect the problem - all datapoints of indexed
436             // series will be displayed on zero position.
437             //=====================================
438             // bool  nonIndexedSeries = false;
439             //=======================================
440             //Loop through the series looking for a indexed one
441             foreach(string seriesName in axisSeriesNames)
442             {
443                 // Get series
444                 Series series = Common.DataManager.Series[seriesName];
445                 // Check if series is indexed
446                 if (!ChartHelper.IndexedSeries(series))
447                 {
448                     // found one nonindexed series - we will treat all series as non indexed.
449                     indexedSeries = false;
450                     break;
451                 }
452                 // DT comments 2
453                 //else
454                 //{
455                 //    nonIndexedSeries = true;
456                 //}
457             }
458 
459             //DT comments 3
460             //if (!indexedSeries && nonIndexedSeries)
461             //{
462             //    foreach (string seriesName in axisSeriesNames)
463             //    {
464             //        // Get series
465             //        Series series = Common.DataManager.Series[seriesName];
466             //        series.xValuesZeros = false;
467             //    }
468             //}
469 
470             if (indexedSeries)
471             {
472                 if (axis.IsLogarithmic)
473                 {
474                     throw (new InvalidOperationException(SR.ExceptionChartAreaAxisScaleLogarithmicUnsuitable));
475                 }
476                 //Set axis defaults from the indexed series
477                 SetDefaultFromIndexes(axis);
478                 //We are done...
479                 return;
480             }
481 
482            // If haven't found any indexed series -> Set axis defaults from the series data
483            SetDefaultFromData(axis);
484            axis.EstimateAxis();
485         }
486 
487 		/// <summary>
488 		/// Enable axes, which are
489 		/// used in chart area data series.
490 		/// </summary>
EnableAxes()491 		private void EnableAxes()
492 		{
493 			if( _series == null )
494 			{
495 				return;
496 			}
497 
498 			bool activeX = false;
499 			bool activeY = false;
500 			bool activeX2 = false;
501 			bool activeY2 = false;
502 
503 			// Data series from this chart area
504 			foreach( string ser in _series )
505 			{
506 				Series	dataSeries = Common.DataManager.Series[ ser ];
507 
508 				// X axes
509 				if( dataSeries.XAxisType == AxisType.Primary )
510 				{
511 					activeX = true;
512 #if SUBAXES
513 					this.Activate( axisX, true, dataSeries.XSubAxisName );
514 #else
515                     this.Activate( axisX, true );
516 #endif // SUBAXES
517 
518                 }
519 				else
520 				{
521 					activeX2 = true;
522 #if SUBAXES
523 					this.Activate( axisX2, true, dataSeries.XSubAxisName );
524 #else
525                     this.Activate( axisX2, true );
526 #endif // SUBAXES
527                 }
528 				// Y axes
529 				if( dataSeries.YAxisType == AxisType.Primary )
530 				{
531 					activeY = true;
532 #if SUBAXES
533 					this.Activate( axisY, true, dataSeries.YSubAxisName );
534 #else
535                     this.Activate( axisY, true );
536 #endif // SUBAXES
537                 }
538 				else
539 				{
540 					activeY2 = true;
541 #if SUBAXES
542 					this.Activate( axisY2, true, dataSeries.YSubAxisName );
543 #else
544                     this.Activate( axisY2, true );
545 #endif // SUBAXES
546                 }
547             }
548 
549 #if SUBAXES
550 			// Enable Axes
551 			if(!activeX)
552 				this.Activate( axisX, false, string.Empty );
553 			if(!activeY)
554 				this.Activate( axisY, false, string.Empty );
555 			if(!activeX2)
556 				this.Activate( axisX2, false, string.Empty );
557 			if(!activeY2)
558 				this.Activate( axisY2, false, string.Empty );
559 #else // SUBAXES
560             // Enable Axes
561 			if(!activeX)
562 				this.Activate( axisX, false);
563 			if(!activeY)
564 				this.Activate( axisY, false);
565 			if(!activeX2)
566 				this.Activate( axisX2, false);
567 			if(!activeY2)
568 				this.Activate( axisY2, false);
569 #endif // SUBAXES
570         }
571 
572 #if SUBAXES
573 
574 		/// <summary>
575 		/// Enable axis.
576 		/// </summary>
577 		/// <param name="axis">Axis.</param>
578 		/// <param name="active">True if axis is active.</param>
579 		/// <param name="subAxisName">Sub axis name to activate.</param>
Activate( Axis axis, bool active, string subAxisName )580 		private void Activate( Axis axis, bool active, string subAxisName )
581 		{
582 			// Auto-Enable axis
583 			if( axis.autoEnabled == true )
584 			{
585 				axis.enabled = active;
586 			}
587 
588 			// Auto-Enable sub axes
589 			if(subAxisName.Length > 0)
590 			{
591 				SubAxis subAxis = axis.SubAxes.FindByName(subAxisName);
592 				if(subAxis != null)
593 				{
594 					if( subAxis.autoEnabled == true )
595 					{
596 						subAxis.enabled = active;
597 					}
598 				}
599 			}
600 		}
601 #else
602         /// <summary>
603 		/// Enable axis.
604 		/// </summary>
605 		/// <param name="axis">Axis.</param>
606 		/// <param name="active">True if axis is active.</param>
Activate( Axis axis, bool active )607 		private void Activate( Axis axis, bool active )
608 		{
609 			if( axis.autoEnabled == true )
610 			{
611 				axis.enabled = active;
612 			}
613         }
614 #endif // SUBAXES
615 
616         /// <summary>
617 		/// Check if all data points from series in
618 		/// this chart area are empty.
619 		/// </summary>
620 		/// <returns>True if all points are empty</returns>
AllEmptyPoints()621 		bool AllEmptyPoints()
622 		{
623 			// Data series from this chart area
624 			foreach( string seriesName in this._series )
625 			{
626 				Series	dataSeries = Common.DataManager.Series[ seriesName ];
627 
628 				// Data point loop
629 				foreach( DataPoint point in dataSeries.Points )
630 				{
631 					if( !point.IsEmpty )
632 					{
633 						return false;
634 					}
635 				}
636 			}
637 			return true;
638 		}
639 
640 		/// <summary>
641 		/// This method sets default minimum and maximum
642 		/// values from values in the data manager. This
643 		/// case is used if X values are not equal to 0 or IsXValueIndexed flag is set.
644 		/// </summary>
645 		/// <param name="axis">Axis</param>
SetDefaultFromData( Axis axis )646 		private void SetDefaultFromData( Axis axis )
647         {
648 #if SUBAXES
649 			// Process all sub-axes
650 			if(!axis.IsSubAxis)
651 			{
652 				foreach(SubAxis subAxis in axis.SubAxes)
653 				{
654 					this.SetDefaultFromData( subAxis );
655 				}
656 			}
657 #endif // SUBAXES
658 
659 
660             // Used for scrolling with logarithmic axes.
661 			if( !Double.IsNaN(axis.ScaleView.Position) &&
662 				!Double.IsNaN(axis.ScaleView.Size) &&
663 				!axis.refreshMinMaxFromData &&
664 				axis.IsLogarithmic )
665 			{
666 				return;
667 			}
668 
669 			// Get minimum and maximum from data source
670 			double autoMaximum;
671 			double autoMinimum;
672 			this.GetValuesFromData( axis, out autoMinimum, out autoMaximum );
673 
674 			// ***************************************************
675 			// This part of code is used to add a margin to the
676 			// axis and to set minimum value to zero if
677 			// IsStartedFromZero property is used. There is special
678 			// code for logarithmic scale, which will set minimum
679 			// to one instead of zero.
680 			// ***************************************************
681 			// The minimum and maximum values from data manager don�t exist.
682 
683 			if( axis.enabled &&
684 				( (axis.AutoMaximum || double.IsNaN( axis.Maximum )) && (autoMaximum == Double.MaxValue || autoMaximum == Double.MinValue)) ||
685 				( (axis.AutoMinimum || double.IsNaN( axis.Minimum )) && (autoMinimum == Double.MaxValue || autoMinimum == Double.MinValue )) )
686 			{
687 				if( this.AllEmptyPoints() )
688 				{
689 					// Supress exception and use predefined min & max
690 					autoMaximum = 8.0;
691 					autoMinimum = 1.0;
692 				}
693 				else
694 				{
695 					if(!this.Common.ChartPicture.SuppressExceptions)
696 					{
697                         throw (new InvalidOperationException(SR.ExceptionAxisMinimumMaximumInvalid));
698 					}
699 				}
700 			}
701 
702 			// Axis margin used for zooming
703 			axis.marginView = 0.0;
704 			if( axis.margin == 100 && (axis.axisType == AxisName.X || axis.axisType == AxisName.X2) )
705 			{
706 				axis.marginView = this.GetPointsInterval( false, 10 );
707 			}
708 
709 			// If minimum and maximum are same margin always exist.
710 			if( autoMaximum == autoMinimum &&
711 				axis.Maximum == axis.Minimum )
712 			{
713 				axis.marginView = 1;
714 			}
715 
716 			// Do not make axis margine for logarithmic axes
717 			if( axis.IsLogarithmic )
718 			{
719 				axis.marginView = 0.0;
720 			}
721 
722 			// Adjust Maximum - Add a gap
723 			if( axis.AutoMaximum )
724 			{
725 				// Add a Gap for X axis
726 				if( !axis.roundedXValues && ( axis.axisType == AxisName.X || axis.axisType == AxisName.X2 ) )
727 				{
728 					axis.SetAutoMaximum( autoMaximum + axis.marginView );
729 				}
730 				else
731 				{
732 					if( axis.isStartedFromZero && autoMaximum < 0 )
733 					{
734 						axis.SetAutoMaximum( 0.0 );
735 					}
736 					else
737 					{
738 						axis.SetAutoMaximum( autoMaximum );
739 					}
740 				}
741 			}
742 
743 			// Adjust Minimum - make rounded values and add a gap
744 			if( axis.AutoMinimum )
745 			{
746 				// IsLogarithmic axis
747 				if( axis.IsLogarithmic )
748 				{
749 					if( autoMinimum < 1.0 )
750 					{
751 						axis.SetAutoMinimum( autoMinimum );
752 					}
753 					else if( axis.isStartedFromZero )
754 					{
755 						axis.SetAutoMinimum( 1.0 );
756 					}
757 					else
758 					{
759 						axis.SetAutoMinimum( autoMinimum );
760 					}
761 				}
762 				else
763 				{
764 					if( autoMinimum > 0.0 ) // If Auto calculated Minimum value is positive
765 					{
766 						// Adjust Minimum
767 						if( !axis.roundedXValues && ( axis.axisType == AxisName.X || axis.axisType == AxisName.X2 ) )
768 						{
769 							axis.SetAutoMinimum( autoMinimum - axis.marginView );
770 						}
771 						// If start From Zero property is true 0 is always on the axis.
772 						// NOTE: Not applicable if date-time values are drawn. Fixes issue #5644
773 						else if( axis.isStartedFromZero &&
774 							!this.SeriesDateTimeType( axis.axisType, axis.SubAxisName ) )
775 						{
776 							axis.SetAutoMinimum( 0.0 );
777 						}
778 						else
779 						{
780 							axis.SetAutoMinimum( autoMinimum );
781 						}
782 					}
783 					else // If Auto calculated Minimum value is non positive
784 					{
785 						if( axis.axisType == AxisName.X || axis.axisType == AxisName.X2 )
786 						{
787 							axis.SetAutoMinimum( autoMinimum - axis.marginView );
788 						}
789 						else
790 						{
791 							// If start From Zero property is true 0 is always on the axis.
792 							axis.SetAutoMinimum( autoMinimum );
793 						}
794 					}
795 				}
796 			}
797 
798 			// If maximum or minimum are not auto set value to non logarithmic
799 			if( axis.IsLogarithmic && axis.logarithmicConvertedToLinear )
800 			{
801 				if( !axis.AutoMinimum )
802 				{
803 					axis.minimum = axis.logarithmicMinimum;
804 				}
805 
806 				if( !axis.AutoMaximum )
807 				{
808 					axis.maximum = axis.logarithmicMaximum;
809 				}
810 				// Min and max will take real values again if scale is logarithmic.
811 				axis.logarithmicConvertedToLinear = false;
812 			}
813 
814 			// Check if Minimum == Maximum
815 			if(this.Common.ChartPicture.SuppressExceptions &&
816 				axis.maximum == axis.minimum)
817 			{
818 				axis.minimum = axis.maximum;
819 				axis.maximum = axis.minimum + 1.0;
820 			}
821 		}
822 
823 		/// <summary>
824 		/// This method checks if all series in the chart area have �integer type�
825 		/// for specified axes, which means int, uint, long and ulong.
826 		/// </summary>
827 		/// <param name="axisName">Name of the axis</param>
828 		/// <param name="subAxisName">Sub axis name.</param>
829 		/// <returns>True if all series are integer</returns>
830         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "subAxisName")]
SeriesIntegerType( AxisName axisName, string subAxisName )831         internal bool SeriesIntegerType( AxisName axisName, string subAxisName )
832 		{
833 			// Series which belong to this chart area
834 			foreach( string seriesName in this._series )
835 			{
836 				Series ser = Common.DataManager.Series[ seriesName ];
837 				// X axes type
838 				if( axisName == AxisName.X )
839                 {
840 #if SUBAXES
841 					if(	ser.XAxisType == AxisType.Primary && ser.XSubAxisName == subAxisName)
842 #else //SUBAXES
843                     if (	ser.XAxisType == AxisType.Primary)
844 #endif //SUBAXES
845                     {
846 						if(ser.XValueType != ChartValueType.Int32 &&
847 							ser.XValueType != ChartValueType.UInt32 &&
848 							ser.XValueType != ChartValueType.UInt64 &&
849 							ser.XValueType != ChartValueType.Int64 )
850 						{
851 							return false;
852 						}
853 						else
854 						{
855 							return true;
856 						}
857 					}
858 				}
859 				// X axes type
860 				else if( axisName == AxisName.X2 )
861                 {
862 #if SUBAXES
863 					if(	ser.XAxisType == AxisType.Secondary && ser.XSubAxisName == subAxisName)
864 #else //SUBAXES
865                     if (	ser.XAxisType == AxisType.Secondary)
866 #endif //SUBAXES
867 
868                     {
869 						if(ser.XValueType != ChartValueType.Int32 &&
870 							ser.XValueType != ChartValueType.UInt32 &&
871 							ser.XValueType != ChartValueType.UInt64 &&
872 							ser.XValueType != ChartValueType.Int64 )
873 						{
874 							return false;
875 						}
876 						else
877 						{
878 							return true;
879 						}
880 					}
881 				}
882 				// Y axes type
883 				else if( axisName == AxisName.Y )
884                 {
885 #if SUBAXES
886 					if(	ser.YAxisType == AxisType.Primary && ser.YSubAxisName == subAxisName)
887 #else //SUBAXES
888                     if (	ser.YAxisType == AxisType.Primary)
889 #endif //SUBAXES
890 
891                     {
892 						if(ser.YValueType != ChartValueType.Int32 &&
893 							ser.YValueType != ChartValueType.UInt32 &&
894 							ser.YValueType != ChartValueType.UInt64 &&
895 							ser.YValueType != ChartValueType.Int64 )
896 						{
897 							return false;
898 						}
899 						else
900 						{
901 							return true;
902 						}
903 					}
904 				}
905 				else if( axisName == AxisName.Y2 )
906                 {
907 #if SUBAXES
908 					if(	ser.YAxisType == AxisType.Secondary && ser.YSubAxisName == subAxisName)
909 #else //SUBAXES
910                     if (	ser.YAxisType == AxisType.Secondary)
911 #endif //SUBAXES
912 
913                     {
914 						if(ser.YValueType != ChartValueType.Int32 &&
915 							ser.YValueType != ChartValueType.UInt32 &&
916 							ser.YValueType != ChartValueType.UInt64 &&
917 							ser.YValueType != ChartValueType.Int64 )
918 						{
919 							return false;
920 						}
921 						else
922 						{
923 							return true;
924 						}
925 					}
926 				}
927 			}
928 			return false;
929 		}
930 
931 		/// <summary>
932 		/// This method checks if all series in the chart area have �date-time type�
933 		/// for specified axes.
934 		/// </summary>
935 		/// <param name="axisName">Name of the axis</param>
936 		/// <param name="subAxisName">Sub axis name.</param>
937 		/// <returns>True if all series are date-time.</returns>
938         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "subAxisName")]
SeriesDateTimeType( AxisName axisName, string subAxisName )939         internal bool SeriesDateTimeType( AxisName axisName, string subAxisName )
940 		{
941 			// Series which belong to this chart area
942 			foreach( string seriesName in this._series )
943 			{
944 				Series ser = Common.DataManager.Series[ seriesName ];
945 				// X axes type
946 				if( axisName == AxisName.X )
947                 {
948 #if SUBAXES
949 					if(	ser.XAxisType == AxisType.Primary && ser.XSubAxisName == subAxisName)
950 #else //SUBAXES
951                     if (	ser.XAxisType == AxisType.Primary)
952 #endif //SUBAXES
953                     {
954 						if(ser.XValueType != ChartValueType.Date &&
955 							ser.XValueType != ChartValueType.DateTime &&
956 							ser.XValueType != ChartValueType.Time &&
957                             ser.XValueType != ChartValueType.DateTimeOffset)
958 						{
959 							return false;
960 						}
961 						else
962 						{
963 							return true;
964 						}
965 					}
966 				}
967 					// X axes type
968 				else if( axisName == AxisName.X2 )
969                 {
970 #if SUBAXES
971 					if(	ser.XAxisType == AxisType.Secondary && ser.XSubAxisName == subAxisName)
972 #else //SUBAXES
973                     if (	ser.XAxisType == AxisType.Secondary)
974 #endif //SUBAXES
975                     {
976 						if(ser.XValueType != ChartValueType.Date &&
977 							ser.XValueType != ChartValueType.DateTime &&
978 							ser.XValueType != ChartValueType.Time &&
979                             ser.XValueType != ChartValueType.DateTimeOffset)
980 						{
981 							return false;
982 						}
983 						else
984 						{
985 							return true;
986 						}
987 					}
988 				}
989 					// Y axes type
990 				else if( axisName == AxisName.Y )
991                 {
992 #if SUBAXES
993 					if(	ser.YAxisType == AxisType.Primary && ser.YSubAxisName == subAxisName)
994 #else //SUBAXES
995                     if (	ser.YAxisType == AxisType.Primary)
996 #endif //SUBAXES
997                     {
998 						if(ser.YValueType != ChartValueType.Date &&
999 							ser.YValueType != ChartValueType.DateTime &&
1000 							ser.YValueType != ChartValueType.Time &&
1001                             ser.YValueType != ChartValueType.DateTimeOffset)
1002 						{
1003 							return false;
1004 						}
1005 						else
1006 						{
1007 							return true;
1008 						}
1009 					}
1010 				}
1011 				else if( axisName == AxisName.Y2 )
1012                 {
1013 #if SUBAXES
1014 					if(	ser.YAxisType == AxisType.Secondary && ser.YSubAxisName == subAxisName)
1015 #else //SUBAXES
1016                     if (	ser.YAxisType == AxisType.Secondary)
1017 #endif //SUBAXES
1018                     {
1019 						if(ser.YValueType != ChartValueType.Date &&
1020 							ser.YValueType != ChartValueType.DateTime &&
1021 							ser.YValueType != ChartValueType.Time &&
1022                             ser.YValueType != ChartValueType.DateTimeOffset)
1023 						{
1024 							return false;
1025 						}
1026 						else
1027 						{
1028 							return true;
1029 						}
1030 					}
1031 				}
1032 			}
1033 			return false;
1034 		}
1035 
1036         /// <summary>
1037         /// This method calculates minimum and maximum from data series.
1038         /// </summary>
1039         /// <param name="axis">Axis which is used to find minimum and maximum</param>
1040         /// <param name="autoMinimum">Minimum value from data.</param>
1041         /// <param name="autoMaximum">Maximum value from data.</param>
GetValuesFromData( Axis axis, out double autoMinimum, out double autoMaximum )1042 		private void GetValuesFromData( Axis axis, out double autoMinimum, out double autoMaximum )
1043 		{
1044 			// Get number of points in series
1045 			int currentPointsNumber = this.GetNumberOfAllPoints();
1046 
1047 			if( !axis.refreshMinMaxFromData &&
1048 				!double.IsNaN(axis.minimumFromData) &&
1049 				!double.IsNaN(axis.maximumFromData) &&
1050 				axis.numberOfPointsInAllSeries == currentPointsNumber )
1051 			{
1052 				autoMinimum = axis.minimumFromData;
1053 				autoMaximum = axis.maximumFromData;
1054 				return;
1055 			}
1056 
1057 			// Set Axis type
1058 			AxisType type = AxisType.Primary;
1059 			if( axis.axisType == AxisName.X2 || axis.axisType == AxisName.Y2 )
1060 			{
1061 				type = AxisType.Secondary;
1062 			}
1063 
1064 			// Creates a list of series, which have same X axis type.
1065 			string [] xAxesSeries = GetXAxesSeries(type, axis.SubAxisName).ToArray();
1066 
1067 			// Creates a list of series, which have same Y axis type.
1068 			string [] yAxesSeries = GetYAxesSeries( type, axis.SubAxisName ).ToArray();
1069 
1070 			// Get auto maximum and auto minimum value
1071 			if( axis.axisType == AxisName.X2 || axis.axisType == AxisName.X ) // X axis type is used (X or X2)
1072 			{
1073 				if( stacked ) // Chart area has a stacked chart types
1074 				{
1075 					try
1076 					{
1077 						Common.DataManager.GetMinMaxXValue(out autoMinimum, out autoMaximum, xAxesSeries );
1078 					}
1079 					catch(System.Exception)
1080 					{
1081                         throw (new InvalidOperationException(SR.ExceptionAxisStackedChartsDataPointsNumberMismatch));
1082 					}
1083 				}
1084 
1085 				// Chart type with two y values used for scale ( bubble chart type )
1086 				else if( secondYScale )
1087 				{
1088 					autoMaximum = Common.DataManager.GetMaxXWithRadiusValue( (ChartArea)this, xAxesSeries );
1089 					autoMinimum = Common.DataManager.GetMinXWithRadiusValue( (ChartArea)this, xAxesSeries );
1090 					ChartValueType valueTypes = Common.DataManager.Series[xAxesSeries[0]].XValueType;
1091 					if( valueTypes != ChartValueType.Date &&
1092                         valueTypes != ChartValueType.DateTime &&
1093                         valueTypes != ChartValueType.Time &&
1094                         valueTypes != ChartValueType.DateTimeOffset )
1095 					{
1096 						axis.roundedXValues = true;
1097 					}
1098 				}
1099 				else
1100 				{
1101 					Common.DataManager.GetMinMaxXValue(out autoMinimum, out autoMaximum, xAxesSeries );
1102 				}
1103 			}
1104 			else // Y axis type is used (Y or Y2)
1105 			{
1106 
1107 				// *****************************
1108 				// Stacked Chart AxisName
1109 				// *****************************
1110 				if( stacked ) // Chart area has a stacked chart types
1111 				{
1112 					try
1113 					{
1114 						if(hundredPercent)	// It's a hundred percent stacked chart
1115 						{
1116 							autoMaximum = Common.DataManager.GetMaxHundredPercentStackedYValue(hundredPercentNegative, yAxesSeries );
1117 							autoMinimum = Common.DataManager.GetMinHundredPercentStackedYValue(hundredPercentNegative, yAxesSeries );
1118 						}
1119 						else
1120 						{
1121 							// If stacked groupes are used Min/Max range must calculated
1122 							// for each group seperatly.
1123 							double stackMaxBarColumn = double.MinValue;
1124 							double stackMinBarColumn = double.MaxValue;
1125 							double stackMaxArea = double.MinValue;
1126 							double stackMinArea = double.MaxValue;
1127 
1128 							// Split series by group names
1129 							ArrayList	stackedGroups = this.SplitSeriesInStackedGroups(yAxesSeries);
1130 							foreach(string[] groupSeriesNames in stackedGroups)
1131 							{
1132 								// For stacked bar and column
1133 								double stackMaxBarColumnForGroup = Common.DataManager.GetMaxStackedYValue(0, groupSeriesNames );
1134 								double stackMinBarColumnForGroup = Common.DataManager.GetMinStackedYValue(0, groupSeriesNames );
1135 
1136 								// For stacked area
1137 								double stackMaxAreaForGroup = Common.DataManager.GetMaxUnsignedStackedYValue(0, groupSeriesNames );
1138 								double stackMinAreaForGroup = Common.DataManager.GetMinUnsignedStackedYValue(0, groupSeriesNames );
1139 
1140 								// Select minimum/maximum
1141 								stackMaxBarColumn = Math.Max(stackMaxBarColumn, stackMaxBarColumnForGroup);
1142 								stackMinBarColumn = Math.Min(stackMinBarColumn, stackMinBarColumnForGroup);
1143 								stackMaxArea = Math.Max(stackMaxArea, stackMaxAreaForGroup);
1144 								stackMinArea = Math.Min(stackMinArea, stackMinAreaForGroup);
1145 							}
1146 
1147 
1148 							autoMaximum = Math.Max(stackMaxBarColumn,stackMaxArea);
1149 							autoMinimum = Math.Min(stackMinBarColumn,stackMinArea);
1150 						}
1151 						// IsLogarithmic axis
1152 						if( axis.IsLogarithmic && autoMinimum < 1.0 )
1153 							autoMinimum = 1.0;
1154 					}
1155 					catch(System.Exception)
1156 					{
1157                         throw (new InvalidOperationException(SR.ExceptionAxisStackedChartsDataPointsNumberMismatch));
1158 					}
1159 				}
1160 				// Chart type with two y values used for scale ( bubble chart type )
1161 				else if( secondYScale )
1162 				{
1163 					autoMaximum = Common.DataManager.GetMaxYWithRadiusValue( (ChartArea)this, yAxesSeries );
1164 					autoMinimum = Common.DataManager.GetMinYWithRadiusValue( (ChartArea)this, yAxesSeries );
1165 				}
1166 
1167 				// *****************************
1168 				// Non Stacked Chart Types
1169 				// *****************************
1170 				else
1171 				{
1172 					// Check if any series in the area has ExtraYValuesConnectedToYAxis flag set
1173 					bool extraYValuesConnectedToYAxis = false;
1174 					if(this.Common != null && this.Common.Chart != null)
1175 					{
1176 						foreach(Series series in this.Common.Chart.Series)
1177 						{
1178 							if(series.ChartArea == ((ChartArea)this).Name)
1179 							{
1180 								IChartType charType = Common.ChartTypeRegistry.GetChartType( series.ChartTypeName );
1181 								if(charType != null && charType.ExtraYValuesConnectedToYAxis)
1182 								{
1183 									extraYValuesConnectedToYAxis = true;
1184 									break;
1185 								}
1186 							}
1187 						}
1188 					}
1189 
1190 					// The first Chart type can have many Y values (Stock Chart, Range Chart)
1191 					if( extraYValuesConnectedToYAxis )
1192 					{
1193 						Common.DataManager.GetMinMaxYValue(out autoMinimum, out autoMaximum, yAxesSeries );
1194 					}
1195 					else
1196 					{ // The first Chart type can have only one Y value
1197 						Common.DataManager.GetMinMaxYValue(0, out autoMinimum, out autoMaximum, yAxesSeries );
1198 					}
1199 				}
1200 			}
1201 
1202 			// Store Minimum and maximum from data. There is no
1203 			// reason to calculate this values every time.
1204 			axis.maximumFromData = autoMaximum;
1205 			axis.minimumFromData = autoMinimum;
1206 			axis.refreshMinMaxFromData = false;
1207 
1208 			// Make extra test for stored minimum and maximum values
1209 			// from data. If Number of points is different then data
1210 			// source is changed. That means that we should read
1211 			// data again.
1212 			axis.numberOfPointsInAllSeries = currentPointsNumber;
1213 		}
1214 
1215 
1216 		/// <summary>
1217 		/// Splits a single array of series names into multiple arrays
1218 		/// based on the stacked group name.
1219 		/// </summary>
1220 		/// <param name="seriesNames">Array of series name to split.</param>
1221 		/// <returns>An array list that contains sub-arrays of series names split by group name.</returns>
SplitSeriesInStackedGroups(string[] seriesNames)1222 		private ArrayList SplitSeriesInStackedGroups(string[] seriesNames)
1223 		{
1224 			Hashtable groupsHashTable = new Hashtable();
1225 			foreach(string seriesName in seriesNames)
1226 			{
1227 				// Get series object
1228 				Series series = this.Common.Chart.Series[seriesName];
1229 
1230 				// NOTE: Fix for issue #6716
1231 				// Double check that series supports stacked group feature
1232                 string groupName = string.Empty;
1233 				if(StackedColumnChart.IsSeriesStackGroupNameSupported(series))
1234 				{
1235 					// Get stacked group name (empty string by default)
1236 					groupName = StackedColumnChart.GetSeriesStackGroupName(series);
1237 				}
1238 
1239                 // Check if this group was alreday added in to the hashtable
1240                 if (groupsHashTable.ContainsKey(groupName))
1241                 {
1242                     ArrayList list = (ArrayList)groupsHashTable[groupName];
1243                     list.Add(seriesName);
1244                 }
1245                 else
1246                 {
1247                     ArrayList list = new ArrayList();
1248                     list.Add(seriesName);
1249                     groupsHashTable.Add(groupName, list);
1250                 }
1251             }
1252 
1253 			// Convert results to a list that contains array of strings
1254 			ArrayList result = new ArrayList();
1255 			foreach(DictionaryEntry entry in groupsHashTable)
1256 			{
1257 				ArrayList list = (ArrayList)entry.Value;
1258 				if(list.Count > 0)
1259 				{
1260 					int index = 0;
1261 					string[] stringArray = new String[list.Count];
1262 					foreach(string str in list)
1263 					{
1264 						stringArray[index++] = str;
1265 					}
1266 					result.Add(stringArray);
1267 				}
1268 			}
1269 
1270 			return result;
1271 		}
1272 
1273 
1274 
1275 		/// <summary>
1276 		/// Find number of points for all series
1277 		/// </summary>
1278 		/// <returns>Number of points</returns>
GetNumberOfAllPoints()1279 		private int GetNumberOfAllPoints()
1280 		{
1281 			int numOfPoints = 0;
1282 			foreach( Series series in Common.DataManager.Series )
1283 			{
1284 				numOfPoints += series.Points.Count;
1285 			}
1286 
1287 			return numOfPoints;
1288 		}
1289 
1290 		/// <summary>
1291 		/// This method sets default minimum and maximum values from
1292 		/// indexes. This case is used if all X values in a series
1293 		/// have 0 value or IsXValueIndexed flag is set.
1294 		/// </summary>
1295 		/// <param name="axis">Axis</param>
SetDefaultFromIndexes( Axis axis )1296 		private void SetDefaultFromIndexes(  Axis axis )
1297 		{
1298 			// Adjust margin for side-by-side charts like column
1299 			axis.SetTempAxisOffset( );
1300 
1301 			// Set Axis type
1302 			AxisType type = AxisType.Primary;
1303 			if( axis.axisType == AxisName.X2 || axis.axisType == AxisName.Y2 )
1304 			{
1305 				type = AxisType.Secondary;
1306 			}
1307 
1308 			// The maximum is equal to the number of data points.
1309 			double autoMaximum = Common.DataManager.GetNumberOfPoints( GetXAxesSeries( type, axis.SubAxisName ).ToArray() );
1310 			double autoMinimum = 0.0;
1311 
1312 			// Axis margin used only for zooming
1313 			axis.marginView = 0.0;
1314 			if( axis.margin == 100 )
1315 				axis.marginView = 1.0;
1316 
1317 			// If minimum and maximum are same margin always exist.
1318 			if( autoMaximum + axis.margin/100 == autoMinimum - axis.margin/100 + 1 )
1319 			{
1320 				// Set Maximum Number.
1321 				axis.SetAutoMaximum( autoMaximum + 1 );
1322 				axis.SetAutoMinimum( autoMinimum );
1323 			}
1324 			else // Nomal case
1325 			{
1326 				// Set Maximum Number.
1327 				axis.SetAutoMaximum( autoMaximum + axis.margin/100 );
1328 				axis.SetAutoMinimum( autoMinimum - axis.margin/100 + 1 );
1329 			}
1330 
1331 			// Find the interval. If the nuber of points
1332 			// is less then 10 interval is 1.
1333 			double axisInterval;
1334 
1335 			if( axis.ViewMaximum - axis.ViewMinimum <= 10 )
1336 			{
1337 				axisInterval = 1.0;
1338 			}
1339 			else
1340 			{
1341 				axisInterval = axis.CalcInterval( ( axis.ViewMaximum - axis.ViewMinimum ) / 5 );
1342 			}
1343 
1344 			ChartArea area = (ChartArea)this;
1345 			if( area.Area3DStyle.Enable3D && !double.IsNaN(axis.interval3DCorrection) )
1346 			{
1347 				axisInterval = Math.Ceiling( axisInterval / axis.interval3DCorrection );
1348 
1349 				axis.interval3DCorrection = double.NaN;
1350 
1351 				// Use interval
1352 				if( axisInterval > 1.0 &&
1353 					axisInterval < 4.0 &&
1354 					axis.ViewMaximum - axis.ViewMinimum <= 4 )
1355 				{
1356 					axisInterval = 1.0;
1357 				}
1358 
1359 			}
1360 
1361 			axis.SetInterval = axisInterval;
1362 
1363 			// If temporary offsets were defined for the margin,
1364 			// adjust offset for minor ticks and grids.
1365 			if(axis.offsetTempSet)
1366 			{
1367 				axis.minorGrid.intervalOffset -= axis.MajorGrid.GetInterval();
1368 				axis.minorTickMark.intervalOffset -= axis.MajorTickMark.GetInterval();
1369 			}
1370 		}
1371 
1372 		/// <summary>
1373 		/// Sets the names of all data series which belong to
1374 		/// this chart area to collection and sets a list of all
1375 		/// different chart types.
1376 		/// </summary>
SetData()1377         internal void SetData()
1378         {
1379             this.SetData(true, true);
1380         }
1381 
1382         /// <summary>
1383         /// Sets the names of all data series which belong to
1384         /// this chart area to collection and sets a list of all
1385         /// different chart types.
1386         /// </summary>
1387         /// <param name="initializeAxes">If set to <c>true</c> the method will initialize axes default values.</param>
1388         /// <param name="checkIndexedAligned">If set to <c>true</c> the method will check that all primary X axis series are aligned if use the IsXValueIndexed flag.</param>
SetData( bool initializeAxes, bool checkIndexedAligned)1389 		internal void SetData( bool initializeAxes, bool checkIndexedAligned)
1390 		{
1391 			// Initialize chart type properties
1392 			stacked = false;
1393 			switchValueAxes = false;
1394 			requireAxes = true;
1395 			hundredPercent = false;
1396 			hundredPercentNegative = false;
1397 			chartAreaIsCurcular = false;
1398 			secondYScale = false;
1399 
1400 			// AxisName of the chart area already set.
1401 			bool typeSet = false;
1402 
1403 			// Remove all elements from the collection
1404 			this._series.Clear();
1405 
1406             // Add series to the collection
1407 			foreach( Series series in Common.DataManager.Series )
1408 			{
1409                 if (series.ChartArea == this.Name && series.IsVisible() && series.Points.Count > 0)
1410 				{
1411 					this._series.Add(series.Name);
1412 				}
1413 			}
1414 
1415 			// Remove all elements from the collection
1416 			this.chartTypes.Clear();
1417 
1418 			// Add series to the collection
1419 			foreach( Series series in Common.DataManager.Series )
1420 			{
1421 				// A item already exist.
1422 				bool foundItem = false;
1423                 if (series.IsVisible() && series.ChartArea==this.Name)
1424                 {
1425 					foreach( string type in chartTypes )
1426 					{
1427 						// AxisName already exist in the chart area
1428 						if( type == series.ChartTypeName )
1429 						{
1430 							foundItem = true;
1431 						}
1432 					}
1433 					// Add chart type to the collection of
1434 					// Chart area's chart types
1435 					if( !foundItem )
1436 					{
1437 						// Set stacked type
1438 						if( Common.ChartTypeRegistry.GetChartType(series.ChartTypeName).Stacked )
1439 						{
1440 							stacked = true;
1441 						}
1442 
1443 						if( !typeSet )
1444 						{
1445 							if( Common.ChartTypeRegistry.GetChartType(series.ChartTypeName).SwitchValueAxes )
1446 								switchValueAxes = true;
1447 							if( !Common.ChartTypeRegistry.GetChartType(series.ChartTypeName).RequireAxes )
1448 								requireAxes = false;
1449 							if( Common.ChartTypeRegistry.GetChartType(series.ChartTypeName).CircularChartArea )
1450 								chartAreaIsCurcular = true;
1451 							if( Common.ChartTypeRegistry.GetChartType(series.ChartTypeName).HundredPercent )
1452 								hundredPercent = true;
1453 							if( Common.ChartTypeRegistry.GetChartType(series.ChartTypeName).HundredPercentSupportNegative )
1454 								hundredPercentNegative = true;
1455 							if( Common.ChartTypeRegistry.GetChartType(series.ChartTypeName).SecondYScale )
1456 								secondYScale = true;
1457 
1458 							typeSet = true;
1459 						}
1460 						else
1461 						{
1462 							if( Common.ChartTypeRegistry.GetChartType(series.ChartTypeName).SwitchValueAxes != switchValueAxes )
1463 							{
1464                                 throw (new InvalidOperationException(SR.ExceptionChartAreaChartTypesCanNotCombine));
1465 							}
1466 						}
1467 
1468 						// Series is not empty
1469 						if( Common.DataManager.GetNumberOfPoints( series.Name ) != 0 )
1470 						{
1471 							this.chartTypes.Add( series.ChartTypeName );
1472 						}
1473 					}
1474 				}
1475 			}
1476 
1477 			// Check that all primary X axis series are aligned if use the IsXValueIndexed flag
1478             if (checkIndexedAligned)
1479             {
1480                 for (int axisIndex = 0; axisIndex <= 1; axisIndex++)
1481                 {
1482                     List<string> seriesArray = this.GetXAxesSeries((axisIndex == 0) ? AxisType.Primary : AxisType.Secondary, string.Empty);
1483                     if (seriesArray.Count > 0)
1484                     {
1485                         bool indexed = false;
1486                         string seriesNamesStr = "";
1487                         foreach (string seriesName in seriesArray)
1488                         {
1489                             seriesNamesStr = seriesNamesStr + seriesName.Replace(",", "\\,") + ",";
1490                             if (Common.DataManager.Series[seriesName].IsXValueIndexed)
1491                             {
1492                                 indexed = true;
1493                             }
1494                         }
1495 
1496                         if (indexed)
1497                         {
1498                             try
1499                             {
1500                                 Common.DataManipulator.CheckXValuesAlignment(
1501                                     Common.DataManipulator.ConvertToSeriesArray(seriesNamesStr.TrimEnd(','), false));
1502                             }
1503                             catch (Exception e)
1504                             {
1505                                 throw (new ArgumentException(SR.ExceptionAxisSeriesNotAligned + e.Message));
1506                             }
1507                         }
1508                     }
1509                 }
1510 			}
1511             if (initializeAxes)
1512             {
1513                 // Set default min, max etc.
1514                 SetDefaultAxesValues();
1515             }
1516 		}
1517 
1518 		/// <summary>
1519 		/// Returns names of all series, which belong to this chart area
1520 		/// and have same chart type.
1521 		/// </summary>
1522 		/// <param name="chartType">Chart type</param>
1523 		/// <returns>Collection with series names</returns>
GetSeriesFromChartType( string chartType )1524 		internal List<string> GetSeriesFromChartType( string chartType )
1525 		{
1526 			// New collection
1527             List<string> list = new List<string>();
1528 
1529 			foreach( string seriesName in _series )
1530 			{
1531 				if( String.Compare( chartType, Common.DataManager.Series[seriesName].ChartTypeName, StringComparison.OrdinalIgnoreCase ) == 0 )
1532 				{
1533 					// Add a series name to the collection
1534 					list.Add( seriesName );
1535 				}
1536 			}
1537 
1538 			return list;
1539 		}
1540 
1541 		/// <summary>
1542 		/// Returns all series which belong to this chart area.
1543 		/// </summary>
1544 		/// <returns>Collection with series</returns>
GetSeries( )1545 		internal List<Series> GetSeries(  )
1546 		{
1547 			// New collection
1548             List<Series> list = new List<Series>();
1549 
1550 			foreach( string seriesName in _series )
1551 			{
1552                 list.Add(Common.DataManager.Series[seriesName]);
1553             }
1554 
1555 			return list;
1556 		}
1557 
1558 		/// <summary>
1559 		/// Creates a list of series, which have same X axis type.
1560 		/// </summary>
1561 		/// <param name="type">Axis type</param>
1562 		/// <param name="subAxisName">Sub Axis name</param>
1563 		/// <returns>A list of series</returns>
GetXAxesSeries( AxisType type, string subAxisName )1564 		internal List<string> GetXAxesSeries( AxisType type, string subAxisName )
1565 		{
1566 			// Create a new collection of series
1567             List<string> list = new List<string>();
1568             if (_series.Count == 0)
1569             {
1570                 return list;
1571             }
1572 			// Ignore sub axis in 3D
1573 			if( !this.IsSubAxesSupported )
1574 			{
1575 				if(subAxisName.Length > 0)
1576 				{
1577 					return list;
1578 				}
1579 			}
1580 
1581 			// Find series which have same axis type
1582 			foreach( string ser in _series )
1583             {
1584 #if SUBAXES
1585 				if( Common.DataManager.Series[ser].XAxisType == type &&
1586 					(Common.DataManager.Series[ser].XSubAxisName == subAxisName || !this.IsSubAxesSupported) )
1587 #else // SUBAXES
1588                 if ( Common.DataManager.Series[ser].XAxisType == type)
1589 #endif // SUBAXES
1590                 {
1591 					// Add a series to the collection
1592 					list.Add( ser );
1593 				}
1594             }
1595 
1596 #if SUBAXES
1597 			// If series list is empty for the sub-axis then
1598 			// try using the main axis.
1599 			if ( list.Count == 0 && subAxisName.Length > 0 )
1600 			{
1601 				return GetXAxesSeries( type, string.Empty );
1602 			}
1603 #endif // SUBAXES
1604 
1605             // If primary series do not exist return secondary series
1606 			// Axis should always be connected with any series.
1607 			if ( list.Count == 0  )
1608 			{
1609                 if (type == AxisType.Secondary)
1610                 {
1611                     return GetXAxesSeries(AxisType.Primary, string.Empty);
1612                 }
1613                 return GetXAxesSeries(AxisType.Secondary, string.Empty);
1614 			}
1615 
1616 			return list;
1617 		}
1618 
1619 		/// <summary>
1620 		/// Creates a list of series, which have same Y axis type.
1621 		/// </summary>
1622 		/// <param name="type">Axis type</param>
1623 		/// <param name="subAxisName">Sub Axis name</param>
1624 		/// <returns>A list of series</returns>
1625         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "subAxisName")]
GetYAxesSeries( AxisType type, string subAxisName )1626 		internal List<string> GetYAxesSeries( AxisType type, string subAxisName )
1627 		{
1628 			// Create a new collection of series
1629             List<string> list = new List<string>();
1630 
1631 			// Find series which have same axis type
1632 			foreach( string ser in _series )
1633 			{
1634                 // Get series Y axis type
1635                 AxisType seriesYAxisType = Common.DataManager.Series[ser].YAxisType;
1636 #if SUBAXES
1637                 string seriesYSubAxisName = subAxisName;
1638 #endif // SUBAXES
1639 
1640                 // NOTE: Fixes issue #6969
1641                 // Ignore series settings if only Primary Y axis supported by the chart type
1642                 if (Common.DataManager.Series[ser].ChartType == SeriesChartType.Radar ||
1643                     Common.DataManager.Series[ser].ChartType == SeriesChartType.Polar)
1644                 {
1645                     seriesYAxisType = AxisType.Primary;
1646 #if SUBAXES
1647                     seriesYSubAxisName = string.Empty;
1648 #endif // SUBAXES
1649                 }
1650 
1651 
1652 #if SUBAXES
1653 				if( seriesYAxisType == type &&
1654 					(Common.DataManager.Series[ser].YSubAxisName == seriesYSubAxisName || !this.IsSubAxesSupported) )
1655 #else // SUBAXES
1656                 if (seriesYAxisType == type)
1657 #endif // SUBAXES
1658                 {
1659 					// Add a series to the collection
1660 					list.Add( ser );
1661 				}
1662             }
1663 
1664 #if SUBAXES
1665 			// If series list is empty for the sub-axis then
1666 			// try using the main axis.
1667 			if ( list.Count == 0 && subAxisName.Length > 0 )
1668 			{
1669 				return GetYAxesSeries( type, string.Empty );
1670 			}
1671 #endif // SUBAXES
1672 
1673             // If primary series do not exist return secondary series
1674 			// Axis should always be connected with any series.
1675 			if ( list.Count == 0 && type == AxisType.Secondary )
1676 			{
1677                 return GetYAxesSeries( AxisType.Primary, string.Empty );
1678 			}
1679 
1680 			return list;
1681 		}
1682 
1683 		/// <summary>
1684 		/// Get first series from the chart area
1685 		/// </summary>
1686 		/// <returns>Data series</returns>
GetFirstSeries()1687 		internal Series GetFirstSeries()
1688 		{
1689 			if( _series.Count == 0 )
1690 			{
1691                 throw (new InvalidOperationException(SR.ExceptionChartAreaSeriesNotFound));
1692 			}
1693 
1694 			return Common.DataManager.Series[_series[0]];
1695 		}
1696 
1697 		/// <summary>
1698 		/// This method returns minimum interval between
1699 		/// any two data points from series which belong
1700 		/// to this chart area.
1701 		/// </summary>
1702 		/// <param name="isLogarithmic">Indicates logarithmic scale.</param>
1703 		/// <param name="logarithmBase">Logarithm Base</param>
1704 		/// <returns>Minimum Interval</returns>
GetPointsInterval(bool isLogarithmic, double logarithmBase)1705 		internal double GetPointsInterval(bool isLogarithmic, double logarithmBase)
1706 		{
1707 			bool sameInterval;
1708 			return GetPointsInterval( _series, isLogarithmic, logarithmBase, false, out sameInterval );
1709 		}
1710 
1711 		/// <summary>
1712 		/// This method returns minimum interval between
1713 		/// any two data points from specified series.
1714 		/// </summary>
1715 		/// <param name="seriesList">List of series.</param>
1716 		/// <param name="isLogarithmic">Indicates logarithmic scale.</param>
1717 		/// <param name="logarithmBase">Base for logarithmic base</param>
1718 		/// <param name="checkSameInterval">True if check for the same interval should be performed.</param>
1719 		/// <param name="sameInterval">Return true if interval is the same.</param>
1720 		/// <returns>Minimum Interval</returns>
GetPointsInterval( List<string> seriesList, bool isLogarithmic, double logarithmBase, bool checkSameInterval, out bool sameInterval )1721 		internal double GetPointsInterval( List<string> seriesList, bool isLogarithmic, double logarithmBase, bool checkSameInterval, out bool sameInterval )
1722 		{
1723 			Series nullSeries = null;
1724             return GetPointsInterval(seriesList, isLogarithmic, logarithmBase, checkSameInterval, out sameInterval, out nullSeries);
1725 		}
1726 
1727 		/// <summary>
1728 		/// This method returns minimum interval between
1729 		/// any two data points from specified series.
1730 		/// </summary>
1731 		/// <param name="seriesList">List of series.</param>
1732 		/// <param name="isLogarithmic">Indicates logarithmic scale.</param>
1733 		/// <param name="logarithmicBase">Logarithm Base</param>
1734 		/// <param name="checkSameInterval">True if check for the same interval should be performed.</param>
1735 		/// <param name="sameInterval">Return true if interval is the same.</param>
1736 		/// <param name="series">Series with the smallest interval between points.</param>
1737 		/// <returns>Minimum Interval</returns>
GetPointsInterval( List<string> seriesList, bool isLogarithmic, double logarithmicBase, bool checkSameInterval, out bool sameInterval, out Series series )1738 		internal double GetPointsInterval( List<string> seriesList, bool isLogarithmic, double logarithmicBase, bool checkSameInterval, out bool sameInterval, out Series series )
1739 		{
1740 			long	ticksInterval = long.MaxValue;
1741 			int		monthsInteval = 0;
1742 			double	previousInterval = double.MinValue;
1743 			double	oldInterval = Double.MaxValue;
1744 
1745 			// Initialize return value
1746 			sameInterval = true;
1747 			series = null;
1748 
1749 			// Create comma separate string of series names
1750 			string	seriesNames = "";
1751 			if(seriesList != null)
1752 			{
1753 				foreach( string serName in seriesList )
1754 				{
1755 					seriesNames += serName + ",";
1756 				}
1757 			}
1758 
1759 			// Do not calculate interval every time;
1760 			if( checkSameInterval == false || diffIntervalAlignmentChecked == true)
1761 			{
1762                 if (!isLogarithmic)
1763 				{
1764 					if( !double.IsNaN(intervalData) && _intervalSeriesList == seriesNames)
1765 					{
1766 						sameInterval = intervalSameSize;
1767 						series = _intervalSeries;
1768 						return intervalData;
1769 					}
1770 				}
1771 				else
1772 				{
1773 					if( !double.IsNaN(intervalLogData) && _intervalSeriesList == seriesNames)
1774 					{
1775 						sameInterval = intervalSameSize;
1776 						series = _intervalSeries;
1777 						return intervalLogData;
1778 					}
1779 				}
1780 			}
1781 
1782 			// Data series loop
1783 			int			seriesIndex = 0;
1784 			Series		currentSmallestSeries = null;
1785 			ArrayList[] seriesXValues = new ArrayList[seriesList.Count];
1786 			foreach( string ser in seriesList )
1787 			{
1788 				Series	dataSeries = Common.DataManager.Series[ ser ];
1789 				bool isXValueDateTime = dataSeries.IsXValueDateTime();
1790 
1791 				// Copy X values to array and prepare for sorting Sort X values.
1792 				seriesXValues[seriesIndex] = new ArrayList();
1793 				bool	sortPoints = false;
1794 				double	prevXValue = double.MinValue;
1795 				double	curentXValue = 0.0;
1796 				if(dataSeries.Points.Count > 0)
1797 				{
1798                     if (isLogarithmic)
1799 					{
1800 						prevXValue = Math.Log(dataSeries.Points[0].XValue, logarithmicBase);
1801 					}
1802 					else
1803 					{
1804 						prevXValue = dataSeries.Points[0].XValue;
1805 					}
1806 				}
1807 				foreach( DataPoint point in dataSeries.Points )
1808 				{
1809                     if (isLogarithmic)
1810 					{
1811 						curentXValue = Math.Log(point.XValue, logarithmicBase);
1812 					}
1813 					else
1814 					{
1815 						curentXValue = point.XValue;
1816 					}
1817 
1818 					if(prevXValue > curentXValue)
1819 					{
1820 						sortPoints = true;
1821 					}
1822 
1823 					seriesXValues[seriesIndex].Add(curentXValue);
1824 					prevXValue = curentXValue;
1825 				}
1826 
1827 				//  Sort X values
1828 				if(sortPoints)
1829 				{
1830 					seriesXValues[seriesIndex].Sort();
1831 				}
1832 
1833 				// Data point loop
1834 				for( int point = 1; point < seriesXValues[seriesIndex].Count; point++ )
1835 				{
1836 					// Interval between two sorted data points.
1837 					double	interval = Math.Abs( (double)seriesXValues[seriesIndex][ point - 1 ] - (double)seriesXValues[seriesIndex][ point ] );
1838 
1839 					// Check if all intervals are same
1840 					if(sameInterval)
1841 					{
1842 						if(isXValueDateTime)
1843 						{
1844 							if(ticksInterval == long.MaxValue)
1845 							{
1846 								// Calculate first interval
1847 								GetDateInterval(
1848 									(double)seriesXValues[seriesIndex][ point - 1 ],
1849 									(double)seriesXValues[seriesIndex][ point ],
1850 									out monthsInteval,
1851 									out ticksInterval);
1852 							}
1853 							else
1854 							{
1855 								// Calculate current interval
1856 								long	curentTicksInterval = long.MaxValue;
1857 								int		curentMonthsInteval = 0;
1858 								GetDateInterval(
1859 									(double)seriesXValues[seriesIndex][ point - 1 ],
1860 									(double)seriesXValues[seriesIndex][ point ],
1861 									out curentMonthsInteval,
1862 									out curentTicksInterval);
1863 
1864 								// Compare current interval with previous
1865 								if(curentMonthsInteval != monthsInteval || curentTicksInterval != ticksInterval)
1866 								{
1867 									sameInterval = false;
1868 								}
1869 
1870 							}
1871 						}
1872 						else
1873 						{
1874 							if( previousInterval != interval && previousInterval != double.MinValue )
1875 							{
1876 								sameInterval = false;
1877 							}
1878 						}
1879 					}
1880 
1881 					previousInterval = interval;
1882 
1883 					// If not minimum interval keep the old one
1884 					if( oldInterval > interval && interval != 0)
1885 					{
1886 						oldInterval = interval;
1887 						currentSmallestSeries = dataSeries;
1888 					}
1889 				}
1890 
1891 				++seriesIndex;
1892 			}
1893 
1894 			// If interval is not the same check if points from all series are aligned
1895 			this.diffIntervalAlignmentChecked = false;
1896 			if( checkSameInterval &&  !sameInterval && seriesXValues.Length > 1)
1897 			{
1898 				bool	sameXValue = false;
1899 				this.diffIntervalAlignmentChecked = true;
1900 
1901 				// All X values must be same
1902 				int	listIndex = 0;
1903 				foreach(ArrayList xList in seriesXValues)
1904 				{
1905 					for(int pointIndex = 0; pointIndex < xList.Count && !sameXValue; pointIndex++)
1906 					{
1907 						double	xValue = (double)xList[pointIndex];
1908 
1909 						// Loop through all other lists and see if point is there
1910 						for(int index = listIndex + 1; index < seriesXValues.Length && !sameXValue; index++)
1911 						{
1912 							if( (pointIndex < seriesXValues[index].Count && (double)seriesXValues[index][pointIndex] == xValue) ||
1913 								seriesXValues[index].Contains(xValue))
1914 							{
1915 								sameXValue = true;
1916 								break;
1917 							}
1918 						}
1919 					}
1920 
1921 					++listIndex;
1922 				}
1923 
1924 
1925 				// Use side-by-side if at least one xommon X value between eries found
1926 				if(sameXValue)
1927 				{
1928 					sameInterval = true;
1929 				}
1930 			}
1931 
1932 
1933 			// Interval not found. Interval is 1.
1934 			if( oldInterval == Double.MaxValue)
1935 			{
1936 				oldInterval = 1;
1937 			}
1938 
1939 			intervalSameSize = sameInterval;
1940             if (!isLogarithmic)
1941 			{
1942 				intervalData = oldInterval;
1943 				_intervalSeries = currentSmallestSeries;
1944 				series = _intervalSeries;
1945 				_intervalSeriesList = seriesNames;
1946 				return intervalData;
1947 			}
1948 			else
1949 			{
1950 				intervalLogData = oldInterval;
1951 				_intervalSeries = currentSmallestSeries;
1952 				series = _intervalSeries;
1953 				_intervalSeriesList = seriesNames;
1954 				return intervalLogData;
1955 			}
1956 		}
1957 
1958 		/// <summary>
1959 		/// Calculates the difference between two values in years, months, days, ...
1960 		/// </summary>
1961 		/// <param name="value1">First value.</param>
1962 		/// <param name="value2">Second value.</param>
1963 		/// <param name="monthsInteval">Interval in months.</param>
1964 		/// <param name="ticksInterval">Interval in ticks.</param>
GetDateInterval(double value1, double value2, out int monthsInteval, out long ticksInterval)1965 		private void GetDateInterval(double value1, double value2, out int monthsInteval, out long ticksInterval)
1966 		{
1967 			// Convert values to dates
1968 			DateTime	date1 = DateTime.FromOADate(value1);
1969 			DateTime	date2 = DateTime.FromOADate(value2);
1970 
1971 			// Calculate months difference
1972 			monthsInteval = date2.Month - date1.Month;
1973 			monthsInteval += (date2.Year - date1.Year) * 12;
1974 
1975 			// Calculate interval in ticks for days, hours, ...
1976 			ticksInterval = 0;
1977 			ticksInterval += (date2.Day - date1.Day) * TimeSpan.TicksPerDay;
1978 			ticksInterval += (date2.Hour - date1.Hour) * TimeSpan.TicksPerHour;
1979 			ticksInterval += (date2.Minute - date1.Minute) * TimeSpan.TicksPerMinute;
1980 			ticksInterval += (date2.Second - date1.Second) * TimeSpan.TicksPerSecond;
1981 			ticksInterval += (date2.Millisecond - date1.Millisecond) * TimeSpan.TicksPerMillisecond;
1982 		}
1983 
1984 		#endregion
1985 	}
1986 }
1987