ChartAreaAxes.cs 58 KB

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