12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076 |
- // Licensed to the .NET Foundation under one or more agreements.
- // The .NET Foundation licenses this file to you under the MIT license.
- // See the LICENSE file in the project root for more information.
- //
- // Purpose: Automatic scale breaks feature related classes.
- //
- using System;
- using System.Collections;
- using System.Collections.Specialized;
- using System.ComponentModel;
- using System.ComponentModel.Design;
- using System.Data;
- using System.Drawing;
- using System.Drawing.Design;
- using System.Drawing.Drawing2D;
- using System.Globalization;
- using FastReport.DataVisualization.Charting.Data;
- using FastReport.DataVisualization.Charting.ChartTypes;
- using FastReport.DataVisualization.Charting.Utilities;
- using FastReport.DataVisualization.Charting.Borders3D;
- namespace FastReport.DataVisualization.Charting
- {
- #region Enumerations
- /// <summary>
- /// An enumeration of line styles for axis scale breaks.
- /// </summary>
- public enum BreakLineStyle
- {
- /// <summary>
- /// No scale break line visible.
- /// </summary>
- None,
- /// <summary>
- /// Straight scale break.
- /// </summary>
- Straight,
- /// <summary>
- /// Wave scale break.
- /// </summary>
- Wave,
- /// <summary>
- /// Ragged scale break.
- /// </summary>
- Ragged,
- }
- /// <summary>
- /// An enumeration which indicates whether an axis segment should start
- /// from zero when scale break is used.
- /// </summary>
- public enum StartFromZero
- {
- /// <summary>
- /// Auto mode
- /// </summary>
- Auto,
- /// <summary>
- /// Start the axis segment scale from zero.
- /// </summary>
- Yes,
- /// <summary>
- /// Do not start the axis segment scale from zero.
- /// </summary>
- No
- };
- #endregion // Enumerations
- /// <summary>
- /// <b>AxisScaleBreakStyle</b> class represents the settings that control the scale break.
- /// </summary>
- [
- SRDescription("DescriptionAttributeAxisScaleBreakStyle_AxisScaleBreakStyle"),
- DefaultProperty("Enabled"),
- ]
- public class AxisScaleBreakStyle
- {
- #region Fields
- // Associated axis
- internal Axis axis = null;
- // True if scale breaks are enabled
- private bool _enabled = false;
- // AxisName of the break line
- private BreakLineStyle _breakLineStyle = BreakLineStyle.Ragged;
- // Spacing between scale segments created by scale breaks
- private double _segmentSpacing = 1.5;
- // Break line color
- private Color _breakLineColor = Color.Black;
- // Break line width
- private int _breakLineWidth = 1;
- // Break line style
- private ChartDashStyle _breakLineDashStyle = ChartDashStyle.Solid;
- // Minimum segment size in axis length percentage
- private double _minSegmentSize = 10.0;
- // Number of segments the axis is devided into to perform statistical analysis
- private int _totalNumberOfSegments = 100;
- // Minimum "empty" size to be replace by the scale break
- private int _minimumNumberOfEmptySegments = 25;
- // Maximum number of breaks
- private int _maximumNumberOfBreaks = 2;
- // Indicates if scale segment should start from zero.
- private StartFromZero _startFromZero = StartFromZero.Auto;
- #endregion // Fields
- #region Constructor
- /// <summary>
- /// AxisScaleBreakStyle constructor.
- /// </summary>
- public AxisScaleBreakStyle()
- {
- }
- /// <summary>
- /// AxisScaleBreakStyle constructor.
- /// </summary>
- /// <param name="axis">Chart axis this class belongs to.</param>
- internal AxisScaleBreakStyle(Axis axis)
- {
- this.axis = axis;
- }
- #endregion // Constructor
- #region Properties
- /// <summary>
- /// Gets or sets a flag which indicates whether one of the axis segments should start its scale from zero
- /// when scale break is used.
- /// </summary>
- /// <remarks>
- /// When property is set to <b>StartFromZero.Auto</b>, the range of the scale determines
- /// if zero value should be included in the scale.
- /// </remarks>
- [
- SRCategory("CategoryAttributeMisc"),
- DefaultValue(StartFromZero.Auto),
- SRDescription("DescriptionAttributeAxisScaleBreakStyle_StartFromZero"),
- ]
- public StartFromZero StartFromZero
- {
- get
- {
- return this._startFromZero;
- }
- set
- {
- this._startFromZero = value;
- this.Invalidate();
- }
- }
- /// <summary>
- /// Maximum number of scale breaks that can be used.
- /// </summary>
- [
- SRCategory("CategoryAttributeMisc"),
- DefaultValue(2),
- SRDescription("DescriptionAttributeAxisScaleBreakStyle_MaxNumberOfBreaks"),
- ]
- public int MaxNumberOfBreaks
- {
- get
- {
- return this._maximumNumberOfBreaks;
- }
- set
- {
- if(value < 1 || value > 5)
- {
- throw (new ArgumentOutOfRangeException("value", SR.ExceptionAxisScaleBreaksNumberInvalid));
- }
- this._maximumNumberOfBreaks = value;
- this.Invalidate();
- }
- }
- /// <summary>
- /// Minimum axis scale region size, in percentage of the total axis length,
- /// that can be collapsed with the scale break.
- /// </summary>
- [
- SRCategory("CategoryAttributeMisc"),
- DefaultValue(25),
- SRDescription("DescriptionAttributeAxisScaleBreakStyle_CollapsibleSpaceThreshold"),
- ]
- public int CollapsibleSpaceThreshold
- {
- get
- {
- return this._minimumNumberOfEmptySegments;
- }
- set
- {
- if(value < 10 || value > 90)
- {
- throw (new ArgumentOutOfRangeException("value", SR.ExceptionAxisScaleBreaksCollapsibleSpaceInvalid));
- }
- this._minimumNumberOfEmptySegments = value;
- this.Invalidate();
- }
- }
- /// <summary>
- /// Gets or sets a flag which determines if axis automatic scale breaks are enabled.
- /// </summary>
- [
- SRCategory("CategoryAttributeMisc"),
- DefaultValue(false),
- SRDescription("DescriptionAttributeAxisScaleBreakStyle_Enabled"),
- ParenthesizePropertyNameAttribute(true),
- ]
- public bool Enabled
- {
- get
- {
- return this._enabled;
- }
- set
- {
- this._enabled = value;
- this.Invalidate();
- }
- }
- /// <summary>
- /// Gets or sets the style of the scale break line.
- /// </summary>
- [
- SRCategory("CategoryAttributeAppearance"),
- DefaultValue(BreakLineStyle.Ragged),
- SRDescription("DescriptionAttributeAxisScaleBreakStyle_BreakLineType"),
- ]
- public BreakLineStyle BreakLineStyle
- {
- get
- {
- return this._breakLineStyle;
- }
- set
- {
- this._breakLineStyle = value;
- this.Invalidate();
- }
- }
- /// <summary>
- /// Gets or sets the spacing of the scale break.
- /// </summary>
- [
- SRCategory("CategoryAttributeMisc"),
- DefaultValue(1.5),
- SRDescription("DescriptionAttributeAxisScaleBreakStyle_Spacing"),
- ]
- public double Spacing
- {
- get
- {
- return this._segmentSpacing;
- }
- set
- {
- if(value < 0.0 || value > 10)
- {
- throw (new ArgumentOutOfRangeException("value", SR.ExceptionAxisScaleBreaksSpacingInvalid));
- }
- this._segmentSpacing = value;
- this.Invalidate();
- }
- }
- /// <summary>
- /// Gets or sets the color of the scale break line.
- /// </summary>
- [
- SRCategory("CategoryAttributeAppearance"),
- DefaultValue(typeof(Color), "Black"),
- SRDescription("DescriptionAttributeLineColor"),
- TypeConverter(typeof(ColorConverter)),
- #if DESIGNER
- Editor(typeof(ChartColorEditor), typeof(UITypeEditor))
- #endif
- ]
- public Color LineColor
- {
- get
- {
- return this._breakLineColor;
- }
- set
- {
- this._breakLineColor = value;
- this.Invalidate();
- }
- }
- /// <summary>
- /// Gets or sets the width of the scale break line.
- /// </summary>
- [
- SRCategory("CategoryAttributeAppearance"),
- DefaultValue(1),
- SRDescription("DescriptionAttributeLineWidth"),
- ]
- public int LineWidth
- {
- get
- {
- return this._breakLineWidth;
- }
- set
- {
- if(value < 1.0 || value > 10)
- {
- throw (new ArgumentOutOfRangeException("value", SR.ExceptionAxisScaleBreaksLineWidthInvalid));
- }
- this._breakLineWidth = value;
- this.Invalidate();
- }
- }
- /// <summary>
- /// Gets or sets the line style of the scale break line.
- /// </summary>
- [
- SRCategory("CategoryAttributeAppearance"),
- DefaultValue(ChartDashStyle.Solid),
- SRDescription("DescriptionAttributeLineDashStyle"),
- ]
- public ChartDashStyle LineDashStyle
- {
- get
- {
- return this._breakLineDashStyle;
- }
- set
- {
- this._breakLineDashStyle = value;
- this.Invalidate();
- }
- }
- #endregion // Properties
- #region Helper Methods
- /// <summary>
- /// Checks if automatic scale breaks are currently enabled.
- /// </summary>
- /// <returns>True if scale breaks are currently enabled.</returns>
- internal bool IsEnabled()
- {
- // Axis scale breaks must be enabled AND supported by the axis.
- if(this.Enabled &&
- this.CanUseAxisScaleBreaks())
- {
- return true;
- }
- return false;
- }
- /// <summary>
- /// Checks if scale breaks can be used on specified axis.
- /// </summary>
- /// <returns>True if scale breaks can be used on this axis</returns>
- internal bool CanUseAxisScaleBreaks()
- {
- // Check input parameters
- if(this.axis == null || this.axis.ChartArea == null || this.axis.ChartArea.Common.Chart == null)
- {
- return false;
- }
- // No scale breaks in 3D charts
- if(this.axis.ChartArea.Area3DStyle.Enable3D)
- {
- return false;
- }
- // Axis scale break can only be applied to the Y and Y 2 axis
- if(this.axis.axisType == AxisName.X || this.axis.axisType == AxisName.X2)
- {
- return false;
- }
-
- // No scale breaks for logarithmic axis
- if(this.axis.IsLogarithmic)
- {
- return false;
- }
- // No scale breaks if axis zooming is enabled
- if(this.axis.ScaleView.IsZoomed)
- {
- return false;
- }
- // Check series associated with this axis
- ArrayList axisSeries = AxisScaleBreakStyle.GetAxisSeries(this.axis);
- foreach(Series series in axisSeries)
- {
- // Some special chart type are not supported
- if(series.ChartType == SeriesChartType.Renko ||
- series.ChartType == SeriesChartType.PointAndFigure)
- {
- return false;
- }
- // Get chart type interface
- IChartType chartType = this.axis.ChartArea.Common.ChartTypeRegistry.GetChartType(series.ChartTypeName);
- if(chartType == null)
- {
- return false;
- }
- // Circular and stacked chart types can not use scale breaks
- if(chartType.CircularChartArea ||
- chartType.Stacked ||
- !chartType.RequireAxes)
- {
- return false;
- }
- }
- return true;
- }
- /// <summary>
- /// Gets a list of series objects attached to the specified axis.
- /// </summary>
- /// <param name="axis">Axis to get the series for.</param>
- /// <returns>A list of series that are attached to the specified axis.</returns>
- static internal ArrayList GetAxisSeries(Axis axis)
- {
- ArrayList seriesList = new ArrayList();
- if(axis != null && axis.ChartArea != null && axis.ChartArea.Common.Chart != null)
- {
- // Iterate through series in the chart
- foreach(Series series in axis.ChartArea.Common.Chart.Series)
- {
- // Series should be on the same chart area and visible
- if(series.ChartArea == axis.ChartArea.Name &&
- series.Enabled)
- {
- // Check primary/secondary axis
- if( (axis.axisType == AxisName.Y && series.YAxisType == AxisType.Secondary) ||
- (axis.axisType == AxisName.Y2 && series.YAxisType == AxisType.Primary))
- {
- continue;
- }
- // Add series into the list
- seriesList.Add(series);
- }
- }
- }
- return seriesList;
- }
-
- /// <summary>
- /// Invalidate chart control.
- /// </summary>
- private void Invalidate()
- {
- if(this.axis != null)
- {
- this.axis.Invalidate();
- }
- }
- #endregion // Helper Methods
- #region Series StatisticFormula Methods
- /// <summary>
- /// Get collection of axis segments to present scale breaks.
- /// </summary>
- /// <param name="axisSegments">Collection of axis scale segments.</param>
- internal void GetAxisSegmentForScaleBreaks(AxisScaleSegmentCollection axisSegments)
- {
- // Clear segment collection
- axisSegments.Clear();
- // Check if scale breaks are enabled
- if(this.IsEnabled())
- {
- // Fill collection of segments
- this.FillAxisSegmentCollection(axisSegments);
- // Check if more than 1 segments were defined
- if(axisSegments.Count >= 1)
- {
- // Get index of segment which scale should start from zero
- int startFromZeroSegmentIndex = this.GetStartScaleFromZeroSegmentIndex(axisSegments);
- // Calculate segment interaval and round the scale
- int index = 0;
- foreach(AxisScaleSegment axisScaleSegment in axisSegments)
- {
- // Check if segment scale should start from zero
- bool startFromZero = (index == startFromZeroSegmentIndex) ? true : false;
- // Calculate interval and round scale
- double minimum = axisScaleSegment.ScaleMinimum;
- double maximum = axisScaleSegment.ScaleMaximum;
- axisScaleSegment.Interval = this.axis.EstimateNumberAxis(
- ref minimum, ref maximum, startFromZero, this.axis.prefferedNumberofIntervals, true, true);
- axisScaleSegment.ScaleMinimum = minimum;
- axisScaleSegment.ScaleMaximum = maximum;
- // Make sure new scale break value range do not exceed axis current scale
- if (axisScaleSegment.ScaleMinimum < this.axis.Minimum)
- {
- axisScaleSegment.ScaleMinimum = this.axis.Minimum;
- }
- if (axisScaleSegment.ScaleMaximum > this.axis.Maximum)
- {
- axisScaleSegment.ScaleMaximum = this.axis.Maximum;
- }
- // Increase segment index
- ++index;
- }
- // Defined axis scale segments cannot overlap.
- // Check for overlapping and join segments or readjust min/max.
- bool adjustPosition = false;
- AxisScaleSegment prevSegment = axisSegments[0];
- for (int segmentIndex = 1; segmentIndex < axisSegments.Count; segmentIndex++)
- {
- AxisScaleSegment currentSegment = axisSegments[segmentIndex];
- if (currentSegment.ScaleMinimum <= prevSegment.ScaleMaximum)
- {
- if (currentSegment.ScaleMaximum > prevSegment.ScaleMaximum)
- {
- // If segments are partially overlapping make sure the previous
- // segment scale is extended
- prevSegment.ScaleMaximum = currentSegment.ScaleMaximum;
- }
- // Remove the overlapped segment
- adjustPosition = true;
- axisSegments.RemoveAt(segmentIndex);
- --segmentIndex;
- }
- else
- {
- prevSegment = currentSegment;
- }
- }
- // Calculate the position of each segment
- if (adjustPosition)
- {
- this.SetAxisSegmentPosition(axisSegments);
- }
- }
- }
- }
- /// <summary>
- /// Gets index of segment that should be started from zero.
- /// </summary>
- /// <param name="axisSegments">Axis scale segment collection.</param>
- /// <returns>Index axis segment or -1.</returns>
- private int GetStartScaleFromZeroSegmentIndex(AxisScaleSegmentCollection axisSegments)
- {
- if (this.StartFromZero == StartFromZero.Auto ||
- this.StartFromZero == StartFromZero.Yes)
- {
- int index = 0;
- foreach(AxisScaleSegment axisScaleSegment in axisSegments)
- {
- // Check if zero value is already part of the scale
- if(axisScaleSegment.ScaleMinimum < 0.0 && axisScaleSegment.ScaleMaximum > 0.0)
- {
- return -1;
- }
- // As soon as we get first segment with positive minimum value or
- // we reached last segment adjust scale to start from zero.
- if(axisScaleSegment.ScaleMinimum > 0.0 ||
- index == (axisSegments.Count - 1) )
- {
- // Check if setting minimum scale to zero will make the
- // data points in the segment hard to read. This may hapen
- // when the distance from zero to current minimum is
- // significantly larger than current scale size.
- if (this.StartFromZero == StartFromZero.Auto &&
- axisScaleSegment.ScaleMinimum > 2.0 * (axisScaleSegment.ScaleMaximum - axisScaleSegment.ScaleMinimum) )
- {
- return -1;
- }
- return index;
- }
- // Increase segment index
- ++index;
- }
- }
- return -1;
- }
- /// <summary>
- /// Sets position of all scale segments in the axis.
- /// </summary>
- /// <param name="axisSegments">Collection of axis scale segments.</param>
- private void SetAxisSegmentPosition(AxisScaleSegmentCollection axisSegments)
- {
- // Calculate total number of points
- int totalPointNumber = 0;
- foreach(AxisScaleSegment axisScaleSegment in axisSegments)
- {
- if(axisScaleSegment.Tag is int)
- {
- totalPointNumber += (int)axisScaleSegment.Tag;
- }
- }
- // Calculate segment minimum size
- double minSize = Math.Min(this._minSegmentSize, Math.Floor(100.0 / axisSegments.Count));
- // Set segment position
- double currentPosition = 0.0;
- for(int index = 0; index < axisSegments.Count; index++)
- {
- axisSegments[index].Position = (currentPosition > 100.0) ? 100.0 : currentPosition;
- axisSegments[index].Size = Math.Round(((int)axisSegments[index].Tag) / (totalPointNumber / 100.0),5);
- if(axisSegments[index].Size < minSize)
- {
- axisSegments[index].Size = minSize;
- }
-
- // Set spacing for all segments except the last one
- if(index < (axisSegments.Count - 1) )
- {
- axisSegments[index].Spacing = this._segmentSpacing;
- }
- // Advance current position
- currentPosition += axisSegments[index].Size;
- }
- // Make sure we do not exceed the 100% axis length
- double totalHeight = 0.0;
- do
- {
- // Calculate total height
- totalHeight = 0.0;
- double maxSize = double.MinValue;
- int maxSizeIndex = -1;
- for(int index = 0; index < axisSegments.Count; index++)
- {
- totalHeight += axisSegments[index].Size;
- if(axisSegments[index].Size > maxSize)
- {
- maxSize = axisSegments[index].Size;
- maxSizeIndex = index;
- }
- }
- // If height is too large find largest segment
- if(totalHeight > 100.0)
- {
- // Adjust segment size
- axisSegments[maxSizeIndex].Size -= totalHeight - 100.0;
- if(axisSegments[maxSizeIndex].Size < minSize)
- {
- axisSegments[maxSizeIndex].Size = minSize;
- }
- // Adjust position of the next segment
- double curentPosition = axisSegments[maxSizeIndex].Position + axisSegments[maxSizeIndex].Size;
- for(int index = maxSizeIndex + 1; index < axisSegments.Count; index++)
- {
- axisSegments[index].Position = curentPosition;
- curentPosition += axisSegments[index].Size;
- }
- }
- } while(totalHeight > 100.0);
- }
- /// <summary>
- /// Fill collection of axis scale segments.
- /// </summary>
- /// <param name="axisSegments">Collection of axis segments.</param>
- private void FillAxisSegmentCollection(AxisScaleSegmentCollection axisSegments)
- {
- // Clear axis segments collection
- axisSegments.Clear();
- // Get statistics for the series attached to the axis
- double minYValue = 0.0;
- double maxYValue = 0.0;
- double segmentSize = 0.0;
- double[] segmentMaxValue = null;
- double[] segmentMinValue = null;
- int[] segmentPointNumber = GetSeriesDataStatistics(
- this._totalNumberOfSegments,
- out minYValue,
- out maxYValue,
- out segmentSize,
- out segmentMaxValue,
- out segmentMinValue);
- if (segmentPointNumber == null)
- {
- return;
- }
- // Calculate scale maximum and minimum
- double minimum = minYValue;
- double maximum = maxYValue;
- this.axis.EstimateNumberAxis(
- ref minimum,
- ref maximum,
- this.axis.IsStartedFromZero,
- this.axis.prefferedNumberofIntervals,
- true,
- true);
- // Make sure max/min Y values are not the same
- if (maxYValue == minYValue)
- {
- return;
- }
- // Calculate the percentage of the scale range covered by the data range.
- double dataRangePercent = (maxYValue - minYValue) / ((maximum - minimum) / 100.0);
- // Get sequences of empty segments
- ArrayList emptySequences = new ArrayList();
- bool doneFlag = false;
- while(!doneFlag)
- {
- doneFlag = true;
- // Get longest sequence of segments with no points
- int startSegment = 0;
- int numberOfSegments = 0;
- this.GetLargestSequenseOfSegmentsWithNoPoints(
- segmentPointNumber,
- out startSegment,
- out numberOfSegments);
- // Adjust minimum empty segments number depending on current segments
- int minEmptySegments = (int)(this._minimumNumberOfEmptySegments * (100.0 / dataRangePercent));
- if(axisSegments.Count > 0 && numberOfSegments > 0)
- {
- // Find the segment which contain newly found empty segments sequence
- foreach(AxisScaleSegment axisScaleSegment in axisSegments)
- {
- if(startSegment > 0 && (startSegment + numberOfSegments) <= segmentMaxValue.Length - 1)
- {
- if(segmentMaxValue[startSegment - 1] >= axisScaleSegment.ScaleMinimum &&
- segmentMinValue[startSegment + numberOfSegments] <= axisScaleSegment.ScaleMaximum)
- {
- // Get percentage of segment scale that is empty and suggested for collapsing
- double segmentScaleRange = axisScaleSegment.ScaleMaximum - axisScaleSegment.ScaleMinimum;
- double emptySpaceRange = segmentMinValue[startSegment + numberOfSegments] - segmentMaxValue[startSegment - 1];
- double emptySpacePercent = emptySpaceRange / (segmentScaleRange / 100.0);
- emptySpacePercent = emptySpacePercent / 100 * axisScaleSegment.Size;
- if(emptySpacePercent > minEmptySegments &&
- numberOfSegments > this._minSegmentSize)
- {
- minEmptySegments = numberOfSegments;
- }
- }
- }
- }
- }
- // Check if found sequence is long enough
- if(numberOfSegments >= minEmptySegments)
- {
- doneFlag = false;
- // Store start segment and number of segments in the list
- emptySequences.Add(startSegment);
- emptySequences.Add(numberOfSegments);
- // Check if there are any emty segments sequence found
- axisSegments.Clear();
- if(emptySequences.Count > 0)
- {
- double segmentFrom = double.NaN;
- double segmentTo = double.NaN;
- // Based on the segments that need to be excluded create axis segments that
- // will present on the axis scale.
- int numberOfPoints = 0;
- for(int index = 0; index < segmentPointNumber.Length; index++)
- {
- // Check if current segment is excluded
- bool excludedSegment = this.IsExcludedSegment(emptySequences, index);
- // If not excluded segment - update from/to range if they were set
- if(!excludedSegment &&
- !double.IsNaN(segmentMinValue[index]) &&
- !double.IsNaN(segmentMaxValue[index]))
- {
- // Calculate total number of points
- numberOfPoints += segmentPointNumber[index];
- // Set From/To of the visible segment
- if(double.IsNaN(segmentFrom))
- {
- segmentFrom = segmentMinValue[index];
- segmentTo = segmentMaxValue[index];
- }
- else
- {
- segmentTo = segmentMaxValue[index];
- }
- }
- // If excluded or last segment - add current visible segment range
- if(!double.IsNaN(segmentFrom) &&
- (excludedSegment || index == (segmentPointNumber.Length - 1) ))
- {
- // Make sure To and From do not match
- if(segmentTo == segmentFrom)
- {
- segmentFrom -= segmentSize;
- segmentTo += segmentSize;
- }
- // Add axis scale segment
- AxisScaleSegment axisScaleSegment = new AxisScaleSegment();
- axisScaleSegment.ScaleMaximum = segmentTo;
- axisScaleSegment.ScaleMinimum = segmentFrom;
- axisScaleSegment.Tag = numberOfPoints;
- axisSegments.Add(axisScaleSegment);
- // Reset segment range
- segmentFrom = double.NaN;
- segmentTo = double.NaN;
- numberOfPoints = 0;
- }
- }
- }
- // Calculate the position of each segment
- this.SetAxisSegmentPosition(axisSegments);
- }
- // Make sure we do not exceed specified number of breaks
- if( (axisSegments.Count - 1) >= this._maximumNumberOfBreaks)
- {
- doneFlag = true;
- }
- }
- }
- /// <summary>
- /// Check if segment was excluded.
- /// </summary>
- /// <param name="excludedSegments">Array of segment indexes.</param>
- /// <param name="segmentIndex">Index of the segment to check.</param>
- /// <returns>True if segment with specified index is marked as excluded.</returns>
- private bool IsExcludedSegment(ArrayList excludedSegments, int segmentIndex)
- {
- for(int index = 0; index < excludedSegments.Count; index += 2)
- {
- if(segmentIndex >= (int)excludedSegments[index] &&
- segmentIndex < (int)excludedSegments[index] + (int)excludedSegments[index + 1])
- {
- return true;
- }
- }
- return false;
- }
- /// <summary>
- /// Collect statistical information about the series.
- /// </summary>
- /// <param name="segmentCount">Segment count.</param>
- /// <param name="minYValue">Minimum Y value.</param>
- /// <param name="maxYValue">Maximum Y value.</param>
- /// <param name="segmentSize">Segment size.</param>
- /// <param name="segmentMaxValue">Array of segment scale maximum values.</param>
- /// <param name="segmentMinValue">Array of segment scale minimum values.</param>
- /// <returns></returns>
- internal int[] GetSeriesDataStatistics(
- int segmentCount,
- out double minYValue,
- out double maxYValue,
- out double segmentSize,
- out double[] segmentMaxValue,
- out double[] segmentMinValue)
- {
- // Get all series associated with the axis
- ArrayList axisSeries = AxisScaleBreakStyle.GetAxisSeries(this.axis);
- // Get range of Y values from axis series
- minYValue = 0.0;
- maxYValue = 0.0;
- axis.Common.DataManager.GetMinMaxYValue(axisSeries, out minYValue, out maxYValue);
-
- int numberOfPoints = 0;
- foreach (Series series in axisSeries)
- {
- numberOfPoints = Math.Max(numberOfPoints, series.Points.Count);
- }
-
- if (axisSeries.Count == 0 || numberOfPoints == 0)
- {
- segmentSize = 0.0;
- segmentMaxValue = null;
- segmentMinValue = null;
- return null;
- }
- // Split range of values into predefined number of segments and calculate
- // how many points will be in each segment.
- segmentSize = (maxYValue - minYValue) / segmentCount;
- int[] segmentPointNumber = new int[segmentCount];
- segmentMaxValue = new double[segmentCount];
- segmentMinValue = new double[segmentCount];
- for(int index = 0; index < segmentCount; index++)
- {
- segmentMaxValue[index] = double.NaN;
- segmentMinValue[index] = double.NaN;
- }
- foreach(Series series in axisSeries)
- {
- // Get number of Y values to process
- int maxYValueCount = 1;
- IChartType chartType = this.axis.ChartArea.Common.ChartTypeRegistry.GetChartType(series.ChartTypeName);
- if(chartType != null)
- {
- if(chartType.ExtraYValuesConnectedToYAxis && chartType.YValuesPerPoint > 1)
- {
- maxYValueCount = chartType.YValuesPerPoint;
- }
- }
- // Iterate throug all data points
- foreach(DataPoint dataPoint in series.Points)
- {
- if(!dataPoint.IsEmpty)
- {
- // Iterate through all yValues
- for(int yValueIndex = 0; yValueIndex < maxYValueCount; yValueIndex++)
- {
- // Calculate index of the scale segment
- int segmentIndex = (int)Math.Floor((dataPoint.YValues[yValueIndex] - minYValue) / segmentSize);
- if(segmentIndex < 0)
- {
- segmentIndex = 0;
- }
- if(segmentIndex > segmentCount - 1)
- {
- segmentIndex = segmentCount - 1;
- }
- // Increase number points in that segment
- ++segmentPointNumber[segmentIndex];
- // Store Min/Max values for the segment
- if(segmentPointNumber[segmentIndex] == 1)
- {
- segmentMaxValue[segmentIndex] = dataPoint.YValues[yValueIndex];
- segmentMinValue[segmentIndex] = dataPoint.YValues[yValueIndex];
- }
- else
- {
- segmentMaxValue[segmentIndex] = Math.Max(segmentMaxValue[segmentIndex], dataPoint.YValues[yValueIndex]);
- segmentMinValue[segmentIndex] = Math.Min(segmentMinValue[segmentIndex], dataPoint.YValues[yValueIndex]);
- }
- }
- }
- }
- }
- return segmentPointNumber;
- }
- /// <summary>
- /// Gets largest segment with no points.
- /// </summary>
- /// <param name="segmentPointNumber">Array that stores number of points for each segment.</param>
- /// <param name="startSegment">Returns largest empty segment sequence starting index.</param>
- /// <param name="numberOfSegments">Returns largest empty segment sequence length.</param>
- /// <returns>True if long empty segment sequence was found.</returns>
- internal bool GetLargestSequenseOfSegmentsWithNoPoints(
- int[] segmentPointNumber,
- out int startSegment,
- out int numberOfSegments)
- {
- // Find the longest sequence of empty segments
- startSegment = -1;
- numberOfSegments = 0;
- int currentSegmentStart = -1;
- int currentNumberOfSegments = -1;
- for(int index = 0; index < segmentPointNumber.Length; index++)
- {
- // Check for the segment with no points
- if(segmentPointNumber[index] == 0)
- {
- if(currentSegmentStart == -1)
- {
- currentSegmentStart = index;
- currentNumberOfSegments = 1;
- }
- else
- {
- ++currentNumberOfSegments;
- }
- }
-
- // Check if longest sequence found
- if(currentNumberOfSegments > 0 &&
- (segmentPointNumber[index] != 0 || index == segmentPointNumber.Length - 1))
- {
- if(currentNumberOfSegments > numberOfSegments)
- {
- startSegment = currentSegmentStart;
- numberOfSegments = currentNumberOfSegments;
- }
- currentSegmentStart = -1;
- currentNumberOfSegments = 0;
- }
- }
- // Store value of "-1" in found sequence
- if(numberOfSegments != 0)
- {
- for(int index = startSegment; index < (startSegment + numberOfSegments); index++)
- {
- segmentPointNumber[index] = -1;
- }
- return true;
- }
- return false;
- }
- #endregion // Series StatisticFormula Methods
- }
- }
|