Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Web.DataVisualization / Common / General / AxisScaleBreaks.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:               AxisScaleBreaks.cs
9 //
10 //  Namespace:  System.Web.UI.WebControls[Windows.Forms].Charting
11 //
12 //      Classes:        AxisScaleBreakStyle
13 //
14 //  Purpose:    Automatic scale breaks feature related classes.
15 //
16 //      Reviewed:       
17 //
18 //===================================================================
19
20 #region Used namespaces
21
22 using System;
23 using System.Collections;
24 using System.Collections.Specialized;
25 using System.ComponentModel;
26 using System.ComponentModel.Design;
27 using System.Data;
28 using System.Drawing;
29 using System.Drawing.Design;
30 using System.Drawing.Drawing2D;
31 using System.Globalization;
32
33 #if Microsoft_CONTROL
34
35         using System.Windows.Forms.DataVisualization.Charting.Data;
36         using System.Windows.Forms.DataVisualization.Charting.ChartTypes;
37         using System.Windows.Forms.DataVisualization.Charting.Utilities;
38         using System.Windows.Forms.DataVisualization.Charting.Borders3D;
39         using System.Windows.Forms.DataVisualization.Charting;
40
41 #else
42         using System.Web;
43         using System.Web.UI;
44         using System.Web.UI.DataVisualization.Charting;
45         using System.Web.UI.DataVisualization.Charting.Data;
46         using System.Web.UI.DataVisualization.Charting.ChartTypes;
47         using System.Web.UI.DataVisualization.Charting.Utilities;
48 #endif
49
50 #endregion
51
52 #if Microsoft_CONTROL
53 namespace System.Windows.Forms.DataVisualization.Charting
54 #else
55 namespace System.Web.UI.DataVisualization.Charting
56
57 #endif
58 {
59         #region Enumerations
60
61         /// <summary>
62         /// An enumeration of line styles for axis scale breaks.
63         /// </summary>
64     public enum BreakLineStyle
65         {
66                 /// <summary>
67                 /// No scale break line visible.
68                 /// </summary>
69                 None,
70
71                 /// <summary>
72                 /// Straight scale break.
73                 /// </summary>
74                 Straight,
75
76                 /// <summary>
77                 /// Wave scale break.
78                 /// </summary>
79                 Wave,
80
81                 /// <summary>
82                 /// Ragged scale break.
83                 /// </summary>
84                 Ragged,
85         }
86
87     /// <summary>
88     /// An enumeration which indicates whether an axis segment should start
89     /// from zero when scale break is used.
90     /// </summary>
91     public enum StartFromZero
92     {
93         /// <summary>
94         /// Auto mode
95         /// </summary>
96         Auto,
97
98         /// <summary>
99         /// Start the axis segment scale from zero.
100         /// </summary>
101         Yes,
102
103         /// <summary>
104         /// Do not start the axis segment scale from zero.
105         /// </summary>
106         No
107
108     };
109
110         #endregion // Enumerations
111
112         /// <summary>
113         /// <b>AxisScaleBreakStyle</b> class represents the settings that control the scale break.
114         /// </summary>
115         [
116         SRDescription("DescriptionAttributeAxisScaleBreakStyle_AxisScaleBreakStyle"),
117         DefaultProperty("Enabled"),
118         ]
119 #if ASPPERM_35
120         [AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
121     [AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
122 #endif
123     public class AxisScaleBreakStyle
124         {
125         #region Fields
126
127         // Associated axis
128                 internal Axis axis = null;
129
130                 // True if scale breaks are enabled
131                 private bool _enabled = false;
132
133                 // AxisName of the break line 
134                 private BreakLineStyle _breakLineStyle = BreakLineStyle.Ragged;
135
136                 // Spacing between scale segments created by scale breaks
137                 private double _segmentSpacing = 1.5;
138
139                 // Break line color
140                 private Color _breakLineColor = Color.Black;
141
142                 // Break line width
143                 private int _breakLineWidth = 1;
144
145                 // Break line style
146                 private ChartDashStyle _breakLineDashStyle = ChartDashStyle.Solid;
147
148                 // Minimum segment size in axis length percentage 
149                 private double _minSegmentSize = 10.0;
150
151                 // Number of segments the axis is devided into to perform statistical analysis
152                 private int _totalNumberOfSegments = 100;
153
154                 // Minimum "empty" size to be replace by the scale break
155                 private int _minimumNumberOfEmptySegments = 25;
156
157                 // Maximum number of breaks
158                 private int _maximumNumberOfBreaks = 2;
159
160                 // Indicates if scale segment should start from zero.
161         private StartFromZero _startFromZero = StartFromZero.Auto;
162
163                 #endregion // Fields
164
165                 #region Constructor
166
167                 /// <summary>
168         /// AxisScaleBreakStyle constructor.
169                 /// </summary>
170                 public AxisScaleBreakStyle()
171                 {
172                 }
173
174                 /// <summary>
175         /// AxisScaleBreakStyle constructor.
176                 /// </summary>
177                 /// <param name="axis">Chart axis this class belongs to.</param>
178         internal AxisScaleBreakStyle(Axis axis)
179                 {
180                         this.axis = axis;
181                 }
182
183                 #endregion // Constructor
184
185                 #region Properties
186
187                 /// <summary>
188                 /// Gets or sets a flag which indicates whether one of the axis segments should start its scale from zero 
189                 /// when scale break is used.
190                 /// </summary>
191                 /// <remarks>
192         /// When property is set to <b>StartFromZero.Auto</b>, the range of the scale determines
193                 /// if zero value should be included in the scale.
194                 /// </remarks>
195                 [
196                 SRCategory("CategoryAttributeMisc"),
197         DefaultValue(StartFromZero.Auto),
198                 SRDescription("DescriptionAttributeAxisScaleBreakStyle_StartFromZero"),
199                 ]
200         public StartFromZero StartFromZero
201                 {
202                         get
203                         {
204                                 return this._startFromZero;
205                         }
206                         set
207                         {
208                                 this._startFromZero = value;
209                                 this.Invalidate();
210                         }
211                 }
212
213                 /// <summary>
214                 /// Maximum number of scale breaks that can be used.
215                 /// </summary>
216                 [
217                 SRCategory("CategoryAttributeMisc"),
218                 DefaultValue(2),
219                 SRDescription("DescriptionAttributeAxisScaleBreakStyle_MaxNumberOfBreaks"),
220                 ]
221                 public int MaxNumberOfBreaks
222                 {
223                         get
224                         {
225                                 return this._maximumNumberOfBreaks;
226                         }
227                         set
228                         {
229                                 if(value < 1 || value > 5)
230                                 {
231                     throw (new ArgumentOutOfRangeException("value", SR.ExceptionAxisScaleBreaksNumberInvalid));
232                                 }
233                                 this._maximumNumberOfBreaks = value;
234                                 this.Invalidate();
235                         }
236                 }
237
238                 /// <summary>
239                 /// Minimum axis scale region size, in percentage of the total axis length, 
240                 /// that can be collapsed with the scale break.
241                 /// </summary>
242                 [
243                 SRCategory("CategoryAttributeMisc"),
244                 DefaultValue(25),
245                 SRDescription("DescriptionAttributeAxisScaleBreakStyle_CollapsibleSpaceThreshold"),
246                 ]
247                 public int CollapsibleSpaceThreshold
248                 {
249                         get
250                         {
251                                 return this._minimumNumberOfEmptySegments;
252                         }
253                         set
254                         {
255                                 if(value < 10 || value > 90)
256                                 {
257                     throw (new ArgumentOutOfRangeException("value", SR.ExceptionAxisScaleBreaksCollapsibleSpaceInvalid));
258                                 }
259                                 this._minimumNumberOfEmptySegments = value;
260                                 this.Invalidate();
261                         }
262                 }
263
264                 /// <summary>
265                 /// Gets or sets a flag which determines if axis automatic scale breaks are enabled.
266                 /// </summary>
267                 [
268                 SRCategory("CategoryAttributeMisc"),
269                 DefaultValue(false),
270                 SRDescription("DescriptionAttributeAxisScaleBreakStyle_Enabled"),
271                 ParenthesizePropertyNameAttribute(true),
272                 ]
273                 public bool Enabled
274                 {
275                         get
276                         {
277                                 return this._enabled;
278                         }
279                         set
280                         {
281                                 this._enabled = value;
282                                 this.Invalidate();
283                         }
284                 }
285
286                 /// <summary>
287                 /// Gets or sets the style of the scale break line.
288                 /// </summary>
289                 [
290                 SRCategory("CategoryAttributeAppearance"),
291                 DefaultValue(BreakLineStyle.Ragged),
292                 SRDescription("DescriptionAttributeAxisScaleBreakStyle_BreakLineType"),
293                 ]
294                 public BreakLineStyle BreakLineStyle
295                 {
296                         get
297                         {
298                                 return this._breakLineStyle;
299                         }
300                         set
301                         {
302                                 this._breakLineStyle = value;
303                                 this.Invalidate();
304                         }
305                 }
306
307                 /// <summary>
308                 /// Gets or sets the spacing of the scale break.
309                 /// </summary>
310                 [
311                 SRCategory("CategoryAttributeMisc"),
312                 DefaultValue(1.5),
313                 SRDescription("DescriptionAttributeAxisScaleBreakStyle_Spacing"),
314                 ]
315                 public double Spacing
316                 {
317                         get
318                         {
319                                 return this._segmentSpacing;
320                         }
321                         set
322                         {
323                                 if(value < 0.0 || value > 10)
324                                 {
325                     throw (new ArgumentOutOfRangeException("value", SR.ExceptionAxisScaleBreaksSpacingInvalid));
326                                 }
327                                 this._segmentSpacing = value;
328                                 this.Invalidate();
329                         }
330                 }
331
332                 /// <summary>
333                 /// Gets or sets the color of the scale break line.
334                 /// </summary>
335                 [
336                 SRCategory("CategoryAttributeAppearance"),
337                 DefaultValue(typeof(Color), "Black"),
338         SRDescription("DescriptionAttributeLineColor"),
339         TypeConverter(typeof(ColorConverter)),
340         Editor(Editors.ChartColorEditor.Editor, Editors.ChartColorEditor.Base)
341                 ]
342                 public Color LineColor
343                 {
344                         get
345                         {
346                                 return this._breakLineColor;
347                         }
348                         set
349                         {
350                                 this._breakLineColor = value;
351                                 this.Invalidate();
352                         }
353                 }
354
355                 /// <summary>
356                 /// Gets or sets the width of the scale break line.
357                 /// </summary>
358                 [
359                 SRCategory("CategoryAttributeAppearance"),
360                 DefaultValue(1),
361         SRDescription("DescriptionAttributeLineWidth"),
362                 ]
363                 public int LineWidth
364                 {
365                         get
366                         {
367                                 return this._breakLineWidth;
368                         }
369                         set
370                         {
371                                 if(value < 1.0 || value > 10)
372                                 {
373                     throw (new ArgumentOutOfRangeException("value", SR.ExceptionAxisScaleBreaksLineWidthInvalid));
374                                 }
375                                 this._breakLineWidth = value;
376                                 this.Invalidate();
377                         }
378                 }
379
380                 /// <summary>
381                 /// Gets or sets the line style of the scale break line.
382                 /// </summary>
383                 [
384                 SRCategory("CategoryAttributeAppearance"),
385                 DefaultValue(ChartDashStyle.Solid),
386         SRDescription("DescriptionAttributeLineDashStyle"),
387                 ]
388                 public ChartDashStyle LineDashStyle
389                 {
390                         get
391                         {
392                                 return this._breakLineDashStyle;
393                         }
394                         set
395                         {
396                                 this._breakLineDashStyle = value;
397                                 this.Invalidate();
398                         }
399                 }
400
401                 #endregion // Properties
402
403                 #region Helper Methods
404
405                 /// <summary>
406                 /// Checks if automatic scale breaks are currently enabled.
407                 /// </summary>
408                 /// <returns>True if scale breaks are currently enabled.</returns>
409                 internal bool IsEnabled()
410                 {
411                         // Axis scale breaks must be enabled AND supported by the axis.
412                         if(this.Enabled && 
413                                 this.CanUseAxisScaleBreaks())
414                         {
415                                 return true;
416                         }
417                         return false;
418                 }
419
420                 /// <summary>
421                 /// Checks if scale breaks can be used on specified axis.
422                 /// </summary>
423                 /// <returns>True if scale breaks can be used on this axis</returns>
424                 internal bool CanUseAxisScaleBreaks()
425                 {
426                         // Check input parameters
427                         if(this.axis == null || this.axis.ChartArea == null || this.axis.ChartArea.Common.Chart == null)
428                         {
429                                 return false;
430                         }
431
432                         // No scale breaks in 3D charts
433                         if(this.axis.ChartArea.Area3DStyle.Enable3D)
434                         {
435                                 return false;
436                         }
437
438                         // Axis scale break can only be applied to the Y and Y 2 axis
439                         if(this.axis.axisType == AxisName.X || this.axis.axisType == AxisName.X2)
440                         {
441                                 return false;
442                         }
443         
444                         // No scale breaks for logarithmic axis
445                         if(this.axis.IsLogarithmic)
446                         {
447                                 return false;
448                         }
449
450                         // No scale breaks if axis zooming is enabled
451                         if(this.axis.ScaleView.IsZoomed)
452                         {
453                                 return false;
454                         }
455
456                         // Check series associated with this axis
457                         ArrayList axisSeries = AxisScaleBreakStyle.GetAxisSeries(this.axis);
458                         foreach(Series series in axisSeries)
459                         {
460
461                                 // Some special chart type are not supported
462                                 if(series.ChartType == SeriesChartType.Renko || 
463                                         series.ChartType == SeriesChartType.PointAndFigure)
464                                 {
465                                         return false;
466                                 }
467
468
469                                 // Get chart type interface
470                                 IChartType chartType = this.axis.ChartArea.Common.ChartTypeRegistry.GetChartType(series.ChartTypeName);
471                                 if(chartType == null)
472                                 {
473                                         return false;
474                                 }
475
476                                 // Circular and stacked chart types can not use scale breaks
477                                 if(chartType.CircularChartArea || 
478                                         chartType.Stacked || 
479                                         !chartType.RequireAxes)
480                                 {
481                                         return false;
482                                 }
483                         }
484
485                         return true;
486                 }
487
488                 /// <summary>
489                 /// Gets a list of series objects attached to the specified axis.
490                 /// </summary>
491                 /// <param name="axis">Axis to get the series for.</param>
492                 /// <returns>A list of series that are attached to the specified axis.</returns>
493                 static internal ArrayList GetAxisSeries(Axis axis)
494                 {
495                         ArrayList seriesList = new ArrayList();
496                         if(axis != null && axis.ChartArea != null && axis.ChartArea.Common.Chart != null)
497                         {
498                                 // Iterate through series in the chart
499                                 foreach(Series series in axis.ChartArea.Common.Chart.Series)
500                                 {
501                                         // Series should be on the same chart area and visible
502                                         if(series.ChartArea == axis.ChartArea.Name &&
503                                                 series.Enabled)
504                                         {
505                                                 // Check primary/secondary axis
506                                                 if( (axis.axisType == AxisName.Y && series.YAxisType == AxisType.Secondary) || 
507                                                         (axis.axisType == AxisName.Y2 && series.YAxisType == AxisType.Primary))
508                                                 {
509                                                         continue;
510                                                 }
511
512                                                 // Add series into the list
513                                                 seriesList.Add(series);
514                                         }
515                                 }
516                         }
517                         return seriesList;
518                 }
519         
520                 /// <summary>
521                 /// Invalidate chart control.
522                 /// </summary>
523                 private void Invalidate()
524                 {
525                         if(this.axis != null)
526                         {
527                                 this.axis.Invalidate();
528                         }
529                 }
530
531                 #endregion // Helper Methods
532
533                 #region Series StatisticFormula Methods
534
535                 /// <summary>
536                 /// Get collection of axis segments to present scale breaks.
537                 /// </summary>
538                 /// <param name="axisSegments">Collection of axis scale segments.</param>
539                 internal void GetAxisSegmentForScaleBreaks(AxisScaleSegmentCollection axisSegments)
540                 {
541                         // Clear segment collection
542                         axisSegments.Clear();
543
544                         // Check if scale breaks are enabled
545                         if(this.IsEnabled())
546                         {
547                                 // Fill collection of segments
548                                 this.FillAxisSegmentCollection(axisSegments);
549
550                                 // Check if more than 1 segments were defined
551                                 if(axisSegments.Count >= 1)
552                                 {
553                                         // Get index of segment which scale should start from zero
554                                         int startFromZeroSegmentIndex = this.GetStartScaleFromZeroSegmentIndex(axisSegments);
555
556                                         // Calculate segment interaval and round the scale
557                                         int index = 0;
558                                         foreach(AxisScaleSegment axisScaleSegment in axisSegments)
559                                         {
560                                                 // Check if segment scale should start from zero
561                                                 bool startFromZero = (index == startFromZeroSegmentIndex) ? true : false;
562
563                                                 // Calculate interval and round scale
564                                                 double minimum = axisScaleSegment.ScaleMinimum;
565                                                 double maximum = axisScaleSegment.ScaleMaximum;
566                                                 axisScaleSegment.Interval = this.axis.EstimateNumberAxis( 
567                                                         ref minimum, ref maximum, startFromZero, this.axis.prefferedNumberofIntervals, true, true);
568                                                 axisScaleSegment.ScaleMinimum = minimum; 
569                                                 axisScaleSegment.ScaleMaximum = maximum;
570
571                         // Make sure new scale break value range do not exceed axis current scale
572                         if (axisScaleSegment.ScaleMinimum < this.axis.Minimum)
573                         {
574                             axisScaleSegment.ScaleMinimum = this.axis.Minimum;
575                         }
576                         if (axisScaleSegment.ScaleMaximum > this.axis.Maximum)
577                         {
578                             axisScaleSegment.ScaleMaximum = this.axis.Maximum;
579                         }
580
581                                                 // Increase segment index
582                                                 ++index;
583                                         }
584
585                     // Defined axis scale segments cannot overlap. 
586                     // Check for overlapping and join segments or readjust min/max.
587                     bool adjustPosition = false;
588                     AxisScaleSegment prevSegment = axisSegments[0];
589                     for (int segmentIndex = 1; segmentIndex < axisSegments.Count; segmentIndex++)
590                     {
591                         AxisScaleSegment currentSegment = axisSegments[segmentIndex];
592                         if (currentSegment.ScaleMinimum <= prevSegment.ScaleMaximum)
593                         {
594                             if (currentSegment.ScaleMaximum > prevSegment.ScaleMaximum)
595                             {
596                                 // If segments are partially overlapping make sure the previous
597                                 // segment scale is extended
598                                 prevSegment.ScaleMaximum = currentSegment.ScaleMaximum;
599                             }
600
601                             // Remove the overlapped segment
602                             adjustPosition = true;
603                             axisSegments.RemoveAt(segmentIndex);
604                             --segmentIndex;
605                         }
606                         else
607                         {
608                             prevSegment = currentSegment;
609                         }
610                     }
611
612                     // Calculate the position of each segment
613                     if (adjustPosition)
614                     {
615                         this.SetAxisSegmentPosition(axisSegments);
616                     }
617                                 }
618                         }
619                 }
620
621         /// <summary>
622         /// Gets index of segment that should be started from zero.
623         /// </summary>
624         /// <param name="axisSegments">Axis scale segment collection.</param>
625         /// <returns>Index axis segment or -1.</returns>
626                 private int GetStartScaleFromZeroSegmentIndex(AxisScaleSegmentCollection axisSegments)
627                 {
628             if (this.StartFromZero == StartFromZero.Auto ||
629                 this.StartFromZero == StartFromZero.Yes)
630                         {
631                                 int index = 0;
632                                 foreach(AxisScaleSegment axisScaleSegment in axisSegments)
633                                 {
634                                         // Check if zero value is already part of the scale
635                                         if(axisScaleSegment.ScaleMinimum < 0.0 && axisScaleSegment.ScaleMaximum > 0.0)
636                                         {
637                                                 return -1;
638                                         }
639
640                                         // As soon as we get first segment with positive minimum value or
641                                         // we reached last segment adjust scale to start from zero.
642                                         if(axisScaleSegment.ScaleMinimum > 0.0 ||
643                                                 index == (axisSegments.Count - 1) )
644                                         {
645                                                 // Check if setting minimum scale to zero will make the
646                                                 // data points in the segment hard to read. This may hapen 
647                                                 // when the distance from zero to current minimum is 
648                                                 // significantly larger than current scale size.
649                         if (this.StartFromZero == StartFromZero.Auto &&
650                                                         axisScaleSegment.ScaleMinimum > 2.0 * (axisScaleSegment.ScaleMaximum - axisScaleSegment.ScaleMinimum) )
651                                                 {
652                                                         return -1;
653                                                 }
654
655                                                 return index;
656                                         }
657
658                                         // Increase segment index
659                                         ++index;
660                                 }
661                         }
662                         return -1;
663                 }
664
665                 /// <summary>
666                 /// Sets position of all scale segments in the axis.
667                 /// </summary>
668                 /// <param name="axisSegments">Collection of axis scale segments.</param>
669                 private void SetAxisSegmentPosition(AxisScaleSegmentCollection axisSegments)
670                 {
671                         // Calculate total number of points
672                         int totalPointNumber = 0;
673                         foreach(AxisScaleSegment axisScaleSegment in axisSegments)
674                         {
675                                 if(axisScaleSegment.Tag is int)
676                                 {
677                                         totalPointNumber += (int)axisScaleSegment.Tag;
678                                 }
679                         }
680
681                         // Calculate segment minimum size
682                         double minSize = Math.Min(this._minSegmentSize, Math.Floor(100.0 / axisSegments.Count));
683
684                         // Set segment position
685                         double currentPosition = 0.0;
686                         for(int index = 0; index < axisSegments.Count; index++)
687                         {
688                                 axisSegments[index].Position = (currentPosition > 100.0) ? 100.0 : currentPosition;
689                                 axisSegments[index].Size = Math.Round(((int)axisSegments[index].Tag) / (totalPointNumber / 100.0),5);
690                                 if(axisSegments[index].Size < minSize)
691                                 {
692                                         axisSegments[index].Size = minSize;
693                                 }
694                                 
695                                 // Set spacing for all segments except the last one
696                                 if(index < (axisSegments.Count - 1) )
697                                 {
698                                         axisSegments[index].Spacing = this._segmentSpacing;
699                                 }
700
701                                 // Advance current position
702                                 currentPosition += axisSegments[index].Size;
703                         }
704
705                         // Make sure we do not exceed the 100% axis length
706                         double totalHeight = 0.0;
707                         do
708                         {
709                                 // Calculate total height
710                                 totalHeight = 0.0;
711                                 double maxSize = double.MinValue;
712                                 int maxSizeIndex = -1;
713                                 for(int index = 0; index < axisSegments.Count; index++)
714                                 {
715                                         totalHeight += axisSegments[index].Size;
716                                         if(axisSegments[index].Size > maxSize)
717                                         {
718                                                 maxSize = axisSegments[index].Size;
719                                                 maxSizeIndex = index;
720                                         }
721                                 }
722
723                                 // If height is too large find largest segment 
724                                 if(totalHeight > 100.0)
725                                 {
726                                         // Adjust segment size
727                                         axisSegments[maxSizeIndex].Size -= totalHeight - 100.0;
728                                         if(axisSegments[maxSizeIndex].Size < minSize)
729                                         {
730                                                 axisSegments[maxSizeIndex].Size = minSize;
731                                         }
732
733                                         // Adjust position of the next segment
734                                         double curentPosition = axisSegments[maxSizeIndex].Position + axisSegments[maxSizeIndex].Size;
735                                         for(int index = maxSizeIndex + 1; index < axisSegments.Count; index++)
736                                         {
737                                                 axisSegments[index].Position = curentPosition;
738                                                 curentPosition += axisSegments[index].Size;
739                                         }
740                                 }
741
742                         } while(totalHeight > 100.0);
743
744                 }
745
746                 /// <summary>
747                 /// Fill collection of axis scale segments.
748                 /// </summary>
749                 /// <param name="axisSegments">Collection of axis segments.</param>
750                 private void FillAxisSegmentCollection(AxisScaleSegmentCollection axisSegments)
751                 {
752                         // Clear axis segments collection
753                         axisSegments.Clear();
754
755                         // Get statistics for the series attached to the axis
756                         double minYValue = 0.0;
757                         double maxYValue = 0.0;
758                         double segmentSize = 0.0;
759                         double[] segmentMaxValue = null;
760                         double[] segmentMinValue = null;
761             int[] segmentPointNumber = GetSeriesDataStatistics(
762                                 this._totalNumberOfSegments, 
763                                 out minYValue, 
764                                 out maxYValue, 
765                                 out segmentSize, 
766                                 out segmentMaxValue, 
767                                 out segmentMinValue);
768             if (segmentPointNumber == null)
769             {
770                 return;
771             }
772
773                         // Calculate scale maximum and minimum
774                         double minimum = minYValue;
775                         double maximum = maxYValue;
776                         this.axis.EstimateNumberAxis(
777                                 ref minimum, 
778                                 ref maximum, 
779                                 this.axis.IsStartedFromZero, 
780                                 this.axis.prefferedNumberofIntervals, 
781                                 true, 
782                                 true);
783
784             // Make sure max/min Y values are not the same
785             if (maxYValue == minYValue)
786             {
787                 return;
788             }
789
790                         // Calculate the percentage of the scale range covered by the data range.
791                         double dataRangePercent = (maxYValue - minYValue) / ((maximum - minimum) / 100.0);
792
793                         // Get sequences of empty segments
794                         ArrayList       emptySequences = new ArrayList();
795                         bool doneFlag = false;
796                         while(!doneFlag)
797                         {
798                                 doneFlag = true;
799
800                                 // Get longest sequence of segments with no points
801                                 int startSegment = 0; 
802                                 int numberOfSegments = 0;
803                                 this.GetLargestSequenseOfSegmentsWithNoPoints(
804                                         segmentPointNumber, 
805                                         out startSegment, 
806                                         out numberOfSegments);
807
808                                 // Adjust minimum empty segments  number depending on current segments
809                                 int minEmptySegments = (int)(this._minimumNumberOfEmptySegments * (100.0 / dataRangePercent));
810                                 if(axisSegments.Count > 0 && numberOfSegments > 0)
811                                 {
812                                         // Find the segment which contain newly found empty segments sequence
813                                         foreach(AxisScaleSegment axisScaleSegment in axisSegments)
814                                         {
815                                                 if(startSegment > 0 && (startSegment + numberOfSegments) <= segmentMaxValue.Length - 1)
816                                                 {
817                                                         if(segmentMaxValue[startSegment - 1] >= axisScaleSegment.ScaleMinimum &&
818                                                                 segmentMinValue[startSegment + numberOfSegments] <= axisScaleSegment.ScaleMaximum)
819                                                         {
820                                                                 // Get percentage of segment scale that is empty and suggested for collapsing
821                                                                 double segmentScaleRange = axisScaleSegment.ScaleMaximum - axisScaleSegment.ScaleMinimum;
822                                                                 double emptySpaceRange = segmentMinValue[startSegment + numberOfSegments] - segmentMaxValue[startSegment - 1];
823                                                                 double emptySpacePercent = emptySpaceRange / (segmentScaleRange / 100.0);
824                                                                 emptySpacePercent = emptySpacePercent / 100 * axisScaleSegment.Size;
825
826                                                                 if(emptySpacePercent > minEmptySegments &&
827                                                                         numberOfSegments > this._minSegmentSize)
828                                                                 {
829                                                                         minEmptySegments = numberOfSegments;
830                                                                 }
831                                                         }
832                                                 }
833                                         }
834                                 }
835
836                                 // Check if found sequence is long enough
837                                 if(numberOfSegments >= minEmptySegments)
838                                 {
839                                         doneFlag = false;
840
841                                         // Store start segment and number of segments in the list
842                                         emptySequences.Add(startSegment);
843                                         emptySequences.Add(numberOfSegments);
844
845                                         // Check if there are any emty segments sequence found
846                                         axisSegments.Clear();
847                                         if(emptySequences.Count > 0)
848                                         {
849                                                 double segmentFrom = double.NaN;
850                                                 double segmentTo = double.NaN;
851
852                                                 // Based on the segments that need to be excluded create axis segments that
853                                                 // will present on the axis scale.
854                                                 int numberOfPoints = 0;
855                                                 for(int index = 0; index < segmentPointNumber.Length; index++)
856                                                 {
857                                                         // Check if current segment is excluded
858                                                         bool excludedSegment = this.IsExcludedSegment(emptySequences, index);
859
860                                                         // If not excluded segment - update from/to range if they were set
861                                                         if(!excludedSegment && 
862                                                                 !double.IsNaN(segmentMinValue[index]) &&
863                                                                 !double.IsNaN(segmentMaxValue[index]))
864                                                         {
865                                                                 // Calculate total number of points
866                                                                 numberOfPoints += segmentPointNumber[index];
867
868                                                                 // Set From/To of the visible segment
869                                                                 if(double.IsNaN(segmentFrom))
870                                                                 {
871                                                                         segmentFrom = segmentMinValue[index];
872                                                                         segmentTo = segmentMaxValue[index];
873                                                                 }
874                                                                 else
875                                                                 {
876                                                                         segmentTo = segmentMaxValue[index];
877                                                                 }
878                                                         }
879
880                                                         // If excluded or last segment - add current visible segment range
881                                                         if(!double.IsNaN(segmentFrom) && 
882                                                                 (excludedSegment || index == (segmentPointNumber.Length - 1) ))
883                                                         {
884                                                                 // Make sure To and From do not match
885                                                                 if(segmentTo == segmentFrom)
886                                                                 {
887                                                                         segmentFrom -= segmentSize;
888                                                                         segmentTo += segmentSize;
889                                                                 }
890
891                                                                 // Add axis scale segment
892                                                                 AxisScaleSegment axisScaleSegment = new AxisScaleSegment();
893                                                                 axisScaleSegment.ScaleMaximum = segmentTo;
894                                                                 axisScaleSegment.ScaleMinimum = segmentFrom;
895                                                                 axisScaleSegment.Tag = numberOfPoints;
896                                                                 axisSegments.Add(axisScaleSegment);
897
898                                                                 // Reset segment range
899                                                                 segmentFrom = double.NaN;
900                                                                 segmentTo = double.NaN;
901                                                                 numberOfPoints = 0;
902                                                         }
903                                                 }
904                                         }
905
906                                         // Calculate the position of each segment
907                                         this.SetAxisSegmentPosition(axisSegments);
908                                 }
909
910                                 // Make sure we do not exceed specified number of breaks
911                                 if( (axisSegments.Count - 1) >= this._maximumNumberOfBreaks)
912                                 {
913                                         doneFlag = true;
914                                 }
915                         }
916
917                 }
918
919                 /// <summary>
920                 /// Check if segment was excluded.
921                 /// </summary>
922                 /// <param name="excludedSegments">Array of segment indexes.</param>
923                 /// <param name="segmentIndex">Index of the segment to check.</param>
924                 /// <returns>True if segment with specified index is marked as excluded.</returns>
925                 private bool IsExcludedSegment(ArrayList excludedSegments, int segmentIndex)
926                 {
927                         for(int index = 0; index < excludedSegments.Count; index += 2)
928                         {
929                                 if(segmentIndex >= (int)excludedSegments[index] && 
930                                         segmentIndex < (int)excludedSegments[index] + (int)excludedSegments[index + 1])
931                                 {
932                                         return true;
933                                 }
934                         }
935                         return false;
936                 }
937
938                 /// <summary>
939                 /// Collect statistical information about the series.
940                 /// </summary>
941                 /// <param name="segmentCount">Segment count.</param>
942                 /// <param name="minYValue">Minimum Y value.</param>
943                 /// <param name="maxYValue">Maximum Y value.</param>
944                 /// <param name="segmentSize">Segment size.</param>
945                 /// <param name="segmentMaxValue">Array of segment scale maximum values.</param>
946                 /// <param name="segmentMinValue">Array of segment scale minimum values.</param>
947                 /// <returns></returns>
948                 internal int[] GetSeriesDataStatistics(
949                         int segmentCount, 
950                         out double minYValue, 
951                         out double maxYValue, 
952                         out double segmentSize,
953                         out double[] segmentMaxValue,
954                         out double[] segmentMinValue)
955                 {
956                         // Get all series associated with the axis
957                         ArrayList axisSeries = AxisScaleBreakStyle.GetAxisSeries(this.axis);
958
959                         // Get range of Y values from axis series
960                         minYValue = 0.0;
961                         maxYValue = 0.0;
962                         axis.Common.DataManager.GetMinMaxYValue(axisSeries, out minYValue, out maxYValue);
963             
964             int numberOfPoints = 0;
965             foreach (Series series in axisSeries)
966             {
967                 numberOfPoints = Math.Max(numberOfPoints, series.Points.Count);
968             }
969             
970             if (axisSeries.Count == 0 || numberOfPoints == 0)
971             {
972                 segmentSize = 0.0;
973                 segmentMaxValue = null;
974                 segmentMinValue = null;
975                 return null;
976             }
977
978                         // Split range of values into predefined number of segments and calculate
979                         // how many points will be in each segment.
980                         segmentSize = (maxYValue - minYValue) / segmentCount;
981                         int[] segmentPointNumber = new int[segmentCount];
982                         segmentMaxValue = new double[segmentCount];
983                         segmentMinValue = new double[segmentCount];
984                         for(int index = 0; index < segmentCount; index++)
985                         {
986                                 segmentMaxValue[index] = double.NaN;
987                                 segmentMinValue[index] = double.NaN;
988                         }
989                         foreach(Series series in axisSeries)
990                         {
991                                 // Get number of Y values to process
992                                 int maxYValueCount = 1;
993                                 IChartType chartType = this.axis.ChartArea.Common.ChartTypeRegistry.GetChartType(series.ChartTypeName);
994                                 if(chartType != null)
995                                 {
996                                         if(chartType.ExtraYValuesConnectedToYAxis && chartType.YValuesPerPoint > 1)
997                                         {
998                                                 maxYValueCount = chartType.YValuesPerPoint;
999                                         }
1000                                 }
1001
1002                                 // Iterate throug all data points
1003                                 foreach(DataPoint dataPoint in series.Points)
1004                                 {
1005                                         if(!dataPoint.IsEmpty)
1006                                         {
1007                                                 // Iterate through all yValues
1008                                                 for(int yValueIndex = 0; yValueIndex < maxYValueCount; yValueIndex++)
1009                                                 {
1010                                                         // Calculate index of the scale segment
1011                                                         int segmentIndex = (int)Math.Floor((dataPoint.YValues[yValueIndex] - minYValue) / segmentSize);
1012                                                         if(segmentIndex < 0)
1013                                                         {
1014                                                                 segmentIndex = 0;
1015                                                         }
1016                                                         if(segmentIndex > segmentCount - 1)
1017                                                         {
1018                                                                 segmentIndex = segmentCount - 1;
1019                                                         }
1020
1021                                                         // Increase number points in that segment
1022                                                         ++segmentPointNumber[segmentIndex];
1023
1024                                                         // Store Min/Max values for the segment
1025                                                         if(segmentPointNumber[segmentIndex] == 1)
1026                                                         {
1027                                                                 segmentMaxValue[segmentIndex] = dataPoint.YValues[yValueIndex];
1028                                                                 segmentMinValue[segmentIndex] = dataPoint.YValues[yValueIndex];
1029                                                         }
1030                                                         else
1031                                                         {
1032                                                                 segmentMaxValue[segmentIndex] = Math.Max(segmentMaxValue[segmentIndex], dataPoint.YValues[yValueIndex]);
1033                                                                 segmentMinValue[segmentIndex] = Math.Min(segmentMinValue[segmentIndex], dataPoint.YValues[yValueIndex]);
1034                                                         }
1035                                                 }
1036                                         }
1037                                 }
1038                         }
1039
1040                         return segmentPointNumber;
1041                 }
1042
1043                 /// <summary>
1044                 /// Gets largest segment with no points.
1045                 /// </summary>
1046                 /// <param name="segmentPointNumber">Array that stores number of points for each segment.</param>
1047                 /// <param name="startSegment">Returns largest empty segment sequence starting index.</param>
1048                 /// <param name="numberOfSegments">Returns largest empty segment sequence length.</param>
1049                 /// <returns>True if long empty segment sequence was found.</returns>
1050                 internal bool GetLargestSequenseOfSegmentsWithNoPoints(
1051                         int[] segmentPointNumber, 
1052                         out int startSegment, 
1053                         out int numberOfSegments)
1054                 {
1055                         // Find the longest sequence of empty segments
1056                         startSegment = -1;
1057                         numberOfSegments = 0;
1058                         int currentSegmentStart = -1;
1059                         int currentNumberOfSegments = -1;
1060                         for(int index = 0; index < segmentPointNumber.Length; index++)
1061                         {
1062                                 // Check for the segment with no points
1063                                 if(segmentPointNumber[index] == 0)
1064                                 {
1065                                         if(currentSegmentStart == -1)
1066                                         {
1067                                                 currentSegmentStart = index;
1068                                                 currentNumberOfSegments = 1;
1069                                         }
1070                                         else
1071                                         {
1072                                                 ++currentNumberOfSegments;
1073                                         }
1074                                 }
1075                                 
1076                                 // Check if longest sequence found
1077                                 if(currentNumberOfSegments > 0 && 
1078                                         (segmentPointNumber[index] != 0 || index == segmentPointNumber.Length - 1))
1079                                 {
1080                                         if(currentNumberOfSegments > numberOfSegments)
1081                                         {
1082                                                 startSegment = currentSegmentStart;
1083                                                 numberOfSegments = currentNumberOfSegments;
1084                                         }
1085                                         currentSegmentStart = -1;
1086                                         currentNumberOfSegments = 0;
1087                                 }
1088                         }
1089
1090                         // Store value of "-1" in found sequence
1091                         if(numberOfSegments != 0)
1092                         {
1093                                 for(int index = startSegment; index < (startSegment + numberOfSegments); index++)
1094                                 {
1095                                         segmentPointNumber[index] = -1;
1096                                 }
1097
1098                                 return true;
1099                         }
1100
1101                         return false;
1102                 }
1103
1104                 #endregion // Series StatisticFormula Methods
1105         }
1106 }
1107
1108