Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Web.DataVisualization / Common / General / ChartAreaAxes.cs
1 //-------------------------------------------------------------
2 // <copyright company=\92Microsoft Corporation\92>
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>
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>
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>
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>
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>
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>
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>
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>
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\92t 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 \93integer type\94 
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")]
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 \93date-time type\94 
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")]
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>
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>
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>
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>
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>
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>
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>
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>
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>
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")]
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>
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>
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>
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>
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>
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 }