AxisScaleBreaks.cs 30 KB

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