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