1 //------------------------------------------------------------- 2 // <copyright company=�Microsoft Corporation�> 3 // Copyright � Microsoft Corporation. All Rights Reserved. 4 // </copyright> 5 //------------------------------------------------------------- 6 // @owner=alexgor, deliant 7 //================================================================= 8 // File: PieChart.cs 9 // 10 // Namespace: DataVisualization.Charting.ChartTypes 11 // 12 // Classes: PieChart 13 // 14 // Purpose: Provides 2D/3D drawing and hit testing functionality 15 // for the Pie chart. A pie chart shows how proportions 16 // of data, shown as pie-shaped pieces, contribute to 17 // the data as a whole. 18 // 19 // PieChart class is used as a base class for the 20 // DoughnutChart class. 21 // 22 // Reviewed: GS - Aug 8, 2002 23 // AG - Aug 8, 2002 24 // AG - Microsoft 6, 2007 25 // 26 //=================================================================== 27 28 #region Used namespaces 29 30 using System; 31 using System.Collections; 32 using System.Collections.Generic; 33 using System.Drawing; 34 using System.Drawing.Drawing2D; 35 using System.ComponentModel.Design; 36 using System.Globalization; 37 38 #if Microsoft_CONTROL 39 using System.Windows.Forms.DataVisualization.Charting.Utilities; 40 #else 41 using System.Web.UI.DataVisualization.Charting.Utilities; 42 #endif 43 44 #endregion 45 46 #if Microsoft_CONTROL 47 namespace System.Windows.Forms.DataVisualization.Charting.ChartTypes 48 #else 49 namespace System.Web.UI.DataVisualization.Charting.ChartTypes 50 #endif 51 { 52 #region Enumerations 53 54 /// <summary> 55 /// Pie Labels style 56 /// </summary> 57 internal enum PieLabelStyle 58 { 59 /// <summary> 60 /// Labels are inside pie slice 61 /// </summary> 62 Inside, 63 64 /// <summary> 65 /// Labels are outside pie slice 66 /// </summary> 67 Outside, 68 69 /// <summary> 70 /// Labels are disabled 71 /// </summary> 72 Disabled, 73 74 }; 75 76 #endregion 77 78 /// <summary> 79 /// PieChart class provides 2D/3D drawing and hit testing functionality 80 /// for the Pie chart. 81 /// </summary> 82 internal class PieChart : IChartType 83 { 84 #region Enumerations 85 86 /// <summary> 87 /// Labels Mode for preparing data 88 /// </summary> 89 enum LabelsMode 90 { 91 /// <summary> 92 /// There are no labels 93 /// </summary> 94 Off, 95 96 /// <summary> 97 /// Drawing labels mode 98 /// </summary> 99 Draw, 100 101 /// <summary> 102 /// Labels Estimation mode 103 /// </summary> 104 EstimateSize, 105 106 /// <summary> 107 /// Labels Overlap Mode 108 /// </summary> 109 LabelsOverlap 110 }; 111 112 #endregion 113 114 #region Fields 115 116 // True if labels fit inside plot area. 117 private bool _labelsFit = true; 118 119 // Field that is used to resize pie 120 // because of labels. 121 private float _sizeCorrection = 0.95F; 122 123 // True if any pie slice is exploded 124 private bool _sliceExploded = false; 125 126 // True if labels overlap for 2D Pie and outside labels 127 private bool _labelsOverlap = false; 128 129 // Left Lable column used for 3D chart and outside labels 130 internal LabelColumn labelColumnLeft; 131 132 // Right Lable column used for 3D chart and outside labels 133 internal LabelColumn labelColumnRight; 134 135 // Array of label rectangles used to prevent labels overlapping 136 // for 2D pie chart outside labels. 137 private ArrayList _labelsRectangles = new ArrayList(); 138 139 #endregion 140 141 #region IChartType interface implementation 142 143 /// <summary> 144 /// Chart type name 145 /// </summary> 146 virtual public string Name { get{ return ChartTypeNames.Pie;}} 147 148 /// <summary> 149 /// Gets chart type image. 150 /// </summary> 151 /// <param name="registry">Chart types registry object.</param> 152 /// <returns>Chart type image.</returns> GetImage(ChartTypeRegistry registry)153 virtual public System.Drawing.Image GetImage(ChartTypeRegistry registry) 154 { 155 return (System.Drawing.Image)registry.ResourceManager.GetObject(this.Name + "ChartType"); 156 } 157 158 /// <summary> 159 /// True if chart type is stacked 160 /// </summary> 161 virtual public bool Stacked { get{ return false;}} 162 163 164 /// <summary> 165 /// True if stacked chart type supports groups 166 /// </summary> 167 virtual public bool SupportStackedGroups { get { return false; } } 168 169 170 /// <summary> 171 /// True if stacked chart type should draw separately positive and 172 /// negative data points ( Bar and column Stacked types ). 173 /// </summary> 174 public bool StackSign { get{ return false;}} 175 176 /// <summary> 177 /// True if chart type supports axeses 178 /// </summary> 179 virtual public bool RequireAxes { get{ return false;} } 180 181 /// <summary> 182 /// Chart type with two y values used for scale ( bubble chart type ) 183 /// </summary> 184 public bool SecondYScale{ get{ return false;} } 185 186 /// <summary> 187 /// True if chart type requires circular chart area. 188 /// </summary> 189 public bool CircularChartArea { get{ return false;} } 190 191 /// <summary> 192 /// True if chart type supports logarithmic axes 193 /// </summary> 194 virtual public bool SupportLogarithmicAxes { get{ return false;} } 195 196 /// <summary> 197 /// True if chart type requires to switch the value (Y) axes position 198 /// </summary> 199 virtual public bool SwitchValueAxes { get{ return false;} } 200 201 /// <summary> 202 /// True if chart series can be placed side-by-side. 203 /// </summary> 204 virtual public bool SideBySideSeries { get{ return false;} } 205 206 /// <summary> 207 /// If the crossing value is auto Crossing value should be 208 /// automatically set to zero for some chart 209 /// types (Bar, column, area etc.) 210 /// </summary> 211 virtual public bool ZeroCrossing { get{ return false;} } 212 213 /// <summary> 214 /// True if each data point of a chart must be represented in the legend 215 /// </summary> 216 virtual public bool DataPointsInLegend { get{ return true;} } 217 218 /// <summary> 219 /// Indicates that extra Y values are connected to the scale of the Y axis 220 /// </summary> 221 virtual public bool ExtraYValuesConnectedToYAxis{ get { return false; } } 222 223 /// <summary> 224 /// Indicates that it's a hundredred percent chart. 225 /// Axis scale from 0 to 100 percent should be used. 226 /// </summary> 227 virtual public bool HundredPercent{ get{return false;} } 228 229 /// <summary> 230 /// Indicates that it's a hundredred percent chart. 231 /// Axis scale from 0 to 100 percent should be used. 232 /// </summary> 233 virtual public bool HundredPercentSupportNegative{ get{return false;} } 234 235 /// <summary> 236 /// True if palette colors should be applied for each data paoint. 237 /// Otherwise the color is applied to the series. 238 /// </summary> 239 virtual public bool ApplyPaletteColorsToPoints { get { return true; } } 240 241 /// <summary> 242 /// How to draw series/points in legend: 243 /// Filled rectangle, Line or Marker 244 /// </summary> 245 /// <param name="series">Legend item series.</param> 246 /// <returns>Legend item style.</returns> GetLegendImageStyle(Series series)247 virtual public LegendImageStyle GetLegendImageStyle(Series series) 248 { 249 return LegendImageStyle.Rectangle; 250 } 251 252 /// <summary> 253 /// Number of supported Y value(s) per point 254 /// </summary> 255 virtual public int YValuesPerPoint{ get { return 1; } } 256 257 /// <summary> 258 /// Chart is Doughnut or Pie type 259 /// </summary> 260 virtual public bool Doughnut{ get { return false; } } 261 262 #endregion 263 264 #region Methods 265 266 /// <summary> 267 /// Default constructor 268 /// </summary> PieChart()269 public PieChart() 270 { 271 } 272 273 274 275 /// <summary> 276 /// Calculates Collected pie slice if required. 277 /// </summary> 278 /// <param name="series">Series to be prepared.</param> PrepareData(Series series)279 internal static void PrepareData(Series series) 280 { 281 // Check series chart type 282 if( String.Compare(series.ChartTypeName, ChartTypeNames.Pie, StringComparison.OrdinalIgnoreCase ) != 0 && 283 String.Compare(series.ChartTypeName, ChartTypeNames.Doughnut, StringComparison.OrdinalIgnoreCase ) != 0 284 ) 285 { 286 return; 287 } 288 289 // Check if collected threshold value is set 290 double threshold = 0.0; 291 if (series.IsCustomPropertySet(CustomPropertyName.CollectedThreshold)) 292 { 293 double t; 294 bool parseSucceed = double.TryParse(series[CustomPropertyName.CollectedThreshold], NumberStyles.Any, CultureInfo.InvariantCulture, out t); 295 if (parseSucceed) 296 { 297 threshold = t; 298 } 299 else 300 { 301 throw (new InvalidOperationException(SR.ExceptionDoughnutCollectedThresholdInvalidFormat)); 302 } 303 304 if (threshold < 0.0) 305 { 306 throw (new InvalidOperationException(SR.ExceptionDoughnutThresholdInvalid)); 307 } 308 } 309 310 // Check if threshold is set 311 if(threshold > 0.0) 312 { 313 // Get reference to the chart control 314 Chart chart = series.Chart; 315 if(chart == null) 316 { 317 throw (new InvalidOperationException(SR.ExceptionDoughnutNullReference)); 318 } 319 320 // Create a temp series which will hold original series data points 321 Series seriesOriginalData = new Series("PIE_ORIGINAL_DATA_" + series.Name, series.YValuesPerPoint); 322 seriesOriginalData.Enabled = false; 323 seriesOriginalData.IsVisibleInLegend = false; 324 chart.Series.Add(seriesOriginalData); 325 foreach(DataPoint dp in series.Points) 326 { 327 seriesOriginalData.Points.Add(dp.Clone()); 328 } 329 330 // Copy temporary design data attribute 331 if(series.IsCustomPropertySet("TempDesignData")) 332 { 333 seriesOriginalData["TempDesignData"] = "true"; 334 } 335 336 // Calculate total value of all data points. IsEmpty points are 337 // ignored. Absolute value is used. 338 double total = 0.0; 339 foreach(DataPoint dp in series.Points) 340 { 341 if(!dp.IsEmpty) 342 { 343 total += Math.Abs(dp.YValues[0]); 344 } 345 } 346 347 // Check if threshold value is set in percents 348 bool percent = true; 349 if(series.IsCustomPropertySet(CustomPropertyName.CollectedThresholdUsePercent)) 350 { 351 if(string.Compare(series[CustomPropertyName.CollectedThresholdUsePercent], "True", StringComparison.OrdinalIgnoreCase) == 0) 352 { 353 percent = true; 354 } 355 else if (string.Compare(series[CustomPropertyName.CollectedThresholdUsePercent], "False", StringComparison.OrdinalIgnoreCase) == 0) 356 { 357 percent = false; 358 } 359 else 360 { 361 throw (new InvalidOperationException(SR.ExceptionDoughnutCollectedThresholdUsePercentInvalid)); 362 } 363 } 364 365 // Convert from percent valur to data point value 366 if(percent) 367 { 368 if(threshold > 100.0) 369 { 370 throw (new InvalidOperationException(SR.ExceptionDoughnutCollectedThresholdInvalidRange)); 371 } 372 373 threshold = total * threshold / 100.0; 374 } 375 376 // Count how many points will be collected and remove collected points 377 DataPoint collectedPoint = null; 378 double collectedTotal = 0.0; 379 int collectedCount = 0; 380 int firstCollectedPointIndex = 0; 381 int originalDataPointIndex = 0; 382 for(int dataPointIndex = 0; dataPointIndex < series.Points.Count; dataPointIndex++) 383 { 384 DataPoint dataPoint = series.Points[dataPointIndex]; 385 if(!dataPoint.IsEmpty && Math.Abs(dataPoint.YValues[0]) <= threshold) 386 { 387 // Keep statistics 388 ++collectedCount; 389 collectedTotal += Math.Abs(dataPoint.YValues[0]); 390 391 // Make a template for the collected point using the first removed point 392 if(collectedPoint == null) 393 { 394 firstCollectedPointIndex = dataPointIndex; 395 collectedPoint = dataPoint.Clone(); 396 } 397 398 // Remove first collected point only when second collected point found 399 if(collectedCount == 2) 400 { 401 series.Points.RemoveAt(firstCollectedPointIndex); 402 --dataPointIndex; 403 } 404 405 // Remove collected point 406 if(collectedCount > 1) 407 { 408 series.Points.RemoveAt(dataPointIndex); 409 --dataPointIndex; 410 } 411 } 412 413 // Set point index that will be used for tooltips 414 dataPoint["OriginalPointIndex"] = originalDataPointIndex.ToString(CultureInfo.InvariantCulture); 415 ++originalDataPointIndex; 416 } 417 418 // Add collected data point into the series 419 if(collectedCount > 1 && collectedPoint != null) 420 { 421 collectedPoint["_COLLECTED_DATA_POINT"] = "TRUE"; 422 collectedPoint.YValues[0] = collectedTotal; 423 series.Points.Add(collectedPoint); 424 425 // Set collected point color 426 if(series.IsCustomPropertySet(CustomPropertyName.CollectedColor)) 427 { 428 ColorConverter colorConverter = new ColorConverter(); 429 try 430 { 431 collectedPoint.Color = (Color)colorConverter.ConvertFromString(null, CultureInfo.InvariantCulture, series[CustomPropertyName.CollectedColor]); 432 } 433 catch 434 { 435 throw (new InvalidOperationException(SR.ExceptionDoughnutCollectedColorInvalidFormat)); 436 } 437 } 438 439 // Set collected point exploded attribute 440 if(series.IsCustomPropertySet(CustomPropertyName.CollectedSliceExploded)) 441 { 442 collectedPoint[CustomPropertyName.Exploded] = series[CustomPropertyName.CollectedSliceExploded]; 443 } 444 445 // Set collected point tooltip 446 if(series.IsCustomPropertySet(CustomPropertyName.CollectedToolTip)) 447 { 448 collectedPoint.ToolTip = series[CustomPropertyName.CollectedToolTip]; 449 } 450 451 // Set collected point legend text 452 if(series.IsCustomPropertySet(CustomPropertyName.CollectedLegendText)) 453 { 454 collectedPoint.LegendText = series[CustomPropertyName.CollectedLegendText]; 455 } 456 else 457 { 458 collectedPoint.LegendText = SR.DescriptionCustomAttributeCollectedLegendDefaultText; 459 } 460 461 // Set collected point label 462 if(series.IsCustomPropertySet(CustomPropertyName.CollectedLabel)) 463 { 464 collectedPoint.Label = series[CustomPropertyName.CollectedLabel]; 465 } 466 } 467 } 468 } 469 470 /// <summary> 471 /// Remove any changes done while preparing Pie/Doughnut charts 472 /// to draw the collected slice. 473 /// </summary> 474 /// <param name="series">Series to be un-prepared.</param> 475 /// <returns>True if series was removed from collection.</returns> UnPrepareData(Series series)476 internal static bool UnPrepareData(Series series) 477 { 478 if(series.Name.StartsWith("PIE_ORIGINAL_DATA_", StringComparison.Ordinal)) 479 { 480 // Get reference to the chart control 481 Chart chart = series.Chart; 482 if(chart == null) 483 { 484 throw (new InvalidOperationException(SR.ExceptionDoughnutNullReference)); 485 } 486 487 // Get original Renko series 488 Series pieSeries = chart.Series[series.Name.Substring(18)]; 489 490 // Copy data back to original Pie series 491 pieSeries.Points.Clear(); 492 if(!series.IsCustomPropertySet("TempDesignData")) 493 { 494 foreach(DataPoint dp in series.Points) 495 { 496 pieSeries.Points.Add(dp); 497 } 498 } 499 500 // Remove series from the collection 501 chart.Series.Remove(series); 502 return true; 503 } 504 return false; 505 } 506 507 508 509 /// <summary> 510 /// Paint Pie Chart 511 /// </summary> 512 /// <param name="graph">The Chart Graphics object</param> 513 /// <param name="common">The Common elements object</param> 514 /// <param name="area">Chart area for this chart</param> 515 /// <param name="seriesToDraw">Chart series to draw.</param> Paint( ChartGraphics graph, CommonElements common, ChartArea area, Series seriesToDraw )516 public void Paint( ChartGraphics graph, CommonElements common, ChartArea area, Series seriesToDraw ) 517 { 518 // Pie chart cannot be combined with other chart types 519 foreach( Series series in common.DataManager.Series ) 520 { 521 // Check if series is visible and belong to the current chart area 522 if( series.IsVisible() && 523 series.ChartArea == area.Name ) 524 { 525 // Check if series chart type matches 526 if( String.Compare( series.ChartTypeName, this.Name, true, System.Globalization.CultureInfo.CurrentCulture ) != 0 ) 527 { 528 if(!common.ChartPicture.SuppressExceptions) 529 { 530 // Pie/Doughnut chart can not be combined with other chart type 531 throw (new InvalidOperationException(SR.ExceptionChartCanNotCombine( this.Name ))); 532 } 533 } 534 } 535 } 536 537 // 3D Pie Chart 538 if( area.Area3DStyle.Enable3D ) 539 { 540 541 float pieWidth = 10 * area.Area3DStyle.PointDepth / 100; 542 543 // Set Clip Region 544 graph.SetClip(area.Position.ToRectangleF()); 545 546 // Make reversed X angle because of default angle. 547 area.Area3DStyle.Inclination *= -1; 548 int oldYAngle = area.Area3DStyle.Rotation; 549 area.Area3DStyle.Rotation = area.GetRealYAngle( ); 550 551 // Draw Pie 552 ProcessChartType3D( false, graph, common, area, pieWidth ); 553 554 // Make reversed X angle because of default angle. 555 area.Area3DStyle.Inclination *= -1; 556 area.Area3DStyle.Rotation = oldYAngle; 557 558 // Reset Clip Region 559 graph.ResetClip(); 560 561 } 562 else 563 { 564 // Reset overlapped labels flag 565 this._labelsOverlap = false; 566 567 //Set Clip Region 568 ((ChartGraphics)graph).SetClip( area.Position.ToRectangleF() ); 569 570 // Resize pie because of labels 571 SizeCorrection( graph, common, area ); 572 573 // Draw Pie labels 574 ProcessChartType( false, graph, common, area, false, LabelsMode.LabelsOverlap ); 575 576 // If overlapping labels are detected they will be drawn in "columns" on each 577 // side of the pie. Adjust plotting area to fit the labels 578 if(this._labelsOverlap) 579 { 580 // Resize pie because of labels 581 SizeCorrection( graph, common, area ); 582 583 // Reset overlapped labels flag 584 this._labelsOverlap = false; 585 586 // Draw Pie labels 587 ProcessChartType( false, graph, common, area, false, LabelsMode.LabelsOverlap ); 588 } 589 590 // Draw Shadow 591 ProcessChartType( false, graph, common, area, true, LabelsMode.Off ); 592 593 // Draw Pie 594 ProcessChartType( false, graph, common, area, false, LabelsMode.Off ); 595 596 // Draw Pie labels 597 ProcessChartType( false, graph, common, area, false, LabelsMode.Draw ); 598 599 //Reset Clip Region 600 ((ChartGraphics)graph).ResetClip(); 601 } 602 } 603 604 /// <summary> 605 /// Take Relative Minimum Pie Size attribute 606 /// </summary> 607 /// <param name="area">Chart Area</param> 608 /// <returns>Custom attribute value.</returns> MinimumRelativePieSize( ChartArea area )609 private double MinimumRelativePieSize( ChartArea area ) 610 { 611 // Default value 612 double minimumSize = 0.3; 613 614 // All data series from chart area which have Pie chart type 615 List<string> typeSeries = area.GetSeriesFromChartType(Name); 616 617 // Data series collection 618 SeriesCollection dataSeries = area.Common.DataManager.Series; 619 620 // Take Relative Minimum Pie Size attribute 621 if(dataSeries[typeSeries[0]].IsCustomPropertySet(CustomPropertyName.MinimumRelativePieSize)) 622 { 623 minimumSize = CommonElements.ParseFloat(dataSeries[typeSeries[0]][CustomPropertyName.MinimumRelativePieSize]) / 100.0; 624 625 // Validation 626 if( minimumSize < 0.1 || minimumSize > 0.7 ) 627 throw (new ArgumentException(SR.ExceptionPieMinimumRelativePieSizeInvalid)); 628 629 } 630 631 return minimumSize; 632 } 633 634 /// <summary> 635 /// Method that is used to resize pie 636 /// because of labels. 637 /// </summary> SizeCorrection( ChartGraphics graph, CommonElements common, ChartArea area )638 private void SizeCorrection( ChartGraphics graph, CommonElements common, ChartArea area ) 639 { 640 float correction = (this._labelsOverlap) ? this._sizeCorrection : 0.95F; 641 _sliceExploded = false; 642 643 // Estimate Labels 644 if( area.InnerPlotPosition.Auto ) 645 { 646 for( ; correction >= (float)MinimumRelativePieSize( area ); correction -= 0.05F ) 647 { 648 // Decrease Pie size 649 this._sizeCorrection = correction; 650 651 // Check if labels fit. 652 ProcessChartType( false, graph, common, area, false, LabelsMode.EstimateSize ); 653 if( _labelsFit ) 654 { 655 break; 656 } 657 } 658 659 // Size correction for exploded pie can not be larger then 0.8 660 if( _sliceExploded && _sizeCorrection > 0.8F ) 661 { 662 _sizeCorrection = 0.8F; 663 } 664 } 665 else 666 { 667 _sizeCorrection = 0.95F; 668 } 669 } 670 671 /// <summary> 672 /// This method recalculates position of pie slices 673 /// or checks if pie slice is selected. 674 /// </summary> 675 /// <param name="selection">If True selection mode is active, otherwise paint mode is active</param> 676 /// <param name="graph">The Chart Graphics object</param> 677 /// <param name="common">The Common elements object</param> 678 /// <param name="area">Chart area for this chart</param> 679 /// <param name="shadow">Draw pie shadow</param> 680 /// <param name="labels">Pie labels</param> ProcessChartType( bool selection, ChartGraphics graph, CommonElements common, ChartArea area, bool shadow, LabelsMode labels )681 private void ProcessChartType( bool selection, ChartGraphics graph, CommonElements common, ChartArea area, bool shadow, LabelsMode labels ) 682 { 683 float startAngle = 0; // Angle in degrees measured clockwise from the x-axis to the first side of the pie section. 684 string explodedAttrib = ""; // Exploded attribute 685 bool exploded; // Exploded pie slice 686 float midAngle; // Angle between Start Angle and End Angle 687 688 // Data series collection 689 SeriesCollection dataSeries = common.DataManager.Series; 690 691 // Clear Labels overlap collection 692 if( labels == LabelsMode.LabelsOverlap ) 693 { 694 _labelsRectangles.Clear(); 695 } 696 697 // All data series from chart area which have Pie chart type 698 List<string> typeSeries = area.GetSeriesFromChartType(Name); 699 if(typeSeries.Count == 0) 700 { 701 return; 702 } 703 704 // Get first pie starting angle 705 if(typeSeries.Count > 0) 706 { 707 if (dataSeries[typeSeries[0]].IsCustomPropertySet(CustomPropertyName.PieStartAngle)) 708 { 709 float angle; 710 bool parseSucceed = float.TryParse(dataSeries[typeSeries[0]][CustomPropertyName.PieStartAngle], NumberStyles.Any, CultureInfo.InvariantCulture, out angle); 711 if (parseSucceed) 712 { 713 startAngle = angle; 714 } 715 716 if (!parseSucceed || startAngle > 360f || startAngle < 0f) 717 { 718 throw (new InvalidOperationException(SR.ExceptionCustomAttributeAngleOutOfRange("PieStartAngle"))); 719 } 720 } 721 } 722 723 // Call Back Paint event 724 if( !selection ) 725 { 726 common.Chart.CallOnPrePaint(new ChartPaintEventArgs(dataSeries[typeSeries[0]], graph, common, area.PlotAreaPosition)); 727 } 728 729 // The data points loop. Find Sum of data points. 730 double sum = 0.0; 731 foreach( DataPoint point in dataSeries[typeSeries[0]].Points ) 732 { 733 if( !point.IsEmpty ) 734 { 735 sum += Math.Abs( point.YValues[0] ); 736 } 737 } 738 739 // No points or all points have zero values 740 if(sum == 0.0) 741 { 742 return; 743 } 744 745 // Take radius attribute 746 float doughnutRadius = 60f; 747 if(dataSeries[typeSeries[0]].IsCustomPropertySet(CustomPropertyName.DoughnutRadius)) 748 { 749 doughnutRadius = CommonElements.ParseFloat(dataSeries[typeSeries[0]][CustomPropertyName.DoughnutRadius]); 750 751 // Validation 752 if( doughnutRadius < 0f || doughnutRadius > 99f ) 753 throw (new ArgumentException(SR.ExceptionPieRadiusInvalid)); 754 755 } 756 757 // This method is introduced to check colors of palette. For 758 // pie chart the first pie slice and the second pie slice can 759 // not have same color because they are connected. 760 CheckPaleteColors( dataSeries[typeSeries[0]].Points ); 761 762 //************************************************************ 763 //** Data point loop 764 //************************************************************ 765 int pointIndx = 0; 766 int nonEmptyPointIndex = 0; 767 foreach( DataPoint point in dataSeries[typeSeries[0]].Points ) 768 { 769 // Do not process empty points 770 if( point.IsEmpty ) 771 { 772 pointIndx++; 773 continue; 774 } 775 776 // Rectangle size 777 RectangleF rectangle; 778 if( area.InnerPlotPosition.Auto ) 779 { 780 rectangle = new RectangleF( area.Position.ToRectangleF().X, area.Position.ToRectangleF().Y, area.Position.ToRectangleF().Width, area.Position.ToRectangleF().Height ); 781 } 782 else 783 { 784 rectangle = new RectangleF( area.PlotAreaPosition.ToRectangleF().X, area.PlotAreaPosition.ToRectangleF().Y, area.PlotAreaPosition.ToRectangleF().Width, area.PlotAreaPosition.ToRectangleF().Height ); 785 } 786 if(rectangle.Width < 0f || rectangle.Height < 0f) 787 { 788 return; 789 } 790 791 // Find smallest edge 792 SizeF absoluteSize = graph.GetAbsoluteSize( new SizeF( rectangle.Width, rectangle.Height ) ); 793 float absRadius = ( absoluteSize.Width < absoluteSize.Height ) ? absoluteSize.Width : absoluteSize.Height; 794 795 // Size of the square, which will be used for drawing pie. 796 SizeF relativeSize = graph.GetRelativeSize( new SizeF( absRadius, absRadius ) ); 797 798 // Center of the pie 799 PointF middlePoint = new PointF( rectangle.X + rectangle.Width / 2, rectangle.Y + rectangle.Height / 2 ); 800 801 // Rectangle which will always create circle, never ellipse. 802 rectangle = new RectangleF( middlePoint.X - relativeSize.Width / 2, middlePoint.Y - relativeSize.Height / 2, relativeSize.Width, relativeSize.Height ); 803 804 // Size correction because of exploded or labels 805 if( _sizeCorrection != 1 ) 806 { 807 rectangle.X += rectangle.Width * ( 1 - _sizeCorrection ) / 2; 808 rectangle.Y += rectangle.Height * ( 1 - _sizeCorrection ) / 2; 809 rectangle.Width = rectangle.Width * _sizeCorrection; 810 rectangle.Height = rectangle.Height * _sizeCorrection; 811 812 // Adjust inner plot position 813 if(area.InnerPlotPosition.Auto) 814 { 815 RectangleF rect = rectangle; 816 rect.X = (rect.X - area.Position.X) / area.Position.Width * 100f; 817 rect.Y = (rect.Y - area.Position.Y) / area.Position.Height * 100f; 818 rect.Width = rect.Width / area.Position.Width * 100f; 819 rect.Height = rect.Height / area.Position.Height * 100f; 820 area.InnerPlotPosition.SetPositionNoAuto(rect.X, rect.Y, rect.Width, rect.Height); 821 } 822 } 823 824 float sweepAngle = (float)( Math.Abs(point.YValues[0]) / sum * 360); 825 826 // Check Exploded attribute for data point 827 exploded = false; 828 if(point.IsCustomPropertySet(CustomPropertyName.Exploded)) 829 { 830 explodedAttrib = point[CustomPropertyName.Exploded]; 831 if( String.Compare(explodedAttrib,"true", StringComparison.OrdinalIgnoreCase) == 0 ) 832 exploded = true; 833 else 834 exploded = false; 835 } 836 837 Color pieLineColor = Color.Empty; 838 ColorConverter colorConverter = new ColorConverter(); 839 840 // Check if special color properties are set 841 if(point.IsCustomPropertySet(CustomPropertyName.PieLineColor) || dataSeries[typeSeries[0]].IsCustomPropertySet(CustomPropertyName.PieLineColor)) 842 { 843 bool failed = false; 844 try 845 { 846 pieLineColor = (Color)colorConverter.ConvertFromString( 847 (point.IsCustomPropertySet(CustomPropertyName.PieLineColor)) ? point[CustomPropertyName.PieLineColor] : dataSeries[typeSeries[0]][CustomPropertyName.PieLineColor]); 848 failed = false; 849 } 850 catch (ArgumentException) 851 { 852 failed = true; 853 } 854 catch (NotSupportedException) 855 { 856 failed = true; 857 } 858 859 if (failed) 860 { 861 pieLineColor = (Color)colorConverter.ConvertFromInvariantString( 862 (point.IsCustomPropertySet(CustomPropertyName.PieLineColor)) ? point[CustomPropertyName.PieLineColor] : dataSeries[typeSeries[0]][CustomPropertyName.PieLineColor]); 863 } 864 } 865 866 // Find Direction to move exploded pie slice 867 if( exploded ) 868 { 869 _sliceExploded = true; 870 midAngle = ( 2 * startAngle + sweepAngle ) / 2; 871 double xComponent = Math.Cos( midAngle * Math.PI / 180 ) * rectangle.Width / 10; 872 double yComponent = Math.Sin( midAngle * Math.PI / 180 ) * rectangle.Height / 10; 873 874 rectangle.Offset( (float)xComponent, (float)yComponent ); 875 } 876 877 // Hot regions of the data points. Labels hot regions are processed aftre drawing. 878 if( common.ProcessModeRegions && labels == LabelsMode.Draw ) 879 { 880 Map( common, point, startAngle, sweepAngle, rectangle, Doughnut, doughnutRadius, graph, pointIndx ); 881 } 882 883 // Painting mode 884 if( common.ProcessModePaint ) 885 { 886 // Draw Shadow 887 if( shadow ) 888 { 889 double offset = graph.GetRelativeSize( new SizeF( point.series.ShadowOffset, point.series.ShadowOffset ) ).Width; 890 891 // Offset is zero. Do not draw shadow pie slice. 892 if( offset == 0.0 ) 893 { 894 break; 895 } 896 897 // Shadow Rectangle 898 RectangleF shadowRect = new RectangleF( rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height ); 899 shadowRect.Offset( (float)offset, (float)offset ); 900 901 // Change shadow color 902 Color shcolor = new Color(); 903 Color shGradientColor = new Color(); 904 Color shBorderColor = new Color(); 905 906 // Solid color 907 if( point.Color.A != 255 ) 908 shcolor = Color.FromArgb( point.Color.A/2, point.series.ShadowColor ); 909 else 910 shcolor = point.series.ShadowColor; 911 912 // Gradient Color 913 if( !point.BackSecondaryColor.IsEmpty ) 914 { 915 if( point.BackSecondaryColor.A != 255 ) 916 shGradientColor = Color.FromArgb( point.BackSecondaryColor.A/2, point.series.ShadowColor ); 917 else 918 shGradientColor = point.series.ShadowColor; 919 } 920 else 921 shGradientColor = Color.Empty; 922 923 // Border color 924 if( !point.BorderColor.IsEmpty ) 925 { 926 if( point.BorderColor.A != 255 ) 927 shBorderColor = Color.FromArgb( point.BorderColor.A/2, point.series.ShadowColor ); 928 else 929 shBorderColor = point.series.ShadowColor; 930 } 931 else 932 shBorderColor = Color.Empty; 933 934 // Draw shadow of pie slice 935 graph.DrawPieRel( 936 shadowRect, 937 startAngle, 938 sweepAngle, 939 shcolor, 940 ChartHatchStyle.None, 941 "", 942 point.BackImageWrapMode, 943 point.BackImageTransparentColor, 944 point.BackGradientStyle, 945 shGradientColor, 946 shBorderColor, 947 point.BorderWidth, 948 point.BorderDashStyle, 949 true, 950 Doughnut, 951 doughnutRadius, 952 PieDrawingStyle.Default); 953 } 954 else 955 { 956 if( labels == LabelsMode.Off ) 957 { 958 // Start Svg Selection mode 959 graph.StartHotRegion( point ); 960 961 // Draw pie slice 962 graph.DrawPieRel( 963 rectangle, 964 startAngle, 965 sweepAngle, 966 point.Color, 967 point.BackHatchStyle, 968 point.BackImage, 969 point.BackImageWrapMode, 970 point.BackImageTransparentColor, 971 point.BackGradientStyle, 972 point.BackSecondaryColor, 973 point.BorderColor, 974 point.BorderWidth, 975 point.BorderDashStyle, 976 false, 977 Doughnut, 978 doughnutRadius, 979 ChartGraphics.GetPieDrawingStyle(point) ); 980 981 // End Svg Selection mode 982 graph.EndHotRegion( ); 983 } 984 } 985 986 // Estimate labels 987 if( labels == LabelsMode.EstimateSize ) 988 { 989 EstimateLabels( graph, middlePoint, rectangle.Size, startAngle, sweepAngle, point, exploded, area ); 990 if( _labelsFit == false ) 991 { 992 return; 993 } 994 } 995 996 // Labels overlap test 997 if( labels == LabelsMode.LabelsOverlap ) 998 { 999 DrawLabels( graph, middlePoint, rectangle.Size, startAngle, sweepAngle, point, doughnutRadius, exploded, area, true, nonEmptyPointIndex, pieLineColor ); 1000 } 1001 1002 // Draw labels and markers 1003 if( labels == LabelsMode.Draw ) 1004 { 1005 DrawLabels( graph, middlePoint, rectangle.Size, startAngle, sweepAngle, point, doughnutRadius, exploded, area, false, nonEmptyPointIndex, pieLineColor ); 1006 } 1007 1008 } 1009 1010 if( common.ProcessModeRegions && labels == LabelsMode.Draw ) 1011 { 1012 // Add labels hot regions if it was not done during the painting 1013 if( !common.ProcessModePaint ) 1014 { 1015 DrawLabels( graph, middlePoint, rectangle.Size, startAngle, sweepAngle, point, doughnutRadius, exploded, area, false, nonEmptyPointIndex, pieLineColor ); 1016 } 1017 } 1018 1019 1020 //************************************************** 1021 //** Remember point relative position 1022 //************************************************** 1023 point.positionRel = new PointF(float.NaN, float.NaN); 1024 1025 1026 // If exploded the shift is bigger 1027 float expShift = 1; 1028 if( exploded ) 1029 expShift = 1.2F; 1030 1031 midAngle = startAngle + sweepAngle / 2; 1032 1033 // Find first line position 1034 point.positionRel.X = (float)Math.Cos( (midAngle) * Math.PI / 180 ) * rectangle.Width * expShift / 2 + middlePoint.X; 1035 point.positionRel.Y = (float)Math.Sin( (midAngle) * Math.PI / 180 ) * rectangle.Height * expShift / 2 + middlePoint.Y; 1036 1037 // Increase point index and sweep angle 1038 pointIndx++; 1039 nonEmptyPointIndex++; 1040 startAngle += sweepAngle; 1041 if(startAngle >= 360) 1042 { 1043 startAngle -= 360; 1044 } 1045 } 1046 1047 if( labels == LabelsMode.LabelsOverlap && this._labelsOverlap ) 1048 { 1049 this._labelsOverlap = PrepareLabels( area.Position.ToRectangleF() ); 1050 } 1051 1052 // Call Paint event 1053 if( !selection ) 1054 { 1055 common.Chart.CallOnPostPaint(new ChartPaintEventArgs(dataSeries[typeSeries[0]], graph, common, area.PlotAreaPosition)); 1056 } 1057 } 1058 1059 /// <summary> 1060 /// Draw Pie labels or test for overlaping. 1061 /// </summary> 1062 /// <param name="graph">Chart Graphics object</param> 1063 /// <param name="middlePoint">Center of the pie chart</param> 1064 /// <param name="relativeSize">Size of the square, which will be used for drawing pie.</param> 1065 /// <param name="startAngle">Starting angle of a pie slice</param> 1066 /// <param name="sweepAngle">Sweep angle of a pie slice</param> 1067 /// <param name="point">Data point</param> 1068 /// <param name="doughnutRadius">Radius for Doughnut Chart in %</param> 1069 /// <param name="exploded">The pie slice is exploded</param> 1070 /// <param name="area">Chart area</param> 1071 /// <param name="overlapTest">True if test mode is on</param> 1072 /// <param name="pointIndex">Data Point Index</param> 1073 /// <param name="pieLineColor">Color of line labels</param> DrawLabels( ChartGraphics graph, PointF middlePoint, SizeF relativeSize, float startAngle, float sweepAngle, DataPoint point, float doughnutRadius, bool exploded, ChartArea area, bool overlapTest, int pointIndex, Color pieLineColor )1074 public void DrawLabels( ChartGraphics graph, PointF middlePoint, SizeF relativeSize, float startAngle, float sweepAngle, DataPoint point, float doughnutRadius, bool exploded, ChartArea area, bool overlapTest, int pointIndex, Color pieLineColor ) 1075 { 1076 bool added = false; // Indicates that label position was added 1077 float x; // Label Position 1078 float y; // Label Position 1079 Series series; // Data Series 1080 float labelsHorizontalLineSize = 1; // Horizontal line size for outside labels 1081 float labelsRadialLineSize = 1; // Radial line size for outside labels 1082 string text; 1083 1084 // Disable the clip region 1085 Region oldClipRegion = graph.Clip; 1086 graph.Clip = new Region(); 1087 1088 // Get label text 1089 text = this.GetLabelText( point ); 1090 if(text.Length == 0) 1091 { 1092 return; 1093 } 1094 1095 float shift; 1096 1097 series = point.series; 1098 1099 PieLabelStyle style = PieLabelStyle.Inside; 1100 1101 // Get label style attribute from series 1102 if(series.IsCustomPropertySet(CustomPropertyName.LabelStyle)) 1103 { 1104 string labelStyleAttrib = series[CustomPropertyName.LabelStyle]; 1105 1106 // Labels Disabled 1107 if( String.Compare(labelStyleAttrib,"disabled",StringComparison.OrdinalIgnoreCase) == 0 ) 1108 style = PieLabelStyle.Disabled; 1109 else if (String.Compare(labelStyleAttrib, "outside", StringComparison.OrdinalIgnoreCase) == 0) 1110 style = PieLabelStyle.Outside; 1111 else 1112 style = PieLabelStyle.Inside; 1113 } 1114 else if(series.IsCustomPropertySet(CustomPropertyName.PieLabelStyle)) 1115 { 1116 string labelStyleAttrib = series[CustomPropertyName.PieLabelStyle]; 1117 1118 // Labels Disabled 1119 if (String.Compare(labelStyleAttrib, "disabled", StringComparison.OrdinalIgnoreCase) == 0) 1120 style = PieLabelStyle.Disabled; 1121 else if (String.Compare(labelStyleAttrib, "outside", StringComparison.OrdinalIgnoreCase) == 0) 1122 style = PieLabelStyle.Outside; 1123 else 1124 style = PieLabelStyle.Inside; 1125 } 1126 1127 // Get label style attribute from point 1128 if(point.IsCustomPropertySet(CustomPropertyName.LabelStyle)) 1129 { 1130 string labelStyleAttrib = point[CustomPropertyName.LabelStyle]; 1131 1132 // Labels Disabled 1133 if (String.Compare(labelStyleAttrib, "disabled", StringComparison.OrdinalIgnoreCase) == 0) 1134 style = PieLabelStyle.Disabled; 1135 else if (String.Compare(labelStyleAttrib, "outside", StringComparison.OrdinalIgnoreCase) == 0) 1136 style = PieLabelStyle.Outside; 1137 else 1138 style = PieLabelStyle.Inside; 1139 } 1140 // Get label style attribute from point 1141 else if(point.IsCustomPropertySet(CustomPropertyName.PieLabelStyle)) 1142 { 1143 string labelStyleAttrib = point[CustomPropertyName.PieLabelStyle]; 1144 1145 // Labels Disabled 1146 if (String.Compare(labelStyleAttrib, "disabled", StringComparison.OrdinalIgnoreCase) == 0) 1147 style = PieLabelStyle.Disabled; 1148 else if (String.Compare(labelStyleAttrib, "outside", StringComparison.OrdinalIgnoreCase) == 0) 1149 style = PieLabelStyle.Outside; 1150 else 1151 style = PieLabelStyle.Inside; 1152 } 1153 1154 1155 // Take labels radial line size attribute from series 1156 if(series.IsCustomPropertySet(CustomPropertyName.LabelsRadialLineSize)) 1157 { 1158 string labelsRadialLineSizeAttrib = series[CustomPropertyName.LabelsRadialLineSize]; 1159 labelsRadialLineSize = CommonElements.ParseFloat( labelsRadialLineSizeAttrib); 1160 1161 // Validation 1162 if( labelsRadialLineSize < 0 || labelsRadialLineSize > 100 ) 1163 throw new InvalidOperationException(SR.ExceptionPieRadialLineSizeInvalid); 1164 } 1165 1166 // Take labels radial line size attribute from point 1167 if(point.IsCustomPropertySet(CustomPropertyName.LabelsRadialLineSize)) 1168 { 1169 string labelsRadialLineSizeAttrib = point[CustomPropertyName.LabelsRadialLineSize]; 1170 labelsRadialLineSize = CommonElements.ParseFloat( labelsRadialLineSizeAttrib); 1171 1172 // Validation 1173 if( labelsRadialLineSize < 0 || labelsRadialLineSize > 100 ) 1174 throw new InvalidOperationException(SR.ExceptionPieRadialLineSizeInvalid); 1175 } 1176 1177 // Take labels horizontal line size attribute from series 1178 if(series.IsCustomPropertySet(CustomPropertyName.LabelsHorizontalLineSize)) 1179 { 1180 string labelsHorizontalLineSizeAttrib = series[CustomPropertyName.LabelsHorizontalLineSize]; 1181 labelsHorizontalLineSize = CommonElements.ParseFloat( labelsHorizontalLineSizeAttrib); 1182 1183 // Validation 1184 if( labelsHorizontalLineSize < 0 || labelsHorizontalLineSize > 100 ) 1185 throw new InvalidOperationException(SR.ExceptionPieHorizontalLineSizeInvalid); 1186 } 1187 1188 // Take labels horizontal line size attribute from point 1189 if(point.IsCustomPropertySet(CustomPropertyName.LabelsHorizontalLineSize)) 1190 { 1191 string labelsHorizontalLineSizeAttrib = point[CustomPropertyName.LabelsHorizontalLineSize]; 1192 labelsHorizontalLineSize = CommonElements.ParseFloat( labelsHorizontalLineSizeAttrib); 1193 1194 // Validation 1195 if( labelsHorizontalLineSize < 0 || labelsHorizontalLineSize > 100 ) 1196 throw new InvalidOperationException(SR.ExceptionPieHorizontalLineSizeInvalid); 1197 } 1198 1199 float expShift = 1; 1200 1201 // ******************************************** 1202 // Labels are set inside pie 1203 // ******************************************** 1204 if( style == PieLabelStyle.Inside && !overlapTest ) 1205 { 1206 float width; 1207 float height; 1208 1209 // If exploded the shift is bigger 1210 if( exploded ) 1211 { 1212 expShift = 1.4F; 1213 } 1214 1215 // Get offset of the inside labels position 1216 // NOTE: This custom attribute is NOT released! 1217 float positionRatio = 4.0f; 1218 if(point.IsCustomPropertySet("InsideLabelOffset")) 1219 { 1220 bool parseSucceed = float.TryParse(point["InsideLabelOffset"], NumberStyles.Any, CultureInfo.InvariantCulture, out positionRatio); 1221 if(!parseSucceed || positionRatio < 0f || positionRatio > 100f) 1222 { 1223 throw(new InvalidOperationException(SR.ExceptionCustomAttributeIsNotInRange0to100("InsideLabelOffset"))); 1224 } 1225 positionRatio = 4f / (1f + positionRatio / 100f); 1226 } 1227 1228 1229 // Shift the string for Doughnut type 1230 if( Doughnut ) 1231 { 1232 width = relativeSize.Width * expShift / positionRatio * ( 1 + ( 100 - doughnutRadius ) / 100F ); 1233 height = relativeSize.Height * expShift / positionRatio * ( 1 + ( 100 - doughnutRadius ) / 100F ); 1234 } 1235 else 1236 { 1237 width = relativeSize.Width * expShift / positionRatio; 1238 height = relativeSize.Height * expShift / positionRatio; 1239 } 1240 1241 // Find string position 1242 x = (float)Math.Cos( (startAngle + sweepAngle / 2) * Math.PI / 180 ) * width + middlePoint.X; 1243 y = (float)Math.Sin( (startAngle + sweepAngle / 2) * Math.PI / 180 ) * height + middlePoint.Y; 1244 1245 // Center the string horizontally and vertically. 1246 using (StringFormat format = new StringFormat()) 1247 { 1248 format.Alignment = StringAlignment.Center; 1249 format.LineAlignment = StringAlignment.Center; 1250 1251 SizeF sizeFont = graph.GetRelativeSize( 1252 graph.MeasureString( 1253 text.Replace("\\n", "\n"), 1254 point.Font, 1255 new SizeF(1000f, 1000f), 1256 StringFormat.GenericTypographic)); 1257 1258 // Get label background position 1259 RectangleF labelBackPosition = RectangleF.Empty; 1260 SizeF sizeLabel = new SizeF(sizeFont.Width, sizeFont.Height); 1261 sizeLabel.Height += sizeLabel.Height / 8; 1262 sizeLabel.Width += sizeLabel.Width / text.Length; 1263 labelBackPosition = PointChart.GetLabelPosition( 1264 graph, 1265 new PointF(x, y), 1266 sizeLabel, 1267 format, 1268 true); 1269 1270 // Draw the label inside the pie 1271 using (Brush brush = new SolidBrush(point.LabelForeColor)) 1272 { 1273 graph.DrawPointLabelStringRel( 1274 area.Common, 1275 text, 1276 point.Font, 1277 brush, 1278 new PointF(x, y), 1279 format, 1280 point.LabelAngle, 1281 labelBackPosition, 1282 point.LabelBackColor, 1283 point.LabelBorderColor, 1284 point.LabelBorderWidth, 1285 point.LabelBorderDashStyle, 1286 series, 1287 point, 1288 pointIndex); 1289 } 1290 } 1291 } 1292 1293 // ******************************************** 1294 // Labels are set outside pie 1295 // ******************************************** 1296 else if( style == PieLabelStyle.Outside ) 1297 { 1298 1299 // Coefficient which represent shift from pie border 1300 shift = 0.5F + labelsRadialLineSize * 0.1F; 1301 1302 // If exploded the shift is bigger 1303 if( exploded ) 1304 expShift = 1.2F; 1305 1306 float midAngle = startAngle + sweepAngle / 2; 1307 1308 // Find first line position 1309 float x1 = (float)Math.Cos( (midAngle) * Math.PI / 180 ) * relativeSize.Width * expShift / 2 + middlePoint.X; 1310 float y1 = (float)Math.Sin( (midAngle) * Math.PI / 180 ) * relativeSize.Height * expShift / 2 + middlePoint.Y; 1311 1312 float x2 = (float)Math.Cos( (midAngle) * Math.PI / 180 ) * relativeSize.Width * shift * expShift + middlePoint.X; 1313 float y2 = (float)Math.Sin( (midAngle) * Math.PI / 180 ) * relativeSize.Height * shift * expShift + middlePoint.Y; 1314 1315 if( pieLineColor == Color.Empty ) 1316 { 1317 pieLineColor = point.BorderColor; 1318 } 1319 1320 // Draw first line 1321 if( !overlapTest ) 1322 { 1323 graph.DrawLineRel( pieLineColor, point.BorderWidth, ChartDashStyle.Solid, new PointF( x1, y1 ), new PointF( x2, y2 ) ); 1324 } 1325 1326 // Set string alingment 1327 using (StringFormat format = new StringFormat()) 1328 { 1329 format.Alignment = StringAlignment.Center; 1330 format.LineAlignment = StringAlignment.Center; 1331 1332 // Find second line position 1333 float y3 = (float)Math.Sin((midAngle) * Math.PI / 180) * relativeSize.Height * shift * expShift + middlePoint.Y; 1334 float x3; 1335 float x3Overlap; 1336 1337 RectangleF labelRect = RectangleF.Empty; 1338 RectangleF labelRectOver = RectangleF.Empty; 1339 1340 if (midAngle > 90 && midAngle < 270) 1341 { 1342 format.Alignment = StringAlignment.Far; 1343 x3Overlap = -relativeSize.Width * shift * expShift + middlePoint.X - relativeSize.Width / 10 * labelsHorizontalLineSize; 1344 x3 = (float)Math.Cos((midAngle) * Math.PI / 180) * relativeSize.Width * shift * expShift + middlePoint.X - relativeSize.Width / 10 * labelsHorizontalLineSize; 1345 1346 if (overlapTest) 1347 { 1348 x3Overlap = x3; 1349 } 1350 1351 // This method returns calculated rectangle from point position 1352 // for outside label. Rectangle mustn�t be out of chart area. 1353 labelRect = GetLabelRect(new PointF(x3, y3), area, text, format, graph, point, true); 1354 labelRectOver = GetLabelRect(new PointF(x3Overlap, y3), area, text, format, graph, point, true); 1355 } 1356 else 1357 { 1358 format.Alignment = StringAlignment.Near; 1359 1360 x3Overlap = relativeSize.Width * shift * expShift + middlePoint.X + relativeSize.Width / 10 * labelsHorizontalLineSize; 1361 x3 = (float)Math.Cos((midAngle) * Math.PI / 180) * relativeSize.Width * shift * expShift + middlePoint.X + relativeSize.Width / 10 * labelsHorizontalLineSize; 1362 1363 if (overlapTest) 1364 { 1365 x3Overlap = x3; 1366 } 1367 1368 // This method returns calculated rectangle from point position 1369 // for outside label. Rectangle mustn�t be out of chart area. 1370 labelRect = GetLabelRect(new PointF(x3, y3), area, text, format, graph, point, false); 1371 labelRectOver = GetLabelRect(new PointF(x3Overlap, y3), area, text, format, graph, point, false); 1372 } 1373 1374 // Draw second line 1375 if (!overlapTest) 1376 { 1377 if (this._labelsOverlap) 1378 { 1379 float calculatedY3 = (((RectangleF)this._labelsRectangles[pointIndex]).Top + ((RectangleF)this._labelsRectangles[pointIndex]).Bottom) / 2f; 1380 graph.DrawLineRel(pieLineColor, point.BorderWidth, ChartDashStyle.Solid, new PointF(x2, y2), new PointF(x3Overlap, calculatedY3)); 1381 } 1382 else 1383 { 1384 graph.DrawLineRel(pieLineColor, point.BorderWidth, ChartDashStyle.Solid, new PointF(x2, y2), new PointF(x3, y3)); 1385 } 1386 } 1387 1388 // Draw the string 1389 if (!overlapTest) 1390 { 1391 RectangleF rect = new RectangleF(labelRect.Location, labelRect.Size); 1392 if (this._labelsOverlap) 1393 { 1394 // Draw label from collection if original labels overlap. 1395 rect = (RectangleF)this._labelsRectangles[pointIndex]; 1396 rect.X = labelRectOver.X; 1397 rect.Width = labelRectOver.Width; 1398 } 1399 1400 // Get label background position 1401 SizeF valueTextSize = graph.MeasureStringRel(text.Replace("\\n", "\n"), point.Font); 1402 valueTextSize.Height += valueTextSize.Height / 8; 1403 float spacing = valueTextSize.Width / text.Length / 2; 1404 valueTextSize.Width += spacing; 1405 RectangleF labelBackPosition = new RectangleF( 1406 rect.X, 1407 rect.Y + rect.Height / 2f - valueTextSize.Height / 2f, 1408 valueTextSize.Width, 1409 valueTextSize.Height); 1410 1411 // Adjust position based on alignment 1412 if (format.Alignment == StringAlignment.Near) 1413 { 1414 labelBackPosition.X -= spacing / 2f; 1415 } 1416 else if (format.Alignment == StringAlignment.Center) 1417 { 1418 labelBackPosition.X = rect.X + (rect.Width - valueTextSize.Width) / 2f; 1419 } 1420 else if (format.Alignment == StringAlignment.Far) 1421 { 1422 labelBackPosition.X = rect.Right - valueTextSize.Width - spacing / 2f; 1423 } 1424 1425 // Draw label text outside 1426 using (Brush brush = new SolidBrush(point.LabelForeColor)) 1427 { 1428 graph.DrawPointLabelStringRel( 1429 area.Common, 1430 text, 1431 point.Font, 1432 brush, 1433 rect, 1434 format, 1435 point.LabelAngle, 1436 labelBackPosition, 1437 point.LabelBackColor, 1438 point.LabelBorderColor, 1439 point.LabelBorderWidth, 1440 point.LabelBorderDashStyle, 1441 series, 1442 point, 1443 pointIndex); 1444 } 1445 } 1446 else 1447 { 1448 // Insert labels in label collection. This 1449 // code is executed only if labels overlap. 1450 this.InsertOverlapLabel(labelRectOver); 1451 added = true; 1452 } 1453 } 1454 } 1455 // Restore old clip region 1456 graph.Clip = oldClipRegion; 1457 1458 1459 // Add empty overlap empty position 1460 if(!added) 1461 { 1462 InsertOverlapLabel( RectangleF.Empty ); 1463 } 1464 1465 return; 1466 1467 } 1468 1469 1470 /// <summary> 1471 /// This method returns calculated rectangle from point position 1472 /// for outside label. Rectangle mustn�t be out of chart area. 1473 /// </summary> 1474 /// <param name="labelPosition">The first position for label</param> 1475 /// <param name="area">Chart area used for chart area position</param> 1476 /// <param name="text">Label text</param> 1477 /// <param name="format">Text format</param> 1478 /// <param name="graph">Chart Graphics object</param> 1479 /// <param name="point">Data point</param> 1480 /// <param name="leftOrientation">Orientation for label. It could be left or right.</param> 1481 /// <returns>Calculated rectangle for label</returns> GetLabelRect( PointF labelPosition, ChartArea area, string text, StringFormat format, ChartGraphics graph, DataPoint point, bool leftOrientation )1482 private RectangleF GetLabelRect( PointF labelPosition, ChartArea area, string text, StringFormat format, ChartGraphics graph, DataPoint point, bool leftOrientation ) 1483 { 1484 RectangleF labelRect = RectangleF.Empty; 1485 if( leftOrientation ) 1486 { 1487 labelRect.X = area.Position.X; 1488 labelRect.Y = area.Position.Y; 1489 labelRect.Width = labelPosition.X - area.Position.X; 1490 labelRect.Height = area.Position.Height; 1491 } 1492 else 1493 { 1494 labelRect.X = labelPosition.X; 1495 labelRect.Y = area.Position.Y; 1496 labelRect.Width = area.Position.Right - labelPosition.X; 1497 labelRect.Height = area.Position.Height; 1498 } 1499 1500 // Find bounding rectangle of the text 1501 SizeF size = graph.MeasureStringRel( text.Replace("\\n", "\n"), point.Font, labelRect.Size, format ); 1502 labelRect.Y = labelPosition.Y - size.Height / 2 * 1.8f; 1503 labelRect.Height = size.Height * 1.8f; 1504 1505 return labelRect; 1506 } 1507 1508 1509 1510 /// <summary> 1511 /// This method returns Pie Label Style enumeration 1512 /// from Data Point Custom attribute. 1513 /// </summary> 1514 /// <param name="point">Data Point</param> 1515 /// <returns>Pie label style enumeration</returns> GetLabelStyle( DataPoint point )1516 private PieLabelStyle GetLabelStyle( DataPoint point ) 1517 { 1518 Series series = point.series; 1519 1520 PieLabelStyle style = PieLabelStyle.Inside; 1521 1522 // Get label style attribute from series 1523 if(series.IsCustomPropertySet(CustomPropertyName.LabelStyle)) 1524 { 1525 string labelStyleAttrib = series[CustomPropertyName.LabelStyle]; 1526 1527 // Labels Disabled 1528 if (String.Compare(labelStyleAttrib, "disabled", StringComparison.OrdinalIgnoreCase) == 0) 1529 style = PieLabelStyle.Disabled; 1530 else if (String.Compare(labelStyleAttrib, "outside", StringComparison.OrdinalIgnoreCase) == 0) 1531 style = PieLabelStyle.Outside; 1532 else 1533 style = PieLabelStyle.Inside; 1534 } 1535 else if(series.IsCustomPropertySet(CustomPropertyName.PieLabelStyle)) 1536 { 1537 string labelStyleAttrib = series[CustomPropertyName.PieLabelStyle]; 1538 1539 // Labels Disabled 1540 if (String.Compare(labelStyleAttrib, "disabled", StringComparison.OrdinalIgnoreCase) == 0) 1541 style = PieLabelStyle.Disabled; 1542 else if (String.Compare(labelStyleAttrib, "outside", StringComparison.OrdinalIgnoreCase) == 0) 1543 style = PieLabelStyle.Outside; 1544 else 1545 style = PieLabelStyle.Inside; 1546 } 1547 1548 // Get label style attribute from point 1549 if(point.IsCustomPropertySet(CustomPropertyName.LabelStyle)) 1550 { 1551 string labelStyleAttrib = point[CustomPropertyName.LabelStyle]; 1552 1553 // Labels Disabled 1554 if (String.Compare(labelStyleAttrib, "disabled", StringComparison.OrdinalIgnoreCase) == 0) 1555 style = PieLabelStyle.Disabled; 1556 else if (String.Compare(labelStyleAttrib, "outside", StringComparison.OrdinalIgnoreCase) == 0) 1557 style = PieLabelStyle.Outside; 1558 else 1559 style = PieLabelStyle.Inside; 1560 } 1561 else if(point.IsCustomPropertySet(CustomPropertyName.PieLabelStyle)) 1562 { 1563 string labelStyleAttrib = point[CustomPropertyName.PieLabelStyle]; 1564 1565 // Labels Disabled 1566 if (String.Compare(labelStyleAttrib, "disabled", StringComparison.OrdinalIgnoreCase) == 0) 1567 style = PieLabelStyle.Disabled; 1568 else if (String.Compare(labelStyleAttrib, "outside", StringComparison.OrdinalIgnoreCase) == 0) 1569 style = PieLabelStyle.Outside; 1570 else 1571 style = PieLabelStyle.Inside; 1572 } 1573 1574 return style; 1575 } 1576 1577 /// <summary> 1578 /// Estimate Labels. 1579 /// </summary> 1580 /// <param name="graph">Chart Graphics object</param> 1581 /// <param name="middlePoint">Center of the pie chart</param> 1582 /// <param name="relativeSize">Size of the square, which will be used for drawing pie.</param> 1583 /// <param name="startAngle">Starting angle of a pie slice</param> 1584 /// <param name="sweepAngle">Sweep angle of a pie slice</param> 1585 /// <param name="point">Data point</param> 1586 /// <param name="exploded">The pie slice is exploded</param> 1587 /// <param name="area">Chart area</param> EstimateLabels( ChartGraphics graph, PointF middlePoint, SizeF relativeSize, float startAngle, float sweepAngle, DataPoint point, bool exploded, ChartArea area )1588 public bool EstimateLabels( ChartGraphics graph, PointF middlePoint, SizeF relativeSize, float startAngle, float sweepAngle, DataPoint point, bool exploded, ChartArea area ) 1589 { 1590 float labelsHorizontalLineSize = 1; // Horizontal line size for outside labels 1591 float labelsRadialLineSize = 1; // Radial line size for outside labels 1592 float shift; 1593 1594 string pointLabel = this.GetPointLabel(point); 1595 1596 Series series = point.series; 1597 1598 PieLabelStyle style = PieLabelStyle.Inside; 1599 1600 // Get label style attribute from series 1601 if(series.IsCustomPropertySet(CustomPropertyName.LabelStyle)) 1602 { 1603 string labelStyleAttrib = series[CustomPropertyName.LabelStyle]; 1604 1605 // Labels Disabled 1606 if( String.Compare(labelStyleAttrib,"disabled", StringComparison.OrdinalIgnoreCase) == 0 ) 1607 style = PieLabelStyle.Disabled; 1608 else if (String.Compare(labelStyleAttrib, "outside", StringComparison.OrdinalIgnoreCase) == 0) 1609 style = PieLabelStyle.Outside; 1610 else 1611 style = PieLabelStyle.Inside; 1612 } 1613 else if(series.IsCustomPropertySet(CustomPropertyName.PieLabelStyle)) 1614 { 1615 string labelStyleAttrib = series[CustomPropertyName.PieLabelStyle]; 1616 1617 // Labels Disabled 1618 if (String.Compare(labelStyleAttrib, "disabled", StringComparison.OrdinalIgnoreCase) == 0) 1619 style = PieLabelStyle.Disabled; 1620 else if (String.Compare(labelStyleAttrib, "outside", StringComparison.OrdinalIgnoreCase) == 0) 1621 style = PieLabelStyle.Outside; 1622 else 1623 style = PieLabelStyle.Inside; 1624 } 1625 1626 // Get label style attribute from point 1627 if(point.IsCustomPropertySet(CustomPropertyName.LabelStyle)) 1628 { 1629 string labelStyleAttrib = point[CustomPropertyName.LabelStyle]; 1630 1631 // Labels Disabled 1632 if (String.Compare(labelStyleAttrib, "disabled", StringComparison.OrdinalIgnoreCase) == 0) 1633 style = PieLabelStyle.Disabled; 1634 else if (String.Compare(labelStyleAttrib, "outside", StringComparison.OrdinalIgnoreCase) == 0) 1635 style = PieLabelStyle.Outside; 1636 else 1637 style = PieLabelStyle.Inside; 1638 } 1639 else if(point.IsCustomPropertySet(CustomPropertyName.PieLabelStyle)) 1640 { 1641 string labelStyleAttrib = point[CustomPropertyName.PieLabelStyle]; 1642 1643 // Labels Disabled 1644 if (String.Compare(labelStyleAttrib, "disabled", StringComparison.OrdinalIgnoreCase) == 0) 1645 style = PieLabelStyle.Disabled; 1646 else if (String.Compare(labelStyleAttrib, "outside", StringComparison.OrdinalIgnoreCase) == 0) 1647 style = PieLabelStyle.Outside; 1648 else 1649 style = PieLabelStyle.Inside; 1650 } 1651 1652 // Take labels radial line size attribute from series 1653 if(series.IsCustomPropertySet(CustomPropertyName.LabelsRadialLineSize)) 1654 { 1655 string labelsRadialLineSizeAttrib = series[CustomPropertyName.LabelsRadialLineSize]; 1656 labelsRadialLineSize = CommonElements.ParseFloat( labelsRadialLineSizeAttrib ); 1657 1658 // Validation 1659 if( labelsRadialLineSize < 0 || labelsRadialLineSize > 100 ) 1660 throw new InvalidOperationException(SR.ExceptionPieRadialLineSizeInvalid); 1661 } 1662 1663 // Take labels radial line size attribute from point 1664 if(point.IsCustomPropertySet(CustomPropertyName.LabelsRadialLineSize)) 1665 { 1666 string labelsRadialLineSizeAttrib = point[CustomPropertyName.LabelsRadialLineSize]; 1667 labelsRadialLineSize = CommonElements.ParseFloat( labelsRadialLineSizeAttrib ); 1668 1669 // Validation 1670 if( labelsRadialLineSize < 0 || labelsRadialLineSize > 100 ) 1671 throw new InvalidOperationException(SR.ExceptionPieRadialLineSizeInvalid); 1672 } 1673 1674 // Take labels horizontal line size attribute from series 1675 if(series.IsCustomPropertySet(CustomPropertyName.LabelsHorizontalLineSize)) 1676 { 1677 string labelsHorizontalLineSizeAttrib = series[CustomPropertyName.LabelsHorizontalLineSize]; 1678 labelsHorizontalLineSize = CommonElements.ParseFloat( labelsHorizontalLineSizeAttrib ); 1679 1680 // Validation 1681 if( labelsHorizontalLineSize < 0 || labelsHorizontalLineSize > 100 ) 1682 throw new InvalidOperationException(SR.ExceptionPieHorizontalLineSizeInvalid); 1683 } 1684 1685 // Take labels horizontal line size attribute from point 1686 if(point.IsCustomPropertySet(CustomPropertyName.LabelsHorizontalLineSize)) 1687 { 1688 string labelsHorizontalLineSizeAttrib = point[CustomPropertyName.LabelsHorizontalLineSize]; 1689 labelsHorizontalLineSize = CommonElements.ParseFloat( labelsHorizontalLineSizeAttrib ); 1690 1691 // Validation 1692 if( labelsHorizontalLineSize < 0 || labelsHorizontalLineSize > 100 ) 1693 throw new InvalidOperationException(SR.ExceptionPieHorizontalLineSizeInvalid); 1694 } 1695 1696 float expShift = 1; 1697 1698 1699 // ******************************************** 1700 // Labels are set outside pie 1701 // ******************************************** 1702 if( style == PieLabelStyle.Outside ) 1703 { 1704 // Coefficient which represent shift from pie border 1705 shift = 0.5F + labelsRadialLineSize * 0.1F; 1706 1707 // If exploded the shift is bigger 1708 if( exploded ) 1709 expShift = 1.2F; 1710 1711 float midAngle = startAngle + sweepAngle / 2; 1712 1713 1714 // Find second line position 1715 float y3 = (float)Math.Sin( (midAngle) * Math.PI / 180 ) * relativeSize.Height * shift * expShift + middlePoint.Y; 1716 float x3; 1717 1718 if( midAngle > 90 && midAngle < 270 ) 1719 { 1720 x3 = (float)Math.Cos( (midAngle) * Math.PI / 180 ) * relativeSize.Width * shift * expShift + middlePoint.X - relativeSize.Width / 10 * labelsHorizontalLineSize; 1721 } 1722 else 1723 { 1724 x3 = (float)Math.Cos( (midAngle) * Math.PI / 180 ) * relativeSize.Width * shift * expShift + middlePoint.X + relativeSize.Width / 10 * labelsHorizontalLineSize; 1725 } 1726 1727 // Get label text 1728 string text; 1729 if( pointLabel.Length == 0 && point.IsValueShownAsLabel ) 1730 { 1731 text = ValueConverter.FormatValue( 1732 series.Chart, 1733 point, 1734 point.Tag, 1735 point.YValues[0], 1736 point.LabelFormat, 1737 point.series.YValueType, 1738 ChartElementType.DataPoint); 1739 } 1740 else 1741 { 1742 text = pointLabel; 1743 } 1744 1745 SizeF size = graph.MeasureStringRel( text.Replace("\\n", "\n"), point.Font); 1746 1747 _labelsFit = true; 1748 1749 if(this._labelsOverlap) 1750 { 1751 if( midAngle > 90 && midAngle < 270 ) 1752 { 1753 float xOverlap = -relativeSize.Width * shift * expShift + middlePoint.X - relativeSize.Width / 10 * labelsHorizontalLineSize; 1754 if( (xOverlap - size.Width) < area.Position.X ) 1755 { 1756 _labelsFit = false; 1757 } 1758 } 1759 else 1760 { 1761 float xOverlap = relativeSize.Width * shift * expShift + middlePoint.X + relativeSize.Width / 10 * labelsHorizontalLineSize; 1762 if( (xOverlap + size.Width) > area.Position.Right ) 1763 { 1764 _labelsFit = false; 1765 } 1766 } 1767 } 1768 else 1769 { 1770 if( midAngle > 90 && midAngle < 270 ) 1771 { 1772 if( x3 - size.Width < area.PlotAreaPosition.ToRectangleF().Left ) 1773 _labelsFit = false; 1774 } 1775 else 1776 { 1777 if( x3 + size.Width > area.PlotAreaPosition.ToRectangleF().Right ) 1778 _labelsFit = false; 1779 } 1780 1781 if( midAngle > 180 && midAngle < 360 ) 1782 { 1783 if( y3 - size.Height/2 < area.PlotAreaPosition.ToRectangleF().Top ) 1784 _labelsFit = false; 1785 } 1786 else 1787 { 1788 if( y3 + size.Height/2 > area.PlotAreaPosition.ToRectangleF().Bottom ) 1789 _labelsFit = false; 1790 } 1791 } 1792 1793 } 1794 return true; 1795 } 1796 1797 /// <summary> 1798 /// This method adds map area information. 1799 /// </summary> 1800 /// <param name="common">The Common elements object</param> 1801 /// <param name="point">Data Point</param> 1802 /// <param name="startAngle">Start Angle</param> 1803 /// <param name="sweepAngle">Sweep Angle</param> 1804 /// <param name="rectangle">Rectangle of the pie</param> 1805 /// <param name="doughnut">True if doughnut</param> 1806 /// <param name="doughnutRadius">Doughnut radius in %</param> 1807 /// <param name="graph">Chart graphics object</param> 1808 /// <param name="pointIndex">Data point index</param> Map( CommonElements common, DataPoint point, float startAngle, float sweepAngle, RectangleF rectangle, bool doughnut, float doughnutRadius, ChartGraphics graph, int pointIndex )1809 private void Map( CommonElements common, DataPoint point, float startAngle, float sweepAngle, RectangleF rectangle, bool doughnut, float doughnutRadius, ChartGraphics graph, int pointIndex ) 1810 { 1811 // Create a graphics path 1812 using (GraphicsPath path = new GraphicsPath()) 1813 { 1814 1815 // Create the interior doughnut rectangle 1816 RectangleF doughnutRect = RectangleF.Empty; 1817 1818 doughnutRect.X = rectangle.X + rectangle.Width * (1 - (100 - doughnutRadius) / 100) / 2; 1819 doughnutRect.Y = rectangle.Y + rectangle.Height * (1 - (100 - doughnutRadius) / 100) / 2; 1820 doughnutRect.Width = rectangle.Width * (100 - doughnutRadius) / 100; 1821 doughnutRect.Height = rectangle.Height * (100 - doughnutRadius) / 100; 1822 1823 // Get absolute coordinates of the pie rectangle 1824 rectangle = graph.GetAbsoluteRectangle(rectangle); 1825 1826 // Add the pie to the graphics path 1827 path.AddPie(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height, startAngle, sweepAngle); 1828 // VSTS #250394 (Dev10:591140) Fix - Control should not return �useless� map areas 1829 if (sweepAngle <= 0) 1830 { 1831 return; 1832 } 1833 // If the chart type is doughnut 1834 if (doughnut) 1835 { 1836 1837 // Get absolute coordinates of the interior doughnut rectangle 1838 doughnutRect = graph.GetAbsoluteRectangle(doughnutRect); 1839 1840 // Add the interior doughnut region to the graphics path 1841 path.AddPie(doughnutRect.X, doughnutRect.Y, doughnutRect.Width, doughnutRect.Width, startAngle, sweepAngle); 1842 } 1843 1844 // Make a polygon from curves 1845 path.Flatten(new Matrix(), 1f); 1846 1847 // Create an area of points and convert them to 1848 // relative coordinates. 1849 PointF[] pointNew = new PointF[path.PointCount]; 1850 for (int i = 0; i < path.PointCount; i++) 1851 { 1852 pointNew[i] = graph.GetRelativePoint(path.PathPoints[i]); 1853 } 1854 1855 // Allocate array of floats 1856 float[] coord = new float[path.PointCount * 2]; 1857 1858 // Transfer path points 1859 for (int index = 0; index < path.PointCount; index++) 1860 { 1861 coord[2 * index] = pointNew[index].X; 1862 coord[2 * index + 1] = pointNew[index].Y; 1863 } 1864 1865 1866 1867 // Check if processing collected data point 1868 if (point.IsCustomPropertySet("_COLLECTED_DATA_POINT")) 1869 { 1870 // Add point to the map area 1871 common.HotRegionsList.AddHotRegion( 1872 graph, 1873 path, 1874 false, 1875 point.ReplaceKeywords(point.ToolTip), 1876 #if Microsoft_CONTROL 1877 string.Empty, 1878 string.Empty, 1879 string.Empty, 1880 #else // Microsoft_CONTROL 1881 point.ReplaceKeywords(point.Url), 1882 point.ReplaceKeywords(point.MapAreaAttributes), 1883 point.ReplaceKeywords(point.PostBackValue), 1884 #endif // Microsoft_CONTROL 1885 point, 1886 ChartElementType.DataPoint); 1887 1888 return; 1889 } 1890 1891 1892 1893 // Add points to the map area 1894 common.HotRegionsList.AddHotRegion( 1895 path, 1896 false, 1897 coord, 1898 point, 1899 point.series.Name, 1900 pointIndex 1901 ); 1902 } 1903 } 1904 1905 /// <summary> 1906 /// This method is introduced to check colors of palette. For 1907 /// pie chart the first pie slice and the second pie slice can 1908 /// not have same color because they are connected. 1909 /// </summary> 1910 /// <param name="points">Data points used for pie chart</param> CheckPaleteColors( DataPointCollection points )1911 private void CheckPaleteColors( DataPointCollection points ) 1912 { 1913 DataPoint firstPoint, lastPoint; 1914 1915 firstPoint = points[0]; 1916 lastPoint = points[ points.Count - 1 ]; 1917 1918 // Change color for last point if same as the first and if it is from pallete. 1919 if( firstPoint.tempColorIsSet && lastPoint.tempColorIsSet && firstPoint.Color == lastPoint.Color ) 1920 { 1921 lastPoint.Color = points[ points.Count / 2 ].Color; 1922 lastPoint.tempColorIsSet = true; 1923 } 1924 } 1925 1926 #endregion 1927 1928 #region 2DLabels 1929 1930 /// <summary> 1931 /// This method finds vertical position for left and 1932 /// right labels on that way that labels do not 1933 /// overlap each other. 1934 /// </summary> 1935 /// <param name="area">Chart area position</param> 1936 /// <returns>True if it is possible to find position that labels do not overlap each other.</returns> PrepareLabels( RectangleF area )1937 private bool PrepareLabels( RectangleF area ) 1938 { 1939 // Initialization of local variables 1940 float splitPoint = area.X + area.Width / 2f; 1941 int numberOfLeft = 0; 1942 int numberOfRight = 0; 1943 1944 // Find the number of left and right labels. 1945 foreach( RectangleF rect in this._labelsRectangles ) 1946 { 1947 if( rect.X < splitPoint ) 1948 { 1949 numberOfLeft++; 1950 } 1951 else 1952 { 1953 numberOfRight++; 1954 } 1955 } 1956 1957 // ********************************************** 1958 // Find the best position for LEFT labels 1959 // ********************************************** 1960 bool leftResult = true; 1961 if(numberOfLeft > 0) 1962 { 1963 double [] startPoints = new double[numberOfLeft]; 1964 double [] endPoints = new double[numberOfLeft]; 1965 int [] positionIndex = new Int32[numberOfLeft]; 1966 1967 // Fill double arrays with Top and Bottom coordinates 1968 // from the label rectangle. 1969 int splitIndex = 0; 1970 for( int index = 0; index < _labelsRectangles.Count; index++ ) 1971 { 1972 RectangleF rect = (RectangleF)_labelsRectangles[index]; 1973 if( rect.X < splitPoint ) 1974 { 1975 startPoints[ splitIndex ] = rect.Top; 1976 endPoints[ splitIndex ] = rect.Bottom; 1977 positionIndex[ splitIndex ] = index; 1978 splitIndex++; 1979 } 1980 } 1981 1982 // Sort label positions 1983 this.SortIntervals( startPoints, endPoints, positionIndex ); 1984 1985 // Find no overlapping positions if possible. 1986 if( this.ArrangeOverlappingIntervals( startPoints, endPoints, area.Top, area.Bottom ) ) 1987 { 1988 // Fill label rectangle top and bottom coordinates 1989 // from double arrays. 1990 splitIndex = 0; 1991 for( int index = 0; index < _labelsRectangles.Count; index++ ) 1992 { 1993 RectangleF rect = (RectangleF)_labelsRectangles[index]; 1994 if( rect.X < splitPoint ) 1995 { 1996 rect.Y = (float)startPoints[ splitIndex ]; 1997 rect.Height = (float)(endPoints[ splitIndex ] - rect.Top); 1998 _labelsRectangles[positionIndex[ splitIndex ]] = rect; 1999 splitIndex++; 2000 2001 } 2002 } 2003 } 2004 else 2005 { 2006 leftResult = false; 2007 } 2008 } 2009 2010 // ********************************************** 2011 // Find the best position for Right labels 2012 // ********************************************** 2013 bool rigthResult = true; 2014 if(numberOfRight > 0) 2015 { 2016 double [] startPoints = new double[numberOfRight]; 2017 double [] endPoints = new double[numberOfRight]; 2018 int [] positionIndex = new Int32[numberOfRight]; 2019 2020 // Fill double arrays with Top and Bottom coordinates 2021 // from the label rectangle. 2022 int splitIndex = 0; 2023 for( int index = 0; index < _labelsRectangles.Count; index++ ) 2024 { 2025 RectangleF rect = (RectangleF)_labelsRectangles[index]; 2026 if( rect.X >= splitPoint ) 2027 { 2028 startPoints[ splitIndex ] = rect.Top; 2029 endPoints[ splitIndex ] = rect.Bottom; 2030 positionIndex[ splitIndex ] = index; 2031 splitIndex++; 2032 } 2033 } 2034 2035 // Sort label positions 2036 this.SortIntervals( startPoints, endPoints, positionIndex ); 2037 2038 // Find no overlapping positions if possible. 2039 if( this.ArrangeOverlappingIntervals( startPoints, endPoints, area.Top, area.Bottom ) ) 2040 { 2041 // Fill label rectangle top and bottom coordinates 2042 // from double arrays. 2043 splitIndex = 0; 2044 for( int index = 0; index < _labelsRectangles.Count; index++ ) 2045 { 2046 RectangleF rect = (RectangleF)_labelsRectangles[index]; 2047 if( rect.X >= splitPoint ) 2048 { 2049 rect.Y = (float)startPoints[ splitIndex ]; 2050 rect.Height = (float)(endPoints[ splitIndex ] - rect.Top); 2051 _labelsRectangles[positionIndex[ splitIndex ]] = rect; 2052 splitIndex++; 2053 } 2054 } 2055 } 2056 else 2057 { 2058 rigthResult = false; 2059 } 2060 } 2061 2062 return ( (!leftResult || !rigthResult) ? true : false ); 2063 } 2064 2065 /// <summary> 2066 /// This algorithm sorts labels vertical intervals. 2067 /// </summary> 2068 /// <param name="startOfIntervals">Double array of label interval start points</param> 2069 /// <param name="endOfIntervals">Double array of label interval end points</param> 2070 /// <param name="positinIndex">Integer array of label interval indexes</param> SortIntervals( double [] startOfIntervals, double [] endOfIntervals, int [] positinIndex )2071 private void SortIntervals( double [] startOfIntervals, double [] endOfIntervals, int [] positinIndex ) 2072 { 2073 double firstCenter; 2074 double secondCenter; 2075 double midDouble; 2076 int midInt; 2077 2078 // Sorting loops 2079 for( int firstIndex = 0; firstIndex < startOfIntervals.Length; firstIndex++ ) 2080 { 2081 for( int secondIndex = firstIndex; secondIndex < startOfIntervals.Length; secondIndex++ ) 2082 { 2083 firstCenter = ( startOfIntervals[ firstIndex ] + endOfIntervals[ firstIndex ] ) / 2.0; 2084 secondCenter = ( startOfIntervals[ secondIndex ] + endOfIntervals[ secondIndex ] ) / 2.0; 2085 2086 if( firstCenter > secondCenter ) 2087 { 2088 // Sort start points 2089 midDouble = startOfIntervals[ firstIndex ]; 2090 startOfIntervals[ firstIndex ] = startOfIntervals[ secondIndex ]; 2091 startOfIntervals[ secondIndex ] = midDouble; 2092 2093 // Sort end points 2094 midDouble = endOfIntervals[ firstIndex ]; 2095 endOfIntervals[ firstIndex ] = endOfIntervals[ secondIndex ]; 2096 endOfIntervals[ secondIndex ] = midDouble; 2097 2098 // Sort indexes 2099 midInt = positinIndex[ firstIndex ]; 2100 positinIndex[ firstIndex ] = positinIndex[ secondIndex ]; 2101 positinIndex[ secondIndex ] = midInt; 2102 } 2103 } 2104 } 2105 } 2106 2107 /// <summary> 2108 /// This method inserts label rectangles 2109 /// into the collection. 2110 /// </summary> 2111 /// <param name="labelRect">Label Rectangle</param> InsertOverlapLabel( RectangleF labelRect )2112 private void InsertOverlapLabel( RectangleF labelRect ) 2113 { 2114 // Check if any pair of labels overlap 2115 if(!labelRect.IsEmpty) 2116 { 2117 foreach( RectangleF rect in _labelsRectangles ) 2118 { 2119 if( labelRect.IntersectsWith( rect ) ) 2120 { 2121 this._labelsOverlap = true; 2122 } 2123 } 2124 } 2125 2126 // Add rectangle to the collection 2127 _labelsRectangles.Add( labelRect ); 2128 } 2129 2130 /// <summary> 2131 /// This method will find the best position for labels. 2132 /// It is based on finding non overlap intervals for 2133 /// left or right side of the pie. This is 2134 /// recursive algorithm. 2135 /// </summary> 2136 /// <param name="startOfIntervals">The start positions of intervals.</param> 2137 /// <param name="endOfIntervals">The end positions of intervals.</param> 2138 /// <param name="startArea">Start position of chart area vertical range.</param> 2139 /// <param name="endArea">End position of chart area vertical range.</param> 2140 /// <returns>False if non overlapping positions for intervals can not be found.</returns> ArrangeOverlappingIntervals( double [] startOfIntervals, double [] endOfIntervals, double startArea, double endArea )2141 private bool ArrangeOverlappingIntervals( double [] startOfIntervals, double [] endOfIntervals, double startArea, double endArea ) 2142 { 2143 2144 // Invalidation 2145 if( startOfIntervals.Length != endOfIntervals.Length ) 2146 { 2147 throw new InvalidOperationException(SR.ExceptionPieIntervalsInvalid); 2148 } 2149 2150 ShiftOverlappingIntervals( startOfIntervals, endOfIntervals ); 2151 2152 // Find amount of empty space between intervals. 2153 double emptySpace = 0; 2154 for( int intervalIndex = 0; intervalIndex < startOfIntervals.Length - 1; intervalIndex++ ) 2155 { 2156 // Check overlapping 2157 if( startOfIntervals[ intervalIndex + 1 ] < endOfIntervals[ intervalIndex ] ) 2158 { 2159 //throw new InvalidOperationException( SR.ExceptionPieIntervalsOverlapping ); 2160 } 2161 2162 emptySpace += startOfIntervals[ intervalIndex + 1 ] - endOfIntervals[ intervalIndex ]; 2163 } 2164 2165 //Find how much intervals are out of area. Out of area could be positive value only. 2166 double outOfArea = ( endOfIntervals[ endOfIntervals.Length - 1 ] - endArea ) + ( startArea - startOfIntervals[ 0 ] ); 2167 if( outOfArea <= 0 ) 2168 { 2169 // This algorithm shifts all intervals for the same 2170 // amount. It is trying to put all intervals inside 2171 // chart area range. 2172 ShiftIntervals( startOfIntervals, endOfIntervals, startArea, endArea ); 2173 return true; 2174 } 2175 2176 // There is no enough space for all intervals. 2177 if( outOfArea > emptySpace ) 2178 { 2179 return false; 2180 } 2181 2182 // This method reduces empty space between intervals. 2183 ReduceEmptySpace( startOfIntervals, endOfIntervals, ( emptySpace - outOfArea ) / emptySpace ); 2184 2185 // This algorithm shifts all intervals for the same 2186 // amount. It is trying to put all intervals inside 2187 // chart area range. 2188 ShiftIntervals( startOfIntervals, endOfIntervals, startArea, endArea ); 2189 2190 return true; 2191 } 2192 2193 /// <summary> 2194 /// This method reduces empty space between intervals. 2195 /// </summary> 2196 /// <param name="startOfIntervals">The start positions of intervals.</param> 2197 /// <param name="endOfIntervals">The end positions of intervals.</param> 2198 /// <param name="reduction">Relative value which presents size reduction.</param> ReduceEmptySpace( double [] startOfIntervals, double [] endOfIntervals, double reduction )2199 private void ReduceEmptySpace( double [] startOfIntervals, double [] endOfIntervals, double reduction ) 2200 { 2201 for( int intervalIndex = 0; intervalIndex < startOfIntervals.Length - 1; intervalIndex++ ) 2202 { 2203 // Check overlapping 2204 if( startOfIntervals[ intervalIndex + 1 ] < endOfIntervals[ intervalIndex ] ) 2205 { 2206 //throw new InvalidOperationException( SR.ExceptionPieIntervalsOverlapping ); 2207 } 2208 2209 // Reduce space 2210 double shift = ( startOfIntervals[ intervalIndex + 1 ] - endOfIntervals[ intervalIndex ] ) - ( startOfIntervals[ intervalIndex + 1 ] - endOfIntervals[ intervalIndex ] ) * reduction; 2211 for( int reductionIndex = intervalIndex + 1; reductionIndex < startOfIntervals.Length; reductionIndex++ ) 2212 { 2213 startOfIntervals[ reductionIndex ] -= shift; 2214 endOfIntervals[ reductionIndex ] -= shift; 2215 } 2216 } 2217 } 2218 2219 /// <summary> 2220 /// This algorithm shifts all intervals for the same 2221 /// amount. It is trying to put all intervals inside 2222 /// chart area range. 2223 /// </summary> 2224 /// <param name="startOfIntervals">The start positions of intervals.</param> 2225 /// <param name="endOfIntervals">The end positions of intervals.</param> 2226 /// <param name="startArea">Start position of chart area vertical range.</param> 2227 /// <param name="endArea">End position of chart area vertical range.</param> ShiftIntervals( double [] startOfIntervals, double [] endOfIntervals, double startArea, double endArea )2228 private void ShiftIntervals( double [] startOfIntervals, double [] endOfIntervals, double startArea, double endArea ) 2229 { 2230 2231 double shift = 0; 2232 2233 if( startOfIntervals[ 0 ] < startArea ) 2234 { 2235 shift = startArea - startOfIntervals[ 0 ]; 2236 } 2237 else if( endOfIntervals[ endOfIntervals.Length - 1 ] > endArea ) 2238 { 2239 shift = endArea - endOfIntervals[ endOfIntervals.Length - 1 ]; 2240 } 2241 2242 for( int index = 0; index < startOfIntervals.Length; index++ ) 2243 { 2244 startOfIntervals[ index ] += shift; 2245 endOfIntervals[ index ] += shift; 2246 } 2247 } 2248 2249 /// <summary> 2250 /// This is used to find non overlapping position for intervals. 2251 /// </summary> 2252 /// <param name="startOfIntervals">The start positions of intervals.</param> 2253 /// <param name="endOfIntervals">The end positions of intervals.</param> 2254 /// <returns>Returns true if any label overlaps before method is used.</returns> ShiftOverlappingIntervals( double [] startOfIntervals, double [] endOfIntervals )2255 private void ShiftOverlappingIntervals( double [] startOfIntervals, double [] endOfIntervals ) 2256 { 2257 // Invalidation 2258 if( startOfIntervals.Length != endOfIntervals.Length ) 2259 { 2260 throw new InvalidOperationException(SR.ExceptionPieIntervalsInvalid); 2261 } 2262 2263 // Find first overlaping intervals 2264 for( int index = 0; index < startOfIntervals.Length - 1; index++ ) 2265 { 2266 // Intervals overlap 2267 if( endOfIntervals[ index ] > startOfIntervals[ index + 1 ] ) 2268 { 2269 double overlapRange = endOfIntervals[ index ] - startOfIntervals[ index + 1 ]; 2270 SpreadInterval( startOfIntervals, endOfIntervals, index, Math.Floor( overlapRange / 2.0 ) ); 2271 } 2272 } 2273 } 2274 2275 /// <summary> 2276 /// This method spread all intervals down or up from 2277 /// splitIndex. Intervals are spread only if there is no 2278 /// empty space which will compensate shifting of intervals. 2279 /// </summary> 2280 /// <param name="startOfIntervals">The start positions of intervals.</param> 2281 /// <param name="endOfIntervals">The end positions of intervals.</param> 2282 /// <param name="splitIndex">Position of the interval which ovelap.</param> 2283 /// <param name="overlapShift">The half of the overlapping range.</param> SpreadInterval( double [] startOfIntervals, double [] endOfIntervals, int splitIndex, double overlapShift )2284 private void SpreadInterval( double [] startOfIntervals, double [] endOfIntervals, int splitIndex, double overlapShift ) 2285 { 2286 // Move first overlapping intervals. 2287 endOfIntervals[ splitIndex ] -= overlapShift; 2288 startOfIntervals[ splitIndex ] -= overlapShift; 2289 2290 endOfIntervals[ splitIndex + 1 ] += overlapShift; 2291 startOfIntervals[ splitIndex + 1 ] += overlapShift; 2292 2293 // Move up other intervals if there is no enough empty space 2294 // to compensate overlapping intervals. 2295 if( splitIndex > 0 ) 2296 { 2297 for( int index = splitIndex - 1; index >= 0; index-- ) 2298 { 2299 if( endOfIntervals[ index ] > startOfIntervals[ index + 1 ] - overlapShift ) 2300 { 2301 endOfIntervals[ index ] -= overlapShift; 2302 startOfIntervals[ index ] -= overlapShift; 2303 } 2304 else 2305 { 2306 break; 2307 } 2308 } 2309 } 2310 2311 // Move down other intervals if there is no enough empty space 2312 // to compensate overlapping intervals. 2313 if( splitIndex + 2 < startOfIntervals.Length - 1 ) 2314 { 2315 for( int index = splitIndex + 2; index < startOfIntervals.Length; index++ ) 2316 { 2317 if( startOfIntervals[ index ] > endOfIntervals[ index - 1 ] + overlapShift ) 2318 { 2319 endOfIntervals[ index ] += overlapShift; 2320 startOfIntervals[ index ] += overlapShift; 2321 } 2322 else 2323 { 2324 break; 2325 } 2326 } 2327 } 2328 } 2329 2330 #endregion 2331 2332 #region Y values related methods 2333 2334 /// <summary> 2335 /// Helper function, which returns the Y value of the point. 2336 /// </summary> 2337 /// <param name="common">Chart common elements.</param> 2338 /// <param name="area">Chart area the series belongs to.</param> 2339 /// <param name="series">Sereis of the point.</param> 2340 /// <param name="point">Point object.</param> 2341 /// <param name="pointIndex">Index of the point.</param> 2342 /// <param name="yValueIndex">Index of the Y value to get.</param> 2343 /// <returns>Y value of the point.</returns> GetYValue( CommonElements common, ChartArea area, Series series, DataPoint point, int pointIndex, int yValueIndex)2344 virtual public double GetYValue( 2345 CommonElements common, 2346 ChartArea area, 2347 Series series, 2348 DataPoint point, 2349 int pointIndex, 2350 int yValueIndex) 2351 { 2352 return point.YValues[yValueIndex]; 2353 } 2354 2355 #endregion 2356 2357 #region 3D painting and selection methods 2358 2359 /// <summary> 2360 /// This method recalculates position of pie slices 2361 /// or checks if pie slice is selected. 2362 /// </summary> 2363 /// <param name="selection">If True selection mode is active, otherwise paint mode is active</param> 2364 /// <param name="graph">The Chart Graphics object</param> 2365 /// <param name="common">The Common elements object</param> 2366 /// <param name="area">Chart area for this chart</param> 2367 /// <param name="pieWidth">Pie width.</param> ProcessChartType3D( bool selection, ChartGraphics graph, CommonElements common, ChartArea area, float pieWidth )2368 private void ProcessChartType3D( 2369 bool selection, 2370 ChartGraphics graph, 2371 CommonElements common, 2372 ChartArea area, 2373 float pieWidth ) 2374 { 2375 string explodedAttrib = ""; // Exploded attribute 2376 bool exploded; // Exploded pie slice 2377 float midAngle; // Angle between Start Angle and End Angle 2378 2379 2380 // Data series collection 2381 SeriesCollection dataSeries = common.DataManager.Series; 2382 2383 // All data series from chart area which have Pie chart type 2384 List<string> typeSeries = area.GetSeriesFromChartType(Name); 2385 2386 if( typeSeries.Count == 0 ) 2387 { 2388 return; 2389 } 2390 2391 // Get first pie starting angle 2392 if (dataSeries[typeSeries[0]].IsCustomPropertySet(CustomPropertyName.PieStartAngle)) 2393 { 2394 int angle; 2395 bool parseSucceed = int.TryParse(dataSeries[typeSeries[0]][CustomPropertyName.PieStartAngle], NumberStyles.Any, CultureInfo.InvariantCulture, out angle); 2396 2397 if (parseSucceed) 2398 { 2399 if (angle > 180 && angle <= 360) 2400 { 2401 angle = -(360 - angle); 2402 } 2403 area.Area3DStyle.Rotation = angle; 2404 } 2405 2406 2407 if (!parseSucceed || area.Area3DStyle.Rotation > 180 || area.Area3DStyle.Rotation < -180) 2408 { 2409 throw (new InvalidOperationException(SR.ExceptionCustomAttributeAngleOutOfRange("PieStartAngle"))); 2410 } 2411 } 2412 2413 // Call Back Paint event 2414 if( !selection ) 2415 { 2416 common.Chart.CallOnPrePaint(new ChartPaintEventArgs(dataSeries[typeSeries[0]], graph, common, area.PlotAreaPosition)); 2417 } 2418 2419 // The data points loop. Find Sum of data points. 2420 double sum = 0; 2421 foreach( DataPoint point in dataSeries[typeSeries[0]].Points ) 2422 { 2423 if( !point.IsEmpty ) 2424 { 2425 sum += Math.Abs(point.YValues[0]); 2426 } 2427 } 2428 2429 // Is exploded if only one is exploded 2430 bool isExploded = false; 2431 foreach( DataPoint point in dataSeries[typeSeries[0]].Points ) 2432 { 2433 if(point.IsCustomPropertySet(CustomPropertyName.Exploded)) 2434 { 2435 explodedAttrib = point[CustomPropertyName.Exploded]; 2436 if( String.Compare(explodedAttrib,"true",StringComparison.OrdinalIgnoreCase) == 0 ) 2437 { 2438 isExploded = true; 2439 } 2440 } 2441 } 2442 2443 // Take radius attribute 2444 float doughnutRadius = 60f; 2445 if(dataSeries[typeSeries[0]].IsCustomPropertySet(CustomPropertyName.DoughnutRadius)) 2446 { 2447 doughnutRadius = CommonElements.ParseFloat(dataSeries[typeSeries[0]][CustomPropertyName.DoughnutRadius] ); 2448 2449 // Validation 2450 if( doughnutRadius < 0f || doughnutRadius > 99f ) 2451 throw (new ArgumentException(SR.ExceptionPieRadiusInvalid)); 2452 2453 } 2454 2455 // Take 3D Label Line Size attribute 2456 float labelLineSize = 100f; 2457 if(dataSeries[typeSeries[0]].IsCustomPropertySet(CustomPropertyName._3DLabelLineSize)) 2458 { 2459 labelLineSize = CommonElements.ParseFloat(dataSeries[typeSeries[0]][CustomPropertyName._3DLabelLineSize] ); 2460 2461 // Validation 2462 if( labelLineSize < 30f || labelLineSize > 200f ) 2463 throw (new ArgumentException(SR.ExceptionPie3DLabelLineSizeInvalid)); 2464 2465 } 2466 labelLineSize = labelLineSize * 0.1F / 100F; 2467 2468 //************************************************************ 2469 //** Data point loop 2470 //************************************************************ 2471 float [] startAngleList; 2472 float [] sweepAngleList; 2473 int [] pointIndexList; 2474 2475 // This method is introduced to check colors of palette. For 2476 // pie chart the first pie slice and the second pie slice can 2477 // not have same color because they are connected. 2478 CheckPaleteColors( dataSeries[typeSeries[0]].Points ); 2479 2480 bool sameBackFront; 2481 DataPoint [] points = PointOrder( dataSeries[typeSeries[0]], area, out startAngleList, out sweepAngleList, out pointIndexList, out sameBackFront ); 2482 2483 // There are no points or all points are empty. 2484 if( points == null ) 2485 { 2486 return; 2487 } 2488 2489 RectangleF plotingRectangle = new RectangleF( area.Position.ToRectangleF().X + 1, area.Position.ToRectangleF().Y + 1, area.Position.ToRectangleF().Width-2, area.Position.ToRectangleF().Height-2 ); 2490 2491 // Check if any data point has outside label 2492 bool outside = false; 2493 foreach( DataPoint point in points ) 2494 { 2495 if( GetLabelStyle( point ) == PieLabelStyle.Outside ) 2496 { 2497 outside = true; 2498 } 2499 } 2500 2501 // If outside labels resize Pie size 2502 if( outside ) 2503 { 2504 InitPieSize( graph, area, ref plotingRectangle, ref pieWidth, points, startAngleList, sweepAngleList, dataSeries[typeSeries[0]], labelLineSize ); 2505 } 2506 2507 // Initialize Matrix 3D 2508 area.matrix3D.Initialize( 2509 plotingRectangle, 2510 pieWidth, 2511 area.Area3DStyle.Inclination, 2512 0F, 2513 0, 2514 false); 2515 2516 //*********************************************************** 2517 //** Initialize Lighting 2518 //*********************************************************** 2519 area.matrix3D.InitLight( 2520 area.Area3DStyle.LightStyle 2521 ); 2522 2523 // Turns are introduce because of special case � Big pie slice, which 2524 // is bigger, then 180 degree and it is back and 2525 // front point in same time. If special case exists drawing has to be split 2526 // into 4 parts: 1. Drawing back pie slices, 2. Drawing the first part of 2527 // big slice and other points, 3. Drawing second part of big slice and 2528 // 4. Drawing top of the pie slices. 2529 for( int turn = 0; turn < 5; turn++ ) 2530 { 2531 int pointIndx = 0; 2532 foreach( DataPoint point in points ) 2533 { 2534 // Reset point anchor location 2535 point.positionRel = PointF.Empty; 2536 2537 // Do not process empty points 2538 if( point.IsEmpty ) 2539 { 2540 pointIndx++; 2541 continue; 2542 } 2543 2544 float sweepAngle = sweepAngleList[pointIndx]; 2545 float startAngle = startAngleList[pointIndx]; 2546 2547 // Rectangle size 2548 RectangleF rectangle; 2549 if( area.InnerPlotPosition.Auto ) 2550 rectangle = new RectangleF( plotingRectangle.X, plotingRectangle.Y, plotingRectangle.Width, plotingRectangle.Height ); 2551 else 2552 rectangle = new RectangleF( area.PlotAreaPosition.ToRectangleF().X, area.PlotAreaPosition.ToRectangleF().Y, area.PlotAreaPosition.ToRectangleF().Width, area.PlotAreaPosition.ToRectangleF().Height ); 2553 2554 // Find smallest edge 2555 SizeF absoluteSize = graph.GetAbsoluteSize( new SizeF( rectangle.Width, rectangle.Height ) ); 2556 float absRadius = ( absoluteSize.Width < absoluteSize.Height ) ? absoluteSize.Width : absoluteSize.Height; 2557 2558 // Size of the square, which will be used for drawing pie. 2559 SizeF relativeSize = graph.GetRelativeSize( new SizeF( absRadius, absRadius ) ); 2560 2561 // Center of the pie 2562 PointF middlePoint = new PointF( rectangle.X + rectangle.Width / 2, rectangle.Y + rectangle.Height / 2 ); 2563 2564 // Rectangle which will always create circle, never ellipse. 2565 rectangle = new RectangleF( middlePoint.X - relativeSize.Width / 2, middlePoint.Y - relativeSize.Height / 2, relativeSize.Width, relativeSize.Height ); 2566 2567 // Check Exploded attribute for data point 2568 exploded = false; 2569 if(point.IsCustomPropertySet(CustomPropertyName.Exploded)) 2570 { 2571 explodedAttrib = point[CustomPropertyName.Exploded]; 2572 if( String.Compare(explodedAttrib,"true",StringComparison.OrdinalIgnoreCase) == 0 ) 2573 exploded = true; 2574 else 2575 exploded = false; 2576 } 2577 2578 // Size correction because of exploded or labels 2579 float sizeCorrection = 1.0F; 2580 if( isExploded ) 2581 { 2582 sizeCorrection = 0.82F; 2583 2584 rectangle.X += rectangle.Width * ( 1 - sizeCorrection ) / 2; 2585 rectangle.Y += rectangle.Height * ( 1 - sizeCorrection ) / 2; 2586 rectangle.Width = rectangle.Width * sizeCorrection; 2587 rectangle.Height = rectangle.Height * sizeCorrection; 2588 } 2589 2590 2591 // Find Direction to move exploded pie slice 2592 if( exploded ) 2593 { 2594 _sliceExploded = true; 2595 midAngle = ( 2 * startAngle + sweepAngle ) / 2; 2596 double xComponent = Math.Cos( midAngle * Math.PI / 180 ) * rectangle.Width / 10; 2597 double yComponent = Math.Sin( midAngle * Math.PI / 180 ) * rectangle.Height / 10; 2598 2599 rectangle.Offset( (float)xComponent, (float)yComponent ); 2600 } 2601 2602 // Adjust inner plot position 2603 if(area.InnerPlotPosition.Auto) 2604 { 2605 RectangleF rect = rectangle; 2606 rect.X = (rect.X - area.Position.X) / area.Position.Width * 100f; 2607 rect.Y = (rect.Y - area.Position.Y) / area.Position.Height * 100f; 2608 rect.Width = rect.Width / area.Position.Width * 100f; 2609 rect.Height = rect.Height / area.Position.Height * 100f; 2610 area.InnerPlotPosition.SetPositionNoAuto(rect.X, rect.Y, rect.Width, rect.Height); 2611 } 2612 2613 // Start Svg Selection mode 2614 graph.StartHotRegion( point ); 2615 2616 // Drawing or selection of pie clice 2617 Draw3DPie( turn, graph, point, area, rectangle, startAngle, sweepAngle, doughnutRadius, pieWidth, sameBackFront, exploded, pointIndexList[pointIndx] ); 2618 2619 // End Svg Selection mode 2620 graph.EndHotRegion( ); 2621 2622 if( turn == 1 ) 2623 { 2624 // Outside labels 2625 if( GetLabelStyle( point ) == PieLabelStyle.Outside ) 2626 { 2627 FillPieLabelOutside( graph, area, rectangle, pieWidth, point, startAngle, sweepAngle, pointIndx, doughnutRadius, exploded ); 2628 } 2629 } 2630 if( turn == 2 ) 2631 { 2632 2633 // Outside labels 2634 if( GetLabelStyle( point ) == PieLabelStyle.Outside && pointIndx == 0 ) 2635 { 2636 labelColumnLeft.Sort(); 2637 labelColumnLeft.AdjustPositions(); 2638 labelColumnRight.Sort(); 2639 labelColumnRight.AdjustPositions(); 2640 } 2641 2642 } 2643 2644 // Increae point index 2645 pointIndx++; 2646 } 2647 } 2648 2649 // Call Paint event 2650 if( !selection ) 2651 { 2652 common.Chart.CallOnPostPaint(new ChartPaintEventArgs(dataSeries[typeSeries[0]], graph, common, area.PlotAreaPosition)); 2653 } 2654 } 2655 2656 /// <summary> 2657 /// This method draws a part of a pie slice. Which part is drown 2658 /// depend on turn. There is special case if there is a big pie 2659 /// slice (>180) when one pie slice has to be split on parts 2660 /// and between that other small pie slices has to be drawn. 2661 /// </summary> 2662 /// <param name="turn">Turn for drawing.</param> 2663 /// <param name="graph">Chart Graphics</param> 2664 /// <param name="point">Data Point to draw</param> 2665 /// <param name="area">Chart area</param> 2666 /// <param name="rectangle">Rectangle used for drawing pie clice.</param> 2667 /// <param name="startAngle">Start angle for pie slice</param> 2668 /// <param name="sweepAngle">End angle for pie slice</param> 2669 /// <param name="doughnutRadius">Inner Radius if chart is doughnut</param> 2670 /// <param name="pieWidth">Width of the pie</param> 2671 /// <param name="sameBackFront">Pie slice is >180 and same pie slice is back and front slice</param> 2672 /// <param name="exploded">Pie slice is exploded</param> 2673 /// <param name="pointIndex">Point Index</param> Draw3DPie( int turn, ChartGraphics graph, DataPoint point, ChartArea area, RectangleF rectangle, float startAngle, float sweepAngle, float doughnutRadius, float pieWidth, bool sameBackFront, bool exploded, int pointIndex )2674 private void Draw3DPie( 2675 int turn, 2676 ChartGraphics graph, 2677 DataPoint point, 2678 ChartArea area, 2679 RectangleF rectangle, 2680 float startAngle, 2681 float sweepAngle, 2682 float doughnutRadius, 2683 float pieWidth, 2684 bool sameBackFront, 2685 bool exploded, 2686 int pointIndex 2687 ) 2688 { 2689 SolidBrush brush = new SolidBrush(point.Color); 2690 2691 // For lightStyle style Non, Border color always exist. 2692 Color penColor = Color.Empty; 2693 Color penCurveColor = Color.Empty; 2694 2695 if( point.BorderColor == Color.Empty && area.Area3DStyle.LightStyle == LightStyle.None ) 2696 { 2697 penColor = ChartGraphics.GetGradientColor( point.Color, Color.Black, 0.5 ); 2698 } 2699 else if( point.BorderColor == Color.Empty ) 2700 { 2701 penColor = point.Color; 2702 } 2703 else 2704 { 2705 penColor = point.BorderColor; 2706 } 2707 2708 if( point.BorderColor != Color.Empty || area.Area3DStyle.LightStyle == LightStyle.None ) 2709 { 2710 penCurveColor = penColor; 2711 } 2712 2713 Pen pen = new Pen(penColor, point.BorderWidth); 2714 pen.DashStyle = graph.GetPenStyle( point.BorderDashStyle ); 2715 2716 // Pen for back side slice. 2717 Pen backSlicePen; 2718 if( point.BorderColor == Color.Empty ) 2719 { 2720 backSlicePen = new Pen(point.Color); 2721 } 2722 else 2723 { 2724 backSlicePen = pen; 2725 } 2726 2727 Pen penCurve = new Pen(penCurveColor, point.BorderWidth); 2728 penCurve.DashStyle = graph.GetPenStyle( point.BorderDashStyle ); 2729 2730 // Set Border Width; 2731 PointF [] points = GetPiePoints( graph, area, pieWidth, rectangle, startAngle, sweepAngle, true, doughnutRadius, exploded ); 2732 2733 if( points == null ) 2734 return; 2735 2736 // Remember data point anchor location 2737 point.positionRel.X = points[(int)PiePoints.TopLabelLine].X; 2738 point.positionRel.Y = points[(int)PiePoints.TopLabelLine].Y; 2739 point.positionRel = graph.GetRelativePoint(point.positionRel); 2740 2741 2742 float midAngle = startAngle + sweepAngle / 2F; 2743 float endAngle = startAngle + sweepAngle; 2744 2745 if( turn == 0 ) 2746 { 2747 // Draw back pie slice (do not fill). 2748 // Used for transparency. 2749 if( !this.Doughnut ) 2750 { 2751 graph.FillPieSlice( 2752 area, 2753 point, 2754 brush, 2755 backSlicePen, 2756 points[(int)PiePoints.BottomRectTopLeftPoint], 2757 points[(int)PiePoints.BottomStart], 2758 points[(int)PiePoints.BottomRectBottomRightPoint], 2759 points[(int)PiePoints.BottomEnd], 2760 points[(int)PiePoints.BottomCenter], 2761 startAngle, 2762 sweepAngle, 2763 false, 2764 pointIndex 2765 ); 2766 } 2767 else 2768 { 2769 graph.FillDoughnutSlice( 2770 area, 2771 point, 2772 brush, 2773 backSlicePen, 2774 points[(int)PiePoints.BottomRectTopLeftPoint], 2775 points[(int)PiePoints.BottomStart], 2776 points[(int)PiePoints.BottomRectBottomRightPoint], 2777 points[(int)PiePoints.BottomEnd], 2778 points[(int)PiePoints.DoughnutBottomEnd], 2779 points[(int)PiePoints.DoughnutBottomStart], 2780 startAngle, 2781 sweepAngle, 2782 false, 2783 doughnutRadius, 2784 pointIndex 2785 ); 2786 } 2787 2788 } 2789 else if( turn == 1 ) 2790 { 2791 // Case when there is big pie slice ( > 180 ) and big slice is 2792 // back and front point in same time. 2793 if( sameBackFront ) 2794 { 2795 2796 2797 // Draw the first part of the curve of the big slice and 2798 // all curves from other slices. Big pie slice could be on the 2799 // right or the left side. 2800 if( midAngle > -90 && midAngle < 90 || midAngle > 270 && midAngle < 450 ) 2801 { 2802 // Draw Inner Arc for Doughnut 2803 if( Doughnut ) 2804 { 2805 DrawDoughnutCurves( graph, area, point, startAngle, sweepAngle, points, brush, penCurve, false, true, pointIndex ); 2806 } 2807 2808 DrawPieCurves( graph, area, point, startAngle, sweepAngle, points, brush, penCurve, true, true, pointIndex ); 2809 } 2810 else 2811 { 2812 // Draw Inner Arc for Doughnut 2813 if( Doughnut ) 2814 { 2815 DrawDoughnutCurves( graph, area, point, startAngle, sweepAngle, points, brush, penCurve, true, true, pointIndex ); 2816 } 2817 2818 DrawPieCurves( graph, area, point, startAngle, sweepAngle, points, brush, penCurve, false, true, pointIndex ); 2819 } 2820 2821 // Draw sides of pie slices 2822 graph.FillPieSides( area, area.Area3DStyle.Inclination, startAngle, sweepAngle, points, brush, pen, Doughnut ); 2823 2824 2825 } 2826 else 2827 { 2828 // Draw Inner Arc for Doughnut 2829 if( Doughnut ) 2830 { 2831 DrawDoughnutCurves( graph, area, point, startAngle, sweepAngle, points, brush, penCurve, false, false, pointIndex ); 2832 } 2833 2834 // This is regular case. There is no big pie slice 2835 // which is back nad front point in same time. 2836 graph.FillPieSides( area, area.Area3DStyle.Inclination, startAngle, sweepAngle, points, brush, pen, Doughnut ); 2837 2838 DrawPieCurves( graph, area, point, startAngle, sweepAngle, points, brush, penCurve, false, false, pointIndex ); 2839 } 2840 2841 } 2842 else if( turn == 2 ) 2843 { 2844 // This second turned is used only for big pie slice (>180). If big pie 2845 // slice exist it has to be split if it is necessary. If the big pie slice 2846 // cover other pie slice from both sides, the big pie slice have to curves. 2847 // The first curve from big pie slice is drawn first, after that all other 2848 // pie slices and at the end second curve from big pie slice. 2849 if( sameBackFront && sweepAngle > 180 ) 2850 { 2851 // Condition when two draw Doughnut arcs after sides for big pie slice ( > 180 ). 2852 bool BackFrontDoughnut = ( startAngle > -180 && startAngle < 0 || startAngle > 180 && startAngle < 360 ) && ( endAngle > -180 && endAngle < 0 || endAngle > 180 && endAngle < 360 ); 2853 2854 if( area.Area3DStyle.Inclination > 0 ) 2855 BackFrontDoughnut = !BackFrontDoughnut; 2856 2857 if( midAngle > -90 && midAngle < 90 || midAngle > 270 && midAngle < 450 ) 2858 { 2859 // Draw Inner Arc for Doughnut 2860 if( Doughnut ) 2861 { 2862 // Draw second part of doughnut curve only for very big slices > 300 ( Visibility issue for Big point depth ). 2863 if( BackFrontDoughnut && sweepAngle > 300 ) 2864 { 2865 DrawDoughnutCurves( graph, area, point, startAngle, sweepAngle, points, brush, penCurve, true, true, pointIndex ); 2866 } 2867 } 2868 2869 DrawPieCurves( graph, area, point, startAngle, sweepAngle, points, brush, penCurve, false, true, pointIndex ); 2870 } 2871 else 2872 { 2873 // Draw Inner Arc for Doughnut 2874 if( Doughnut ) 2875 { 2876 // Draw second part of doughnut curve only for very big slices > 300( Visibility issue for Big point depth ). 2877 if( BackFrontDoughnut && sweepAngle > 300 ) 2878 { 2879 DrawDoughnutCurves( graph, area, point, startAngle, sweepAngle, points, brush, penCurve, false, true, pointIndex ); 2880 } 2881 } 2882 DrawPieCurves( graph, area, point, startAngle, sweepAngle, points, brush, penCurve, true, true, pointIndex ); 2883 } 2884 } 2885 } 2886 else if( turn == 3 ) 2887 { 2888 if( !this.Doughnut ) 2889 { 2890 // Fill pie slice 2891 graph.FillPieSlice( 2892 area, 2893 point, 2894 brush, 2895 pen, 2896 points[(int)PiePoints.TopRectTopLeftPoint], 2897 points[(int)PiePoints.TopStart], 2898 points[(int)PiePoints.TopRectBottomRightPoint], 2899 points[(int)PiePoints.TopEnd], 2900 points[(int)PiePoints.TopCenter], 2901 startAngle, 2902 sweepAngle, 2903 true, 2904 pointIndex 2905 ); 2906 2907 // Draw Border 2908 graph.FillPieSlice( 2909 area, 2910 point, 2911 brush, 2912 pen, 2913 points[(int)PiePoints.TopRectTopLeftPoint], 2914 points[(int)PiePoints.TopStart], 2915 points[(int)PiePoints.TopRectBottomRightPoint], 2916 points[(int)PiePoints.TopEnd], 2917 points[(int)PiePoints.TopCenter], 2918 startAngle, 2919 sweepAngle, 2920 false, 2921 pointIndex 2922 ); 2923 } 2924 else 2925 { 2926 // Fill 2927 graph.FillDoughnutSlice( 2928 area, 2929 point, 2930 brush, 2931 pen, 2932 points[(int)PiePoints.TopRectTopLeftPoint], 2933 points[(int)PiePoints.TopStart], 2934 points[(int)PiePoints.TopRectBottomRightPoint], 2935 points[(int)PiePoints.TopEnd], 2936 points[(int)PiePoints.DoughnutTopEnd], 2937 points[(int)PiePoints.DoughnutTopStart], 2938 startAngle, 2939 sweepAngle, 2940 true, 2941 doughnutRadius, 2942 pointIndex 2943 ); 2944 2945 // Draw Border 2946 graph.FillDoughnutSlice( 2947 area, 2948 point, 2949 brush, 2950 pen, 2951 points[(int)PiePoints.TopRectTopLeftPoint], 2952 points[(int)PiePoints.TopStart], 2953 points[(int)PiePoints.TopRectBottomRightPoint], 2954 points[(int)PiePoints.TopEnd], 2955 points[(int)PiePoints.DoughnutTopEnd], 2956 points[(int)PiePoints.DoughnutTopStart], 2957 startAngle, 2958 sweepAngle, 2959 false, 2960 doughnutRadius, 2961 pointIndex 2962 ); 2963 } 2964 2965 // Draw 3D Outside labels 2966 if( GetLabelStyle( point ) == PieLabelStyle.Outside ) 2967 { 2968 // Check if special color properties are set 2969 Color pieLineColor = pen.Color; 2970 if(point.IsCustomPropertySet(CustomPropertyName.PieLineColor) || (point.series != null && point.series.IsCustomPropertySet(CustomPropertyName.PieLineColor)) ) 2971 { 2972 ColorConverter colorConverter = new ColorConverter(); 2973 bool failed = false; 2974 2975 try 2976 { 2977 if(point.IsCustomPropertySet(CustomPropertyName.PieLineColor)) 2978 { 2979 pieLineColor = (Color)colorConverter.ConvertFromString(point[CustomPropertyName.PieLineColor]); 2980 } 2981 else if(point.series != null && point.series.IsCustomPropertySet(CustomPropertyName.PieLineColor)) 2982 { 2983 pieLineColor = (Color)colorConverter.ConvertFromString(point.series[CustomPropertyName.PieLineColor]); 2984 } 2985 } 2986 catch (ArgumentException) 2987 { 2988 failed = true; 2989 } 2990 catch (NotSupportedException) 2991 { 2992 failed = true; 2993 } 2994 2995 if(failed) 2996 { 2997 if(point.IsCustomPropertySet(CustomPropertyName.PieLineColor)) 2998 { 2999 pieLineColor = (Color)colorConverter.ConvertFromInvariantString(point[CustomPropertyName.PieLineColor]); 3000 } 3001 else if(point.series != null && point.series.IsCustomPropertySet(CustomPropertyName.PieLineColor)) 3002 { 3003 pieLineColor = (Color)colorConverter.ConvertFromInvariantString(point.series[CustomPropertyName.PieLineColor]); 3004 } 3005 } 3006 } 3007 3008 // Draw labels 3009 using (Pen labelPen = new Pen(pieLineColor, pen.Width)) 3010 { 3011 Draw3DOutsideLabels(graph, area, labelPen, points, point, midAngle, pointIndex); 3012 } 3013 } 3014 3015 } 3016 else 3017 { 3018 3019 // Draw 3D Inside labels 3020 if( GetLabelStyle( point ) == PieLabelStyle.Inside ) 3021 { 3022 Draw3DInsideLabels( graph, points, point, pointIndex ); 3023 } 3024 } 3025 3026 //Clean up resources 3027 if (brush!=null) 3028 brush.Dispose(); 3029 if (pen != null) 3030 pen.Dispose(); 3031 if (penCurve != null) 3032 penCurve.Dispose(); 3033 } 3034 3035 /// <summary> 3036 /// This method transforms in 3D space important points for 3037 /// doughnut or pie slice. 3038 /// </summary> 3039 /// <param name="graph">Chart Graphics</param> 3040 /// <param name="area">Chart Area</param> 3041 /// <param name="pieWidth">The width of a pie.</param> 3042 /// <param name="rectangle">Rectangle used for drawing pie clice.</param> 3043 /// <param name="startAngle">Start angle for pie slice.</param> 3044 /// <param name="sweepAngle">End angle for pie slice.</param> 3045 /// <param name="relativeCoordinates">true if relative coordinates has to be returned.</param> 3046 /// <param name="doughnutRadius">Doughnut Radius</param> 3047 /// <param name="exploded">Exploded pie slice</param> 3048 /// <returns>Returns 3D Transformed pie or doughnut points.</returns> GetPiePoints( ChartGraphics graph, ChartArea area, float pieWidth, RectangleF rectangle, float startAngle, float sweepAngle, bool relativeCoordinates, float doughnutRadius, bool exploded )3049 private PointF [] GetPiePoints( 3050 ChartGraphics graph, 3051 ChartArea area, 3052 float pieWidth, 3053 RectangleF rectangle, 3054 float startAngle, 3055 float sweepAngle, 3056 bool relativeCoordinates, 3057 float doughnutRadius, 3058 bool exploded 3059 ) 3060 { 3061 doughnutRadius = 1 - doughnutRadius / 100F; 3062 3063 Point3D [] points; 3064 PointF [] result; 3065 3066 // Doughnut chart has 12 more points 3067 if( Doughnut ) 3068 { 3069 points = new Point3D[29]; 3070 result = new PointF[29]; 3071 } 3072 else 3073 { 3074 points = new Point3D[17]; 3075 result = new PointF[17]; 3076 } 3077 3078 // Angle 180 Top point on the arc 3079 points[(int)PiePoints.Top180] = new Point3D( 3080 rectangle.X + (float)Math.Cos( 180 * Math.PI / 180 ) * rectangle.Width / 2F + rectangle.Width / 2F, 3081 rectangle.Y + (float)Math.Sin( 180 * Math.PI / 180 ) * rectangle.Height / 2F + rectangle.Height / 2F, 3082 pieWidth ); 3083 3084 // Angle 180 Bottom point on the arc 3085 points[(int)PiePoints.Bottom180] = new Point3D( 3086 rectangle.X + (float)Math.Cos( 180 * Math.PI / 180 ) * rectangle.Width / 2F + rectangle.Width / 2F, 3087 rectangle.Y + (float)Math.Sin( 180 * Math.PI / 180 ) * rectangle.Height / 2F + rectangle.Height / 2F, 3088 0 ); 3089 3090 // Angle 0 Top point on the arc 3091 points[(int)PiePoints.Top0] = new Point3D( 3092 rectangle.X + (float)Math.Cos( 0 * Math.PI / 180 ) * rectangle.Width / 2F + rectangle.Width / 2F, 3093 rectangle.Y + (float)Math.Sin( 0 * Math.PI / 180 ) * rectangle.Height / 2F + rectangle.Height / 2F, 3094 pieWidth ); 3095 3096 // Angle 0 Bottom point on the arc 3097 points[(int)PiePoints.Bottom0] = new Point3D( 3098 rectangle.X + (float)Math.Cos( 0 * Math.PI / 180 ) * rectangle.Width / 2F + rectangle.Width / 2F, 3099 rectangle.Y + (float)Math.Sin( 0 * Math.PI / 180 ) * rectangle.Height / 2F + rectangle.Height / 2F, 3100 0 ); 3101 3102 // Top Start Angle point on the arc 3103 points[(int)PiePoints.TopStart] = new Point3D( 3104 rectangle.X + (float)Math.Cos( startAngle * Math.PI / 180 ) * rectangle.Width / 2F + rectangle.Width / 2F, 3105 rectangle.Y + (float)Math.Sin( startAngle * Math.PI / 180 ) * rectangle.Height / 2F + rectangle.Height / 2F, 3106 pieWidth ); 3107 3108 // Top End Angle point on the arc 3109 points[(int)PiePoints.TopEnd] = new Point3D( 3110 rectangle.X + (float)Math.Cos( ( startAngle + sweepAngle ) * Math.PI / 180 ) * rectangle.Width / 2F + rectangle.Width / 2F, 3111 rectangle.Y + (float)Math.Sin( ( startAngle + sweepAngle ) * Math.PI / 180 ) * rectangle.Height / 2F + rectangle.Height / 2F, 3112 pieWidth ); 3113 3114 // Bottom Start Angle point on the arc 3115 points[(int)PiePoints.BottomStart] = new Point3D( 3116 rectangle.X + (float)Math.Cos( startAngle * Math.PI / 180 ) * rectangle.Width / 2F + rectangle.Width / 2F, 3117 rectangle.Y + (float)Math.Sin( startAngle * Math.PI / 180 ) * rectangle.Height / 2F + rectangle.Height / 2F, 3118 0 ); 3119 3120 // Bottom End Angle point on the arc 3121 points[(int)PiePoints.BottomEnd] = new Point3D( 3122 rectangle.X + (float)Math.Cos( ( startAngle + sweepAngle ) * Math.PI / 180 ) * rectangle.Width / 2F + rectangle.Width / 2F, 3123 rectangle.Y + (float)Math.Sin( ( startAngle + sweepAngle ) * Math.PI / 180 ) * rectangle.Height / 2F + rectangle.Height / 2F, 3124 0 ); 3125 3126 // Center Top 3127 points[(int)PiePoints.TopCenter] = new Point3D( 3128 rectangle.X + rectangle.Width / 2F, 3129 rectangle.Y + rectangle.Height / 2F, 3130 pieWidth ); 3131 3132 // Center Bottom 3133 points[(int)PiePoints.BottomCenter] = new Point3D( 3134 rectangle.X + rectangle.Width / 2F, 3135 rectangle.Y + rectangle.Height / 2F, 3136 0 ); 3137 3138 // Top Label Line 3139 points[(int)PiePoints.TopLabelLine] = new Point3D( 3140 rectangle.X + (float)Math.Cos( ( startAngle + sweepAngle / 2 ) * Math.PI / 180 ) * rectangle.Width / 2F + rectangle.Width / 2F, 3141 rectangle.Y + (float)Math.Sin( ( startAngle + sweepAngle / 2 ) * Math.PI / 180 ) * rectangle.Height / 2F + rectangle.Height / 2F, 3142 pieWidth ); 3143 3144 // If Pie slice is exploded Label line out size is changed 3145 float sizeOut; 3146 if( exploded ) 3147 { 3148 sizeOut = 1.1F; 3149 } 3150 else 3151 { 3152 sizeOut = 1.3F; 3153 } 3154 3155 // Top Label Line Out 3156 points[(int)PiePoints.TopLabelLineout] = new Point3D( 3157 rectangle.X + (float)Math.Cos( ( startAngle + sweepAngle / 2 ) * Math.PI / 180 ) * rectangle.Width * sizeOut / 2F + rectangle.Width / 2F, 3158 rectangle.Y + (float)Math.Sin( ( startAngle + sweepAngle / 2 ) * Math.PI / 180 ) * rectangle.Height * sizeOut / 2F + rectangle.Height / 2F, 3159 pieWidth ); 3160 3161 // Top Label Center 3162 if( this.Doughnut ) 3163 { 3164 points[(int)PiePoints.TopLabelCenter] = new Point3D( 3165 rectangle.X + (float)Math.Cos( ( startAngle + sweepAngle / 2 ) * Math.PI / 180 ) * rectangle.Width * ( 1 + doughnutRadius ) / 4F + rectangle.Width / 2F, 3166 rectangle.Y + (float)Math.Sin( ( startAngle + sweepAngle / 2 ) * Math.PI / 180 ) * rectangle.Height * ( 1 + doughnutRadius ) / 4F + rectangle.Height / 2F, 3167 pieWidth ); 3168 } 3169 else 3170 { 3171 points[(int)PiePoints.TopLabelCenter] = new Point3D( 3172 rectangle.X + (float)Math.Cos( ( startAngle + sweepAngle / 2 ) * Math.PI / 180 ) * rectangle.Width * 0.5F / 2F + rectangle.Width / 2F, 3173 rectangle.Y + (float)Math.Sin( ( startAngle + sweepAngle / 2 ) * Math.PI / 180 ) * rectangle.Height * 0.5F / 2F + rectangle.Height / 2F, 3174 pieWidth ); 3175 } 3176 3177 3178 // Top Rectangle Top Left Point 3179 points[(int)PiePoints.TopRectTopLeftPoint] = new Point3D(rectangle.X,rectangle.Y,pieWidth); 3180 3181 // Top Rectangle Right Bottom Point 3182 points[(int)PiePoints.TopRectBottomRightPoint] = new Point3D(rectangle.Right,rectangle.Bottom,pieWidth); 3183 3184 // Bottom Rectangle Top Left Point 3185 points[(int)PiePoints.BottomRectTopLeftPoint] = new Point3D(rectangle.X,rectangle.Y,0); 3186 3187 // Bottom Rectangle Right Bottom Point 3188 points[(int)PiePoints.BottomRectBottomRightPoint] = new Point3D(rectangle.Right,rectangle.Bottom,0); 3189 3190 if( Doughnut ) 3191 { 3192 // Angle 180 Top point on the Doughnut arc 3193 points[(int)PiePoints.DoughnutTop180] = new Point3D( 3194 rectangle.X + (float)Math.Cos( 180 * Math.PI / 180 ) * rectangle.Width * doughnutRadius / 2F + rectangle.Width / 2F, 3195 rectangle.Y + (float)Math.Sin( 180 * Math.PI / 180 ) * rectangle.Height * doughnutRadius / 2F + rectangle.Height / 2F, 3196 pieWidth ); 3197 3198 // Angle 180 Bottom point on the Doughnut arc 3199 points[(int)PiePoints.DoughnutBottom180] = new Point3D( 3200 rectangle.X + (float)Math.Cos( 180 * Math.PI / 180 ) * rectangle.Width * doughnutRadius / 2F + rectangle.Width / 2F, 3201 rectangle.Y + (float)Math.Sin( 180 * Math.PI / 180 ) * rectangle.Height * doughnutRadius / 2F + rectangle.Height / 2F, 3202 0 ); 3203 3204 // Angle 0 Top point on the Doughnut arc 3205 points[(int)PiePoints.DoughnutTop0] = new Point3D( 3206 rectangle.X + (float)Math.Cos( 0 * Math.PI / 180 ) * rectangle.Width * doughnutRadius / 2F + rectangle.Width / 2F, 3207 rectangle.Y + (float)Math.Sin( 0 * Math.PI / 180 ) * rectangle.Height * doughnutRadius / 2F + rectangle.Height / 2F, 3208 pieWidth ); 3209 3210 // Angle 0 Bottom point on the Doughnut arc 3211 points[(int)PiePoints.DoughnutBottom0] = new Point3D( 3212 rectangle.X + (float)Math.Cos( 0 * Math.PI / 180 ) * rectangle.Width * doughnutRadius / 2F + rectangle.Width / 2F, 3213 rectangle.Y + (float)Math.Sin( 0 * Math.PI / 180 ) * rectangle.Height * doughnutRadius / 2F + rectangle.Height / 2F, 3214 0 ); 3215 3216 // Top Start Angle point on the Doughnut arc 3217 points[(int)PiePoints.DoughnutTopStart] = new Point3D( 3218 rectangle.X + (float)Math.Cos( startAngle * Math.PI / 180 ) * rectangle.Width * doughnutRadius / 2F + rectangle.Width / 2F, 3219 rectangle.Y + (float)Math.Sin( startAngle * Math.PI / 180 ) * rectangle.Height * doughnutRadius / 2F + rectangle.Height / 2F, 3220 pieWidth ); 3221 3222 // Top End Angle point on the Doughnut arc 3223 points[(int)PiePoints.DoughnutTopEnd] = new Point3D( 3224 rectangle.X + (float)Math.Cos( ( startAngle + sweepAngle ) * Math.PI / 180 ) * rectangle.Width * doughnutRadius / 2F + rectangle.Width / 2F, 3225 rectangle.Y + (float)Math.Sin( ( startAngle + sweepAngle ) * Math.PI / 180 ) * rectangle.Height * doughnutRadius / 2F + rectangle.Height / 2F, 3226 pieWidth ); 3227 3228 // Bottom Start Angle point on the Doughnut arc 3229 points[(int)PiePoints.DoughnutBottomStart] = new Point3D( 3230 rectangle.X + (float)Math.Cos( startAngle * Math.PI / 180 ) * rectangle.Width * doughnutRadius / 2F + rectangle.Width / 2F, 3231 rectangle.Y + (float)Math.Sin( startAngle * Math.PI / 180 ) * rectangle.Height * doughnutRadius / 2F + rectangle.Height / 2F, 3232 0 ); 3233 3234 // Bottom End Angle point on the Doughnut arc 3235 points[(int)PiePoints.DoughnutBottomEnd] = new Point3D( 3236 rectangle.X + (float)Math.Cos( ( startAngle + sweepAngle ) * Math.PI / 180 ) * rectangle.Width * doughnutRadius / 2F + rectangle.Width / 2F, 3237 rectangle.Y + (float)Math.Sin( ( startAngle + sweepAngle ) * Math.PI / 180 ) * rectangle.Height * doughnutRadius / 2F + rectangle.Height / 2F, 3238 0 ); 3239 3240 rectangle.Inflate( -rectangle.Width * (1 - doughnutRadius) / 2F, -rectangle.Height * (1 - doughnutRadius) / 2F); 3241 3242 // Doughnut Top Rectangle Top Left Point 3243 points[(int)PiePoints.DoughnutTopRectTopLeftPoint] = new Point3D(rectangle.X,rectangle.Y,pieWidth); 3244 3245 // Doughnut Top Rectangle Right Bottom Point 3246 points[(int)PiePoints.DoughnutTopRectBottomRightPoint] = new Point3D(rectangle.Right,rectangle.Bottom,pieWidth); 3247 3248 // Doughnut Bottom Rectangle Top Left Point 3249 points[(int)PiePoints.DoughnutBottomRectTopLeftPoint] = new Point3D(rectangle.X,rectangle.Y,0); 3250 3251 // Doughnut Bottom Rectangle Right Bottom Point 3252 points[(int)PiePoints.DoughnutBottomRectBottomRightPoint] = new Point3D(rectangle.Right,rectangle.Bottom,0); 3253 3254 3255 } 3256 3257 // Make 3D transformations 3258 area.matrix3D.TransformPoints(points); 3259 3260 int pointIndx = 0; 3261 foreach( Point3D point in points ) 3262 { 3263 result[pointIndx] = point.PointF; 3264 3265 // Convert Relative coordinates to absolute. 3266 if( relativeCoordinates ) 3267 { 3268 result[pointIndx] = graph.GetAbsolutePoint(result[pointIndx]); 3269 } 3270 pointIndx++; 3271 } 3272 3273 return result; 3274 3275 } 3276 3277 3278 3279 3280 #endregion 3281 3282 #region 3D Drawing surfaces 3283 3284 /// <summary> 3285 /// This method is used for drawing curve around pie slices. This is 3286 /// the most complex part of 3D Pie slice. There is special case if 3287 /// pie slice is bigger then 180 degree. 3288 /// </summary> 3289 /// <param name="graph">Chart Grahics.</param> 3290 /// <param name="area">Chart Area.</param> 3291 /// <param name="dataPoint">Data Point used for pie slice.</param> 3292 /// <param name="startAngle">Start angle of a pie slice.</param> 3293 /// <param name="sweepAngle">Sweep angle of a pie slice.</param> 3294 /// <param name="points">Important 3d points of a pie slice.</param> 3295 /// <param name="brushWithoutLight">Brush without lithing efects.</param> 3296 /// <param name="pen">Pen used for border.</param> 3297 /// <param name="rightPosition">Position of the curve of big pie slice. Big pie slice coud have to visible curves - left and right</param> 3298 /// <param name="sameBackFront">This is big pie slice which is in same time back and front slice.</param> 3299 /// <param name="pointIndex">Data Point Index</param> DrawPieCurves( ChartGraphics graph, ChartArea area, DataPoint dataPoint, float startAngle, float sweepAngle, PointF [] points, SolidBrush brushWithoutLight, Pen pen, bool rightPosition, bool sameBackFront, int pointIndex )3300 private void DrawPieCurves( 3301 ChartGraphics graph, 3302 ChartArea area, 3303 DataPoint dataPoint, 3304 float startAngle, 3305 float sweepAngle, 3306 PointF [] points, 3307 SolidBrush brushWithoutLight, 3308 Pen pen, 3309 bool rightPosition, 3310 bool sameBackFront, 3311 int pointIndex 3312 ) 3313 { 3314 // Create a graphics path 3315 using (GraphicsPath path = new GraphicsPath()) 3316 { 3317 Brush brush; 3318 3319 if (area.Area3DStyle.LightStyle == LightStyle.None) 3320 { 3321 brush = brushWithoutLight; 3322 } 3323 else 3324 { 3325 brush = graph.GetGradientBrush(graph.GetAbsoluteRectangle(area.Position.ToRectangleF()), Color.FromArgb(brushWithoutLight.Color.A, 0, 0, 0), brushWithoutLight.Color, GradientStyle.VerticalCenter); 3326 } 3327 3328 float endAngle = startAngle + sweepAngle; 3329 3330 // Very big pie slice ( > 180 degree ) 3331 if (sweepAngle > 180) 3332 { 3333 if (DrawPieCurvesBigSlice(graph, area, dataPoint, startAngle, sweepAngle, points, brush, pen, rightPosition, sameBackFront, pointIndex)) 3334 return; 3335 } 3336 3337 // Pie slice pass throw 180 degree. Curve has to be spited. 3338 if (startAngle < 180 && endAngle > 180) 3339 { 3340 if (area.Area3DStyle.Inclination < 0) 3341 { 3342 graph.FillPieCurve( 3343 area, 3344 dataPoint, 3345 brush, 3346 pen, 3347 points[(int)PiePoints.TopRectTopLeftPoint], 3348 points[(int)PiePoints.TopRectBottomRightPoint], 3349 points[(int)PiePoints.BottomRectTopLeftPoint], 3350 points[(int)PiePoints.BottomRectBottomRightPoint], 3351 points[(int)PiePoints.TopStart], 3352 points[(int)PiePoints.Top180], 3353 points[(int)PiePoints.BottomStart], 3354 points[(int)PiePoints.Bottom180], 3355 startAngle, 3356 180 - startAngle, 3357 pointIndex 3358 ); 3359 3360 } 3361 else 3362 { 3363 graph.FillPieCurve( 3364 area, 3365 dataPoint, 3366 brush, 3367 pen, 3368 points[(int)PiePoints.TopRectTopLeftPoint], 3369 points[(int)PiePoints.TopRectBottomRightPoint], 3370 points[(int)PiePoints.BottomRectTopLeftPoint], 3371 points[(int)PiePoints.BottomRectBottomRightPoint], 3372 points[(int)PiePoints.Top180], 3373 points[(int)PiePoints.TopEnd], 3374 points[(int)PiePoints.Bottom180], 3375 points[(int)PiePoints.BottomEnd], 3376 180, 3377 startAngle + sweepAngle - 180, 3378 pointIndex 3379 ); 3380 3381 } 3382 } 3383 3384 // Pie slice pass throw 0 degree. Curve has to be spited. 3385 else if (startAngle < 0 && endAngle > 0) 3386 { 3387 if (area.Area3DStyle.Inclination > 0) 3388 { 3389 graph.FillPieCurve( 3390 area, 3391 dataPoint, 3392 brush, 3393 pen, 3394 points[(int)PiePoints.TopRectTopLeftPoint], 3395 points[(int)PiePoints.TopRectBottomRightPoint], 3396 points[(int)PiePoints.BottomRectTopLeftPoint], 3397 points[(int)PiePoints.BottomRectBottomRightPoint], 3398 points[(int)PiePoints.TopStart], 3399 points[(int)PiePoints.Top0], 3400 points[(int)PiePoints.BottomStart], 3401 points[(int)PiePoints.Bottom0], 3402 startAngle, 3403 -startAngle, 3404 pointIndex 3405 ); 3406 3407 } 3408 else 3409 { 3410 graph.FillPieCurve( 3411 area, 3412 dataPoint, 3413 brush, 3414 pen, 3415 points[(int)PiePoints.TopRectTopLeftPoint], 3416 points[(int)PiePoints.TopRectBottomRightPoint], 3417 points[(int)PiePoints.BottomRectTopLeftPoint], 3418 points[(int)PiePoints.BottomRectBottomRightPoint], 3419 points[(int)PiePoints.Top0], 3420 points[(int)PiePoints.TopEnd], 3421 points[(int)PiePoints.Bottom0], 3422 points[(int)PiePoints.BottomEnd], 3423 0, 3424 sweepAngle + startAngle, 3425 pointIndex 3426 ); 3427 3428 } 3429 } 3430 // Pie slice pass throw 360 degree. Curve has to be spited. 3431 else if (startAngle < 360 && endAngle > 360) 3432 { 3433 if (area.Area3DStyle.Inclination > 0) 3434 { 3435 graph.FillPieCurve( 3436 area, 3437 dataPoint, 3438 brush, 3439 pen, 3440 points[(int)PiePoints.TopRectTopLeftPoint], 3441 points[(int)PiePoints.TopRectBottomRightPoint], 3442 points[(int)PiePoints.BottomRectTopLeftPoint], 3443 points[(int)PiePoints.BottomRectBottomRightPoint], 3444 points[(int)PiePoints.TopStart], 3445 points[(int)PiePoints.Top0], 3446 points[(int)PiePoints.BottomStart], 3447 points[(int)PiePoints.Bottom0], 3448 startAngle, 3449 360 - startAngle, 3450 pointIndex 3451 ); 3452 3453 } 3454 else 3455 { 3456 graph.FillPieCurve( 3457 area, 3458 dataPoint, 3459 brush, 3460 pen, 3461 points[(int)PiePoints.TopRectTopLeftPoint], 3462 points[(int)PiePoints.TopRectBottomRightPoint], 3463 points[(int)PiePoints.BottomRectTopLeftPoint], 3464 points[(int)PiePoints.BottomRectBottomRightPoint], 3465 points[(int)PiePoints.Top0], 3466 points[(int)PiePoints.TopEnd], 3467 points[(int)PiePoints.Bottom0], 3468 points[(int)PiePoints.BottomEnd], 3469 0, 3470 endAngle - 360, 3471 pointIndex 3472 ); 3473 } 3474 } 3475 else 3476 { 3477 // *************************************************** 3478 // REGULAR CASE: The curve is not split. 3479 // *************************************************** 3480 if (startAngle < 180 && startAngle >= 0 && area.Area3DStyle.Inclination < 0 3481 || startAngle < 540 && startAngle >= 360 && area.Area3DStyle.Inclination < 0 3482 || startAngle >= 180 && startAngle < 360 && area.Area3DStyle.Inclination > 0 3483 || startAngle >= -180 && startAngle < 0 && area.Area3DStyle.Inclination > 0 3484 ) 3485 { 3486 graph.FillPieCurve( 3487 area, 3488 dataPoint, 3489 brush, 3490 pen, 3491 points[(int)PiePoints.TopRectTopLeftPoint], 3492 points[(int)PiePoints.TopRectBottomRightPoint], 3493 points[(int)PiePoints.BottomRectTopLeftPoint], 3494 points[(int)PiePoints.BottomRectBottomRightPoint], 3495 points[(int)PiePoints.TopStart], 3496 points[(int)PiePoints.TopEnd], 3497 points[(int)PiePoints.BottomStart], 3498 points[(int)PiePoints.BottomEnd], 3499 startAngle, 3500 sweepAngle, 3501 pointIndex 3502 ); 3503 } 3504 } 3505 } 3506 } 3507 3508 /// <summary> 3509 /// This method is used for special case when big pie slice has to be drawn. 3510 /// </summary> 3511 /// <param name="graph">Chart Grahics.</param> 3512 /// <param name="area">Chart Area.</param> 3513 /// <param name="dataPoint">Data Point used for pie slice.</param> 3514 /// <param name="startAngle">Start angle of a pie slice.</param> 3515 /// <param name="sweepAngle">Sweep angle of a pie slice.</param> 3516 /// <param name="points">Important 3d points of a pie slice.</param> 3517 /// <param name="brush">Brush without lithing efects.</param> 3518 /// <param name="pen">Pen used for border.</param> 3519 /// <param name="rightPosition">Position of the curve of big pie slice. Big pie slice coud have to visible curves - left and right</param> 3520 /// <param name="sameBackFront">This is big pie slice which is in same time back and front slice.</param> 3521 /// <param name="pointIndex">Data Point Index</param> 3522 /// <returns>True if slice is special case and it is drawn as a special case.</returns> DrawPieCurvesBigSlice( ChartGraphics graph, ChartArea area, DataPoint dataPoint, float startAngle, float sweepAngle, PointF [] points, Brush brush, Pen pen, bool rightPosition, bool sameBackFront, int pointIndex )3523 private bool DrawPieCurvesBigSlice 3524 ( 3525 ChartGraphics graph, 3526 ChartArea area, 3527 DataPoint dataPoint, 3528 float startAngle, 3529 float sweepAngle, 3530 PointF [] points, 3531 Brush brush, 3532 Pen pen, 3533 bool rightPosition, 3534 bool sameBackFront, 3535 int pointIndex 3536 ) 3537 { 3538 float endAngle = startAngle + sweepAngle; 3539 3540 // Two different cases connected with X angle. 3541 // ***************************************************** 3542 // X angle is positive 3543 // ***************************************************** 3544 if( area.Area3DStyle.Inclination > 0 ) 3545 { 3546 // Show curve from 0 to 180. 3547 if( startAngle < 180 && endAngle > 360 ) 3548 { 3549 graph.FillPieCurve( 3550 area, 3551 dataPoint, 3552 brush, 3553 pen, 3554 points[(int)PiePoints.TopRectTopLeftPoint], 3555 points[(int)PiePoints.TopRectBottomRightPoint], 3556 points[(int)PiePoints.BottomRectTopLeftPoint], 3557 points[(int)PiePoints.BottomRectBottomRightPoint], 3558 points[(int)PiePoints.Top0], 3559 points[(int)PiePoints.Top180], 3560 points[(int)PiePoints.Bottom0], 3561 points[(int)PiePoints.Bottom180], 3562 0, 3563 -180, 3564 pointIndex 3565 ); 3566 } 3567 else if( startAngle < 0 && endAngle > 180 ) 3568 { 3569 // There is big data point which is back and 3570 // front point in same time. 3571 if( sameBackFront ) 3572 { 3573 // The big pie slice has to be split. This part makes 3574 // decision which part of this big slice will be 3575 // drawn first. 3576 if( rightPosition ) 3577 { 3578 graph.FillPieCurve( 3579 area, 3580 dataPoint, 3581 brush, 3582 pen, 3583 points[(int)PiePoints.TopRectTopLeftPoint], 3584 points[(int)PiePoints.TopRectBottomRightPoint], 3585 points[(int)PiePoints.BottomRectTopLeftPoint], 3586 points[(int)PiePoints.BottomRectBottomRightPoint], 3587 points[(int)PiePoints.Top180], 3588 points[(int)PiePoints.TopEnd], 3589 points[(int)PiePoints.Bottom180], 3590 points[(int)PiePoints.BottomEnd], 3591 180, 3592 endAngle - 180, 3593 pointIndex 3594 ); 3595 } 3596 else 3597 { 3598 graph.FillPieCurve( 3599 area, 3600 dataPoint, 3601 brush, 3602 pen, 3603 points[(int)PiePoints.TopRectTopLeftPoint], 3604 points[(int)PiePoints.TopRectBottomRightPoint], 3605 points[(int)PiePoints.BottomRectTopLeftPoint], 3606 points[(int)PiePoints.BottomRectBottomRightPoint], 3607 points[(int)PiePoints.TopStart], 3608 points[(int)PiePoints.Top0], 3609 points[(int)PiePoints.BottomStart], 3610 points[(int)PiePoints.Bottom0], 3611 startAngle, 3612 -startAngle, 3613 pointIndex 3614 ); 3615 } 3616 } 3617 else 3618 { 3619 // There is big pie slice (>180), but that pie slice 3620 // is not back and front point in same time. 3621 graph.FillPieCurve( 3622 area, 3623 dataPoint, 3624 brush, 3625 pen, 3626 points[(int)PiePoints.TopRectTopLeftPoint], 3627 points[(int)PiePoints.TopRectBottomRightPoint], 3628 points[(int)PiePoints.BottomRectTopLeftPoint], 3629 points[(int)PiePoints.BottomRectBottomRightPoint], 3630 points[(int)PiePoints.TopStart], 3631 points[(int)PiePoints.Top0], 3632 points[(int)PiePoints.BottomStart], 3633 points[(int)PiePoints.Bottom0], 3634 startAngle, 3635 -startAngle, 3636 pointIndex 3637 ); 3638 3639 graph.FillPieCurve( 3640 area, 3641 dataPoint, 3642 brush, 3643 pen, 3644 points[(int)PiePoints.TopRectTopLeftPoint], 3645 points[(int)PiePoints.TopRectBottomRightPoint], 3646 points[(int)PiePoints.BottomRectTopLeftPoint], 3647 points[(int)PiePoints.BottomRectBottomRightPoint], 3648 points[(int)PiePoints.Top180], 3649 points[(int)PiePoints.TopEnd], 3650 points[(int)PiePoints.Bottom180], 3651 points[(int)PiePoints.BottomEnd], 3652 180, 3653 endAngle - 180, 3654 pointIndex 3655 ); 3656 } 3657 3658 } 3659 else 3660 { 3661 // Big pie slice behaves as normal pie slice. Continue 3662 // Non special case alghoritham 3663 return false; 3664 } 3665 } 3666 // ********************************************* 3667 // X angle negative 3668 // ********************************************* 3669 else 3670 { 3671 // Show curve from 0 to 180. 3672 if( startAngle < 0 && endAngle > 180 ) 3673 { 3674 graph.FillPieCurve( 3675 area, 3676 dataPoint, 3677 brush, 3678 pen, 3679 points[(int)PiePoints.TopRectTopLeftPoint], 3680 points[(int)PiePoints.TopRectBottomRightPoint], 3681 points[(int)PiePoints.BottomRectTopLeftPoint], 3682 points[(int)PiePoints.BottomRectBottomRightPoint], 3683 points[(int)PiePoints.Top0], 3684 points[(int)PiePoints.Top180], 3685 points[(int)PiePoints.Bottom0], 3686 points[(int)PiePoints.Bottom180], 3687 0, 3688 180, 3689 pointIndex 3690 ); 3691 } 3692 else if( startAngle < 180 && endAngle > 360 ) 3693 { 3694 // There is big data point which is back and 3695 // front point in same time. 3696 if( sameBackFront ) 3697 { 3698 // The big pie slice has to be split. This part makes 3699 // decision which part of this big slice will be 3700 // drawn first. 3701 if( rightPosition ) 3702 { 3703 graph.FillPieCurve( 3704 area, 3705 dataPoint, 3706 brush, 3707 pen, 3708 points[(int)PiePoints.TopRectTopLeftPoint], 3709 points[(int)PiePoints.TopRectBottomRightPoint], 3710 points[(int)PiePoints.BottomRectTopLeftPoint], 3711 points[(int)PiePoints.BottomRectBottomRightPoint], 3712 points[(int)PiePoints.TopStart], 3713 points[(int)PiePoints.Top180], 3714 points[(int)PiePoints.BottomStart], 3715 points[(int)PiePoints.Bottom180], 3716 startAngle, 3717 180 - startAngle, 3718 pointIndex 3719 ); 3720 } 3721 else 3722 { 3723 graph.FillPieCurve( 3724 area, 3725 dataPoint, 3726 brush, 3727 pen, 3728 points[(int)PiePoints.TopRectTopLeftPoint], 3729 points[(int)PiePoints.TopRectBottomRightPoint], 3730 points[(int)PiePoints.BottomRectTopLeftPoint], 3731 points[(int)PiePoints.BottomRectBottomRightPoint], 3732 points[(int)PiePoints.Top0], 3733 points[(int)PiePoints.TopEnd], 3734 points[(int)PiePoints.Bottom0], 3735 points[(int)PiePoints.BottomEnd], 3736 0, 3737 endAngle - 360, 3738 pointIndex 3739 ); 3740 } 3741 } 3742 else 3743 { 3744 // There is big pie slice (>180), but that pie slice 3745 // is not back and front point in same time. 3746 graph.FillPieCurve( 3747 area, 3748 dataPoint, 3749 brush, 3750 pen, 3751 points[(int)PiePoints.TopRectTopLeftPoint], 3752 points[(int)PiePoints.TopRectBottomRightPoint], 3753 points[(int)PiePoints.BottomRectTopLeftPoint], 3754 points[(int)PiePoints.BottomRectBottomRightPoint], 3755 points[(int)PiePoints.Top0], 3756 points[(int)PiePoints.TopEnd], 3757 points[(int)PiePoints.Bottom0], 3758 points[(int)PiePoints.BottomEnd], 3759 0, 3760 endAngle - 360, 3761 pointIndex 3762 ); 3763 3764 graph.FillPieCurve( 3765 area, 3766 dataPoint, 3767 brush, 3768 pen, 3769 points[(int)PiePoints.TopRectTopLeftPoint], 3770 points[(int)PiePoints.TopRectBottomRightPoint], 3771 points[(int)PiePoints.BottomRectTopLeftPoint], 3772 points[(int)PiePoints.BottomRectBottomRightPoint], 3773 points[(int)PiePoints.TopStart], 3774 points[(int)PiePoints.Top180], 3775 points[(int)PiePoints.BottomStart], 3776 points[(int)PiePoints.Bottom180], 3777 startAngle, 3778 180 - startAngle, 3779 pointIndex 3780 ); 3781 } 3782 3783 } 3784 else 3785 { 3786 // Big pie slice behaves as normal pie slice. Continue 3787 // Non special case alghoritham 3788 return false; 3789 } 3790 } 3791 3792 return true; 3793 } 3794 3795 /// <summary> 3796 /// This method is used for drawing curve around doughnut slices - inner curve. 3797 /// This is the most complex part of 3D Doughnut slice. There is special case if 3798 /// pie slice is bigger then 180 degree. 3799 /// </summary> 3800 /// <param name="graph">Chart Grahics.</param> 3801 /// <param name="area">Chart Area.</param> 3802 /// <param name="dataPoint">Data Point used for pie slice.</param> 3803 /// <param name="startAngle">Start angle of a pie slice.</param> 3804 /// <param name="sweepAngle">Sweep angle of a pie slice.</param> 3805 /// <param name="points">Important 3d points of a pie slice.</param> 3806 /// <param name="brushWithoutLight">Brush without lithing efects.</param> 3807 /// <param name="pen">Pen used for border.</param> 3808 /// <param name="rightPosition">Position of the curve of big pie slice. Big pie slice coud have to visible curves - left and right</param> 3809 /// <param name="sameBackFront">This is big pie slice which is in same time back and front slice.</param> 3810 /// <param name="pointIndex">Data Point Index</param> DrawDoughnutCurves( ChartGraphics graph, ChartArea area, DataPoint dataPoint, float startAngle, float sweepAngle, PointF [] points, SolidBrush brushWithoutLight, Pen pen, bool rightPosition, bool sameBackFront, int pointIndex )3811 private void DrawDoughnutCurves( 3812 ChartGraphics graph, 3813 ChartArea area, 3814 DataPoint dataPoint, 3815 float startAngle, 3816 float sweepAngle, 3817 PointF [] points, 3818 SolidBrush brushWithoutLight, 3819 Pen pen, 3820 bool rightPosition, 3821 bool sameBackFront, 3822 int pointIndex 3823 ) 3824 { 3825 // Create a graphics path 3826 using (GraphicsPath path = new GraphicsPath()) 3827 { 3828 3829 Brush brush; 3830 3831 if (area.Area3DStyle.LightStyle == LightStyle.None) 3832 { 3833 brush = brushWithoutLight; 3834 } 3835 else 3836 { 3837 brush = graph.GetGradientBrush(graph.GetAbsoluteRectangle(area.Position.ToRectangleF()), Color.FromArgb(brushWithoutLight.Color.A, 0, 0, 0), brushWithoutLight.Color, GradientStyle.VerticalCenter); 3838 } 3839 3840 float endAngle = startAngle + sweepAngle; 3841 3842 // Very big pie slice ( > 180 degree ) 3843 if (sweepAngle > 180) 3844 { 3845 if (DrawDoughnutCurvesBigSlice(graph, area, dataPoint, startAngle, sweepAngle, points, brush, pen, rightPosition, sameBackFront, pointIndex)) 3846 return; 3847 } 3848 3849 // Pie slice pass throw 180 degree. Curve has to be spited. 3850 if (startAngle < 180 && endAngle > 180) 3851 { 3852 if (area.Area3DStyle.Inclination > 0) 3853 { 3854 graph.FillPieCurve( 3855 area, 3856 dataPoint, 3857 brush, 3858 pen, 3859 points[(int)PiePoints.DoughnutTopRectTopLeftPoint], 3860 points[(int)PiePoints.DoughnutTopRectBottomRightPoint], 3861 points[(int)PiePoints.DoughnutBottomRectTopLeftPoint], 3862 points[(int)PiePoints.DoughnutBottomRectBottomRightPoint], 3863 points[(int)PiePoints.DoughnutTopStart], 3864 points[(int)PiePoints.DoughnutTop180], 3865 points[(int)PiePoints.DoughnutBottomStart], 3866 points[(int)PiePoints.DoughnutBottom180], 3867 startAngle, 3868 180 - startAngle, 3869 pointIndex 3870 ); 3871 3872 } 3873 else 3874 { 3875 graph.FillPieCurve( 3876 area, 3877 dataPoint, 3878 brush, 3879 pen, 3880 points[(int)PiePoints.DoughnutTopRectTopLeftPoint], 3881 points[(int)PiePoints.DoughnutTopRectBottomRightPoint], 3882 points[(int)PiePoints.DoughnutBottomRectTopLeftPoint], 3883 points[(int)PiePoints.DoughnutBottomRectBottomRightPoint], 3884 points[(int)PiePoints.DoughnutTop180], 3885 points[(int)PiePoints.DoughnutTopEnd], 3886 points[(int)PiePoints.DoughnutBottom180], 3887 points[(int)PiePoints.DoughnutBottomEnd], 3888 180, 3889 startAngle + sweepAngle - 180, 3890 pointIndex 3891 ); 3892 3893 } 3894 } 3895 3896 // Pie slice pass throw 0 degree. Curve has to be spited. 3897 else if (startAngle < 0 && endAngle > 0) 3898 { 3899 if (area.Area3DStyle.Inclination < 0) 3900 { 3901 graph.FillPieCurve( 3902 area, 3903 dataPoint, 3904 brush, 3905 pen, 3906 points[(int)PiePoints.DoughnutTopRectTopLeftPoint], 3907 points[(int)PiePoints.DoughnutTopRectBottomRightPoint], 3908 points[(int)PiePoints.DoughnutBottomRectTopLeftPoint], 3909 points[(int)PiePoints.DoughnutBottomRectBottomRightPoint], 3910 points[(int)PiePoints.DoughnutTopStart], 3911 points[(int)PiePoints.DoughnutTop0], 3912 points[(int)PiePoints.DoughnutBottomStart], 3913 points[(int)PiePoints.DoughnutBottom0], 3914 startAngle, 3915 -startAngle, 3916 pointIndex 3917 ); 3918 3919 } 3920 else 3921 { 3922 graph.FillPieCurve( 3923 area, 3924 dataPoint, 3925 brush, 3926 pen, 3927 points[(int)PiePoints.DoughnutTopRectTopLeftPoint], 3928 points[(int)PiePoints.DoughnutTopRectBottomRightPoint], 3929 points[(int)PiePoints.DoughnutBottomRectTopLeftPoint], 3930 points[(int)PiePoints.DoughnutBottomRectBottomRightPoint], 3931 points[(int)PiePoints.DoughnutTop0], 3932 points[(int)PiePoints.DoughnutTopEnd], 3933 points[(int)PiePoints.DoughnutBottom0], 3934 points[(int)PiePoints.DoughnutBottomEnd], 3935 0, 3936 sweepAngle + startAngle, 3937 pointIndex 3938 ); 3939 3940 } 3941 } 3942 // Pie slice pass throw 360 degree. Curve has to be spited. 3943 else if (startAngle < 360 && endAngle > 360) 3944 { 3945 if (area.Area3DStyle.Inclination < 0) 3946 { 3947 graph.FillPieCurve( 3948 area, 3949 dataPoint, 3950 brush, 3951 pen, 3952 points[(int)PiePoints.DoughnutTopRectTopLeftPoint], 3953 points[(int)PiePoints.DoughnutTopRectBottomRightPoint], 3954 points[(int)PiePoints.DoughnutBottomRectTopLeftPoint], 3955 points[(int)PiePoints.DoughnutBottomRectBottomRightPoint], 3956 points[(int)PiePoints.DoughnutTopStart], 3957 points[(int)PiePoints.DoughnutTop0], 3958 points[(int)PiePoints.DoughnutBottomStart], 3959 points[(int)PiePoints.DoughnutBottom0], 3960 startAngle, 3961 360 - startAngle, 3962 pointIndex 3963 ); 3964 3965 } 3966 else 3967 { 3968 graph.FillPieCurve( 3969 area, 3970 dataPoint, 3971 brush, 3972 pen, 3973 points[(int)PiePoints.DoughnutTopRectTopLeftPoint], 3974 points[(int)PiePoints.DoughnutTopRectBottomRightPoint], 3975 points[(int)PiePoints.DoughnutBottomRectTopLeftPoint], 3976 points[(int)PiePoints.DoughnutBottomRectBottomRightPoint], 3977 points[(int)PiePoints.DoughnutTop0], 3978 points[(int)PiePoints.DoughnutTopEnd], 3979 points[(int)PiePoints.DoughnutBottom0], 3980 points[(int)PiePoints.DoughnutBottomEnd], 3981 0, 3982 endAngle - 360, 3983 pointIndex 3984 ); 3985 } 3986 } 3987 else 3988 { 3989 // *************************************************** 3990 // REGULAR CASE: The curve is not split. 3991 // *************************************************** 3992 if (startAngle < 180 && startAngle >= 0 && area.Area3DStyle.Inclination > 0 3993 || startAngle < 540 && startAngle >= 360 && area.Area3DStyle.Inclination > 0 3994 || startAngle >= 180 && startAngle < 360 && area.Area3DStyle.Inclination < 0 3995 || startAngle >= -180 && startAngle < 0 && area.Area3DStyle.Inclination < 0 3996 ) 3997 { 3998 graph.FillPieCurve( 3999 area, 4000 dataPoint, 4001 brush, 4002 pen, 4003 points[(int)PiePoints.DoughnutTopRectTopLeftPoint], 4004 points[(int)PiePoints.DoughnutTopRectBottomRightPoint], 4005 points[(int)PiePoints.DoughnutBottomRectTopLeftPoint], 4006 points[(int)PiePoints.DoughnutBottomRectBottomRightPoint], 4007 points[(int)PiePoints.DoughnutTopStart], 4008 points[(int)PiePoints.DoughnutTopEnd], 4009 points[(int)PiePoints.DoughnutBottomStart], 4010 points[(int)PiePoints.DoughnutBottomEnd], 4011 startAngle, 4012 sweepAngle, 4013 pointIndex 4014 ); 4015 } 4016 } 4017 4018 } 4019 4020 } 4021 4022 4023 /// <summary> 4024 /// This method is used for special case when big doughnut slice has to be drawn. 4025 /// </summary> 4026 /// <param name="graph">Chart Grahics.</param> 4027 /// <param name="area">Chart Area.</param> 4028 /// <param name="dataPoint">Data Point used for pie slice.</param> 4029 /// <param name="startAngle">Start angle of a pie slice.</param> 4030 /// <param name="sweepAngle">Sweep angle of a pie slice.</param> 4031 /// <param name="points">Important 3d points of a pie slice.</param> 4032 /// <param name="brush">Brush without lithing efects.</param> 4033 /// <param name="pen">Pen used for border.</param> 4034 /// <param name="rightPosition">Position of the curve of big pie slice. Big pie slice coud have to visible curves - left and right</param> 4035 /// <param name="sameBackFront">This is big pie slice which is in same time back and front slice.</param> 4036 /// <param name="pointIndex">Data Point Index</param> 4037 /// <returns>True if slice is special case and it is drawn as a special case.</returns> DrawDoughnutCurvesBigSlice( ChartGraphics graph, ChartArea area, DataPoint dataPoint, float startAngle, float sweepAngle, PointF [] points, Brush brush, Pen pen, bool rightPosition, bool sameBackFront, int pointIndex )4038 private bool DrawDoughnutCurvesBigSlice 4039 ( 4040 ChartGraphics graph, 4041 ChartArea area, 4042 DataPoint dataPoint, 4043 float startAngle, 4044 float sweepAngle, 4045 PointF [] points, 4046 Brush brush, 4047 Pen pen, 4048 bool rightPosition, 4049 bool sameBackFront, 4050 int pointIndex 4051 ) 4052 { 4053 float endAngle = startAngle + sweepAngle; 4054 4055 // Two different cases connected with X angle. 4056 // ***************************************************** 4057 // X angle is positive 4058 // ***************************************************** 4059 if( area.Area3DStyle.Inclination < 0 ) 4060 { 4061 // Show curve from 0 to 180. 4062 if( startAngle < 180 && endAngle > 360 ) 4063 { 4064 graph.FillPieCurve( 4065 area, 4066 dataPoint, 4067 brush, 4068 pen, 4069 points[(int)PiePoints.DoughnutTopRectTopLeftPoint], 4070 points[(int)PiePoints.DoughnutTopRectBottomRightPoint], 4071 points[(int)PiePoints.DoughnutBottomRectTopLeftPoint], 4072 points[(int)PiePoints.DoughnutBottomRectBottomRightPoint], 4073 points[(int)PiePoints.DoughnutTop0], 4074 points[(int)PiePoints.DoughnutTop180], 4075 points[(int)PiePoints.DoughnutBottom0], 4076 points[(int)PiePoints.DoughnutBottom180], 4077 0, 4078 -180, 4079 pointIndex 4080 ); 4081 } 4082 else if( startAngle < 0 && endAngle > 180 ) 4083 { 4084 // There is big data point which is back and 4085 // front point in same time. 4086 if( sameBackFront ) 4087 { 4088 // The big pie slice has to be split. This part makes 4089 // decision which part of this big slice will be 4090 // drawn first. 4091 if( rightPosition ) 4092 { 4093 graph.FillPieCurve( 4094 area, 4095 dataPoint, 4096 brush, 4097 pen, 4098 points[(int)PiePoints.DoughnutTopRectTopLeftPoint], 4099 points[(int)PiePoints.DoughnutTopRectBottomRightPoint], 4100 points[(int)PiePoints.DoughnutBottomRectTopLeftPoint], 4101 points[(int)PiePoints.DoughnutBottomRectBottomRightPoint], 4102 points[(int)PiePoints.DoughnutTop180], 4103 points[(int)PiePoints.DoughnutTopEnd], 4104 points[(int)PiePoints.DoughnutBottom180], 4105 points[(int)PiePoints.DoughnutBottomEnd], 4106 180, 4107 endAngle - 180, 4108 pointIndex 4109 ); 4110 } 4111 else 4112 { 4113 graph.FillPieCurve( 4114 area, 4115 dataPoint, 4116 brush, 4117 pen, 4118 points[(int)PiePoints.DoughnutTopRectTopLeftPoint], 4119 points[(int)PiePoints.DoughnutTopRectBottomRightPoint], 4120 points[(int)PiePoints.DoughnutBottomRectTopLeftPoint], 4121 points[(int)PiePoints.DoughnutBottomRectBottomRightPoint], 4122 points[(int)PiePoints.DoughnutTopStart], 4123 points[(int)PiePoints.DoughnutTop0], 4124 points[(int)PiePoints.DoughnutBottomStart], 4125 points[(int)PiePoints.DoughnutBottom0], 4126 startAngle, 4127 -startAngle, 4128 pointIndex 4129 ); 4130 } 4131 } 4132 else 4133 { 4134 // There is big pie slice (>180), but that pie slice 4135 // is not back and front point in same time. 4136 graph.FillPieCurve( 4137 area, 4138 dataPoint, 4139 brush, 4140 pen, 4141 points[(int)PiePoints.DoughnutTopRectTopLeftPoint], 4142 points[(int)PiePoints.DoughnutTopRectBottomRightPoint], 4143 points[(int)PiePoints.DoughnutBottomRectTopLeftPoint], 4144 points[(int)PiePoints.DoughnutBottomRectBottomRightPoint], 4145 points[(int)PiePoints.DoughnutTopStart], 4146 points[(int)PiePoints.DoughnutTop0], 4147 points[(int)PiePoints.DoughnutBottomStart], 4148 points[(int)PiePoints.DoughnutBottom0], 4149 startAngle, 4150 -startAngle, 4151 pointIndex 4152 ); 4153 4154 graph.FillPieCurve( 4155 area, 4156 dataPoint, 4157 brush, 4158 pen, 4159 points[(int)PiePoints.DoughnutTopRectTopLeftPoint], 4160 points[(int)PiePoints.DoughnutTopRectBottomRightPoint], 4161 points[(int)PiePoints.DoughnutBottomRectTopLeftPoint], 4162 points[(int)PiePoints.DoughnutBottomRectBottomRightPoint], 4163 points[(int)PiePoints.DoughnutTop180], 4164 points[(int)PiePoints.DoughnutTopEnd], 4165 points[(int)PiePoints.DoughnutBottom180], 4166 points[(int)PiePoints.DoughnutBottomEnd], 4167 180, 4168 endAngle - 180, 4169 pointIndex 4170 ); 4171 } 4172 4173 } 4174 else 4175 { 4176 // Big pie slice behaves as normal pie slice. Continue 4177 // Non special case alghoritham 4178 return false; 4179 } 4180 } 4181 // ********************************************* 4182 // X angle negative 4183 // ********************************************* 4184 else 4185 { 4186 // Show curve from 0 to 180. 4187 if( startAngle < 0 && endAngle > 180 ) 4188 { 4189 graph.FillPieCurve( 4190 area, 4191 dataPoint, 4192 brush, 4193 pen, 4194 points[(int)PiePoints.DoughnutTopRectTopLeftPoint], 4195 points[(int)PiePoints.DoughnutTopRectBottomRightPoint], 4196 points[(int)PiePoints.DoughnutBottomRectTopLeftPoint], 4197 points[(int)PiePoints.DoughnutBottomRectBottomRightPoint], 4198 points[(int)PiePoints.DoughnutTop0], 4199 points[(int)PiePoints.DoughnutTop180], 4200 points[(int)PiePoints.DoughnutBottom0], 4201 points[(int)PiePoints.DoughnutBottom180], 4202 0, 4203 180, 4204 pointIndex 4205 ); 4206 } 4207 else if( startAngle < 180 && endAngle > 360 ) 4208 { 4209 // There is big data point which is back and 4210 // front point in same time. 4211 if( sameBackFront ) 4212 { 4213 // The big pie slice has to be split. This part makes 4214 // decision which part of this big slice will be 4215 // drawn first. 4216 if( rightPosition ) 4217 { 4218 graph.FillPieCurve( 4219 area, 4220 dataPoint, 4221 brush, 4222 pen, 4223 points[(int)PiePoints.DoughnutTopRectTopLeftPoint], 4224 points[(int)PiePoints.DoughnutTopRectBottomRightPoint], 4225 points[(int)PiePoints.DoughnutBottomRectTopLeftPoint], 4226 points[(int)PiePoints.DoughnutBottomRectBottomRightPoint], 4227 points[(int)PiePoints.DoughnutTopStart], 4228 points[(int)PiePoints.DoughnutTop180], 4229 points[(int)PiePoints.DoughnutBottomStart], 4230 points[(int)PiePoints.DoughnutBottom180], 4231 startAngle, 4232 180 - startAngle, 4233 pointIndex 4234 ); 4235 } 4236 else 4237 { 4238 graph.FillPieCurve( 4239 area, 4240 dataPoint, 4241 brush, 4242 pen, 4243 points[(int)PiePoints.DoughnutTopRectTopLeftPoint], 4244 points[(int)PiePoints.DoughnutTopRectBottomRightPoint], 4245 points[(int)PiePoints.DoughnutBottomRectTopLeftPoint], 4246 points[(int)PiePoints.DoughnutBottomRectBottomRightPoint], 4247 points[(int)PiePoints.DoughnutTop0], 4248 points[(int)PiePoints.DoughnutTopEnd], 4249 points[(int)PiePoints.DoughnutBottom0], 4250 points[(int)PiePoints.DoughnutBottomEnd], 4251 0, 4252 endAngle - 360, 4253 pointIndex 4254 ); 4255 } 4256 } 4257 else 4258 { 4259 // There is big pie slice (>180), but that pie slice 4260 // is not back and front point in same time. 4261 graph.FillPieCurve( 4262 area, 4263 dataPoint, 4264 brush, 4265 pen, 4266 points[(int)PiePoints.DoughnutTopRectTopLeftPoint], 4267 points[(int)PiePoints.DoughnutTopRectBottomRightPoint], 4268 points[(int)PiePoints.DoughnutBottomRectTopLeftPoint], 4269 points[(int)PiePoints.DoughnutBottomRectBottomRightPoint], 4270 points[(int)PiePoints.DoughnutTop0], 4271 points[(int)PiePoints.DoughnutTopEnd], 4272 points[(int)PiePoints.DoughnutBottom0], 4273 points[(int)PiePoints.DoughnutBottomEnd], 4274 0, 4275 endAngle - 360, 4276 pointIndex 4277 ); 4278 4279 graph.FillPieCurve( 4280 area, 4281 dataPoint, 4282 brush, 4283 pen, 4284 points[(int)PiePoints.DoughnutTopRectTopLeftPoint], 4285 points[(int)PiePoints.DoughnutTopRectBottomRightPoint], 4286 points[(int)PiePoints.DoughnutBottomRectTopLeftPoint], 4287 points[(int)PiePoints.DoughnutBottomRectBottomRightPoint], 4288 points[(int)PiePoints.DoughnutTopStart], 4289 points[(int)PiePoints.DoughnutTop180], 4290 points[(int)PiePoints.DoughnutBottomStart], 4291 points[(int)PiePoints.DoughnutBottom180], 4292 startAngle, 4293 180 - startAngle, 4294 pointIndex 4295 ); 4296 } 4297 4298 } 4299 else 4300 { 4301 // Big pie slice behaves as normal pie slice. Continue 4302 // Non special case alghoritham 4303 return false; 4304 } 4305 } 4306 4307 return true; 4308 } 4309 4310 4311 4312 #endregion 4313 4314 #region 3D Order of points Methods 4315 4316 /// <summary> 4317 /// This method sort data points on specific way. Because 4318 /// of order of drawing in 3D space, the back data point 4319 /// (point which pass throw 270 degree has to be drawn first. 4320 /// After that side data points have to be drawn. At the end 4321 /// front data point (data point which pass throw 0 degree) 4322 /// has to be drawn. There is special case if there is big 4323 /// data point, which is back and front point in same time. 4324 /// </summary> 4325 /// <param name="series">Data series</param> 4326 /// <param name="area">Chart area</param> 4327 /// <param name="newStartAngleList">Unsorted List of Start angles.</param> 4328 /// <param name="newSweepAngleList">Unsorted List of Sweep angles.</param> 4329 /// <param name="newPointIndexList">Data Point index list</param> 4330 /// <param name="sameBackFrontPoint">Beck and Fron Points are same - There is a big pie slice.</param> 4331 /// <returns>Sorted data point list.</returns> PointOrder( Series series, ChartArea area, out float [] newStartAngleList, out float [] newSweepAngleList, out int [] newPointIndexList, out bool sameBackFrontPoint )4332 private DataPoint [] PointOrder( Series series, ChartArea area, out float [] newStartAngleList, out float [] newSweepAngleList, out int [] newPointIndexList, out bool sameBackFrontPoint ) 4333 { 4334 4335 double startAngle; 4336 double sweepAngle; 4337 double endAngle; 4338 int backPoint = -1; 4339 int frontPoint = -1; 4340 sameBackFrontPoint = false; 4341 4342 // The data points loop. Find Sum of data points. 4343 double sum = 0; 4344 int numOfEmpty = 0; 4345 foreach( DataPoint point in series.Points ) 4346 { 4347 if( point.IsEmpty ) 4348 numOfEmpty++; 4349 4350 if( !point.IsEmpty ) 4351 { 4352 sum += Math.Abs(point.YValues[0]); 4353 } 4354 } 4355 4356 // Find number of data points 4357 int numOfPoints = series.Points.Count - numOfEmpty; 4358 4359 DataPoint [] points = new DataPoint[ numOfPoints ]; 4360 float [] startAngleList = new float[ numOfPoints ]; 4361 float [] sweepAngleList = new float[ numOfPoints ]; 4362 int [] pointIndexList = new int[ numOfPoints ]; 4363 newStartAngleList = new float[ numOfPoints ]; 4364 newSweepAngleList = new float[ numOfPoints ]; 4365 newPointIndexList = new int[ numOfPoints ]; 4366 4367 // If sum is less then 0 do not draw pie chart 4368 if( sum <= 0 ) 4369 { 4370 return null; 4371 } 4372 // ***************************************************** 4373 // Find Back and Front Points. Back point is a point 4374 // which pass throw 270 degree. Front point pass 4375 // throw 90 degree. 4376 // There are two points in the data point list which will be 4377 // placed at the end and at the beginning on the sorted list: Back 4378 // point (beginning) and Front point (the end). Back point could 4379 // be only after Front point at the unsorted list. 4380 // ***************************************************** 4381 int pointIndx = 0; 4382 startAngle = area.Area3DStyle.Rotation; 4383 foreach( DataPoint point in series.Points ) 4384 { 4385 // Do not process empty points 4386 if( point.IsEmpty ) 4387 { 4388 continue; 4389 } 4390 4391 // Find angles 4392 sweepAngle = (float)( Math.Abs(point.YValues[0]) * 360 / sum ); 4393 endAngle = startAngle + sweepAngle; 4394 4395 startAngleList[ pointIndx ] = (float)startAngle; 4396 sweepAngleList[ pointIndx ] = (float)sweepAngle; 4397 pointIndexList[ pointIndx ] = pointIndx; 4398 4399 // *************************************************************** 4400 // Find Back point. 4401 // Because angle could be between -180 and 540 ( Y axis 4402 // rotation from -180 to 180 ), Back point could be at -90 and 270 4403 // *************************************************************** 4404 if( startAngle <= -90 && endAngle > -90 || startAngle <= 270 && endAngle > 270 && points[0] == null ) 4405 { 4406 /*if( points[0] != null ) 4407 throw new InvalidOperationException(SR.ExceptionPiePointOrderInvalid); 4408 */ 4409 backPoint = pointIndx; 4410 points[0] = point; 4411 newStartAngleList[0] = startAngleList[pointIndx]; 4412 newSweepAngleList[0] = sweepAngleList[pointIndx]; 4413 newPointIndexList[0] = pointIndexList[pointIndx]; 4414 } 4415 4416 // *************************************************************** 4417 // Find Front point. 4418 // Because angle could be between -180 and 540 ( Y axis 4419 // rotation from -180 to 180 ), Front point could be at 90 and 450 4420 // Case frontPoint == -1 is set because of rounding error. 4421 // *************************************************************** 4422 if( startAngle <= 90 && endAngle > 90 || startAngle <= 450 && endAngle > 450 && frontPoint == -1 && ( points[points.Length-1] == null || points.Length == 1 ) ) 4423 { 4424 /* 4425 if( points[points.Length-1] != null && points.Length != 1) 4426 throw new InvalidOperationException(SR.ExceptionPiePointOrderInvalid); 4427 */ 4428 frontPoint = pointIndx; 4429 points[points.Length-1] = point; 4430 newStartAngleList[points.Length-1] = startAngleList[pointIndx]; 4431 newSweepAngleList[points.Length-1] = sweepAngleList[pointIndx]; 4432 newPointIndexList[points.Length-1] = pointIndexList[pointIndx]; 4433 } 4434 4435 pointIndx++; 4436 startAngle += sweepAngle; 4437 } 4438 4439 if( frontPoint == -1 || backPoint == -1 ) 4440 { 4441 throw new InvalidOperationException(SR.ExceptionPieUnassignedFrontBackPoints); 4442 } 4443 4444 // If front point and back point are same do not 4445 // put same point in two fields. 4446 if( frontPoint == backPoint && points.Length != 1 ) 4447 { 4448 points[points.Length-1] = null; 4449 newStartAngleList[points.Length-1] = 0; 4450 newSweepAngleList[points.Length-1] = 0; 4451 newPointIndexList[points.Length-1] = 0; 4452 sameBackFrontPoint = true; 4453 } 4454 4455 // ******************************************** 4456 // Special case. Front Point and Back points 4457 // are same. 4458 // ******************************************** 4459 if( frontPoint == backPoint ) 4460 { 4461 // Find middle angle of a data point 4462 float midAngle = startAngleList[backPoint] + sweepAngleList[backPoint] / 2F; 4463 4464 int listIndx; 4465 bool rightSidePoints = false; 4466 4467 // If big pie slice is on the right and all other 4468 // pie slices are on the left. 4469 if( midAngle > -90 && midAngle < 90 || midAngle > 270 && midAngle < 450 ) 4470 { 4471 rightSidePoints = true; 4472 } 4473 4474 listIndx = numOfPoints - frontPoint; 4475 pointIndx = 0; 4476 foreach( DataPoint point in series.Points ) 4477 { 4478 // Do not process empty points 4479 if( point.IsEmpty ) 4480 { 4481 continue; 4482 } 4483 4484 // If Front and back points continue with loop 4485 if( pointIndx == frontPoint ) 4486 { 4487 pointIndx++; 4488 continue; 4489 } 4490 4491 if( pointIndx < frontPoint ) 4492 { 4493 if( points[listIndx] != null ) 4494 throw new InvalidOperationException(SR.ExceptionPiePointOrderInvalid); 4495 points[listIndx] = point; 4496 newStartAngleList[listIndx] = startAngleList[pointIndx]; 4497 newSweepAngleList[listIndx] = sweepAngleList[pointIndx]; 4498 newPointIndexList[listIndx] = pointIndexList[pointIndx]; 4499 4500 listIndx++; 4501 } 4502 pointIndx++; 4503 } 4504 4505 pointIndx = 0; 4506 listIndx = 1; 4507 foreach( DataPoint point in series.Points ) 4508 { 4509 // Do not process empty points 4510 if( point.IsEmpty ) 4511 { 4512 continue; 4513 } 4514 4515 // If Front and back points continue with loop 4516 if( pointIndx == frontPoint ) 4517 { 4518 pointIndx++; 4519 continue; 4520 } 4521 4522 if( pointIndx > frontPoint ) 4523 { 4524 if( points[listIndx] != null ) 4525 throw new InvalidOperationException(SR.ExceptionPiePointOrderInvalid); 4526 points[listIndx] = point; 4527 newStartAngleList[listIndx] = startAngleList[pointIndx]; 4528 newSweepAngleList[listIndx] = sweepAngleList[pointIndx]; 4529 newPointIndexList[listIndx] = pointIndexList[pointIndx]; 4530 listIndx++; 4531 } 4532 pointIndx++; 4533 } 4534 if( rightSidePoints ) 4535 { 4536 SwitchPoints( numOfPoints, ref points, ref newStartAngleList, ref newSweepAngleList, ref newPointIndexList, backPoint == frontPoint ); 4537 } 4538 } 4539 else if( frontPoint < backPoint ) 4540 { 4541 4542 // ************************************************ 4543 // Fill From Back Point to the end of unsorted list 4544 // ************************************************ 4545 pointIndx = 0; 4546 int listIndx = 1; 4547 foreach( DataPoint point in series.Points ) 4548 { 4549 // Do not process empty points 4550 if( point.IsEmpty ) 4551 { 4552 continue; 4553 } 4554 4555 // If Front and back points continue with loop 4556 if( pointIndx == frontPoint || pointIndx == backPoint ) 4557 { 4558 pointIndx++; 4559 continue; 4560 } 4561 4562 // If curent point is after front point. 4563 else if( pointIndx > backPoint ) 4564 { 4565 if( points[listIndx] != null ) 4566 throw new InvalidOperationException(SR.ExceptionPiePointOrderInvalid); 4567 points[listIndx] = point; 4568 newStartAngleList[listIndx] = startAngleList[pointIndx]; 4569 newSweepAngleList[listIndx] = sweepAngleList[pointIndx]; 4570 newPointIndexList[listIndx] = pointIndexList[pointIndx]; 4571 listIndx++; 4572 } 4573 4574 pointIndx++; 4575 } 4576 4577 // ****************************************************** 4578 // Fill from the begining of unsorted list to Front Point 4579 // ****************************************************** 4580 pointIndx = 0; 4581 foreach( DataPoint point in series.Points ) 4582 { 4583 // Do not process empty points 4584 if( point.IsEmpty ) 4585 { 4586 continue; 4587 } 4588 4589 // If Front and back points continue with loop 4590 if( pointIndx == frontPoint || pointIndx == backPoint ) 4591 { 4592 pointIndx++; 4593 continue; 4594 } 4595 4596 // If curent point is before front point. 4597 else if( pointIndx < frontPoint ) 4598 { 4599 if( points[listIndx] != null ) 4600 throw new InvalidOperationException(SR.ExceptionPiePointOrderInvalid); 4601 points[listIndx] = point; 4602 newStartAngleList[listIndx] = startAngleList[pointIndx]; 4603 newSweepAngleList[listIndx] = sweepAngleList[pointIndx]; 4604 newPointIndexList[listIndx] = pointIndexList[pointIndx]; 4605 listIndx++; 4606 } 4607 4608 pointIndx++; 4609 } 4610 4611 4612 // ********************************************************* 4613 // This code run only if special case is not active. 4614 // Special case: FrontPoint and back point are same. This is 4615 // happening because pie slice is bigger then 180 degree. 4616 // ********************************************************* 4617 4618 4619 // ********************************** 4620 // Fill from Front Point to Back Point 4621 // ********************************** 4622 listIndx = points.Length - 2; 4623 pointIndx = 0; 4624 foreach( DataPoint point in series.Points ) 4625 { 4626 // Do not process empty points 4627 if( point.IsEmpty ) 4628 { 4629 continue; 4630 } 4631 4632 // If Front and back points continue with loop 4633 if( pointIndx == frontPoint || pointIndx == backPoint ) 4634 { 4635 pointIndx++; 4636 continue; 4637 } 4638 4639 // If curent point is between front point and back point. 4640 else if( pointIndx > frontPoint && pointIndx < backPoint ) 4641 { 4642 if (points[listIndx] != null) throw new InvalidOperationException(SR.ExceptionPiePointOrderInvalid); 4643 points[listIndx] = point; 4644 newStartAngleList[listIndx] = startAngleList[pointIndx]; 4645 newSweepAngleList[listIndx] = sweepAngleList[pointIndx]; 4646 newPointIndexList[listIndx] = pointIndexList[pointIndx]; 4647 listIndx--; 4648 } 4649 4650 pointIndx++; 4651 } 4652 } 4653 else 4654 { 4655 // ********************************** 4656 // Fill from Back Point to Front Point 4657 // ********************************** 4658 int listIndx = 1; 4659 pointIndx = 0; 4660 foreach( DataPoint point in series.Points ) 4661 { 4662 // Do not process empty points 4663 if( point.IsEmpty ) 4664 { 4665 continue; 4666 } 4667 4668 // If Front and back points continue with loop 4669 if( pointIndx == frontPoint || pointIndx == backPoint ) 4670 { 4671 pointIndx++; 4672 continue; 4673 } 4674 4675 // If curent point is between front back and front points. 4676 else if( pointIndx > backPoint && pointIndx < frontPoint ) 4677 { 4678 if (points[listIndx] != null) throw new InvalidOperationException(SR.ExceptionPiePointOrderInvalid); 4679 points[listIndx] = point; 4680 newStartAngleList[listIndx] = startAngleList[pointIndx]; 4681 newSweepAngleList[listIndx] = sweepAngleList[pointIndx]; 4682 newPointIndexList[listIndx] = pointIndexList[pointIndx]; 4683 listIndx++; 4684 } 4685 4686 pointIndx++; 4687 } 4688 4689 // ************************************************ 4690 // Fill From Front Point to the end of unsorted list 4691 // ************************************************ 4692 listIndx = points.Length - 2; 4693 pointIndx = 0; 4694 foreach( DataPoint point in series.Points ) 4695 { 4696 // Do not process empty points 4697 if( point.IsEmpty ) 4698 { 4699 continue; 4700 } 4701 4702 // If Front and back points continue with loop 4703 if( pointIndx == frontPoint || pointIndx == backPoint ) 4704 { 4705 pointIndx++; 4706 continue; 4707 } 4708 // If curent point is after front point. 4709 else if( pointIndx > frontPoint ) 4710 { 4711 if( points[listIndx] != null ) 4712 throw new InvalidOperationException(SR.ExceptionPiePointOrderInvalid); 4713 points[listIndx] = point; 4714 newStartAngleList[listIndx] = startAngleList[pointIndx]; 4715 newSweepAngleList[listIndx] = sweepAngleList[pointIndx]; 4716 newPointIndexList[listIndx] = pointIndexList[pointIndx]; 4717 listIndx--; 4718 } 4719 4720 pointIndx++; 4721 } 4722 4723 // ****************************************************** 4724 // Fill from the begining of unsorted list to Back Point 4725 // ****************************************************** 4726 pointIndx = 0; 4727 foreach( DataPoint point in series.Points ) 4728 { 4729 // Do not process empty points 4730 if( point.IsEmpty ) 4731 { 4732 continue; 4733 } 4734 4735 // If Front and back points continue with loop 4736 if( pointIndx == frontPoint || pointIndx == backPoint ) 4737 { 4738 pointIndx++; 4739 continue; 4740 } 4741 4742 // If curent point is before front point. 4743 else if( pointIndx < backPoint ) 4744 { 4745 if( points[listIndx] != null ) 4746 throw new InvalidOperationException(SR.ExceptionPiePointOrderInvalid); 4747 points[listIndx] = point; 4748 newStartAngleList[listIndx] = startAngleList[pointIndx]; 4749 newSweepAngleList[listIndx] = sweepAngleList[pointIndx]; 4750 newPointIndexList[listIndx] = pointIndexList[pointIndx]; 4751 listIndx--; 4752 } 4753 4754 pointIndx++; 4755 } 4756 4757 4758 // ********************************************************* 4759 // This code run only if special case is not active. 4760 // Special case: FrontPoint and back point are same. This is 4761 // happening because pie slice is bigger then 180 degree. 4762 // ********************************************************* 4763 4764 4765 4766 } 4767 4768 4769 // ******************************************************* 4770 // If X angle is positive direction of drawing data points 4771 // should be opposite. This part of code switch order of 4772 // data points. 4773 // ******************************************************* 4774 if( area.Area3DStyle.Inclination > 0 ) 4775 { 4776 SwitchPoints( numOfPoints, ref points, ref newStartAngleList, ref newSweepAngleList, ref newPointIndexList, backPoint == frontPoint ); 4777 } 4778 4779 return points; 4780 } 4781 4782 /// <summary> 4783 /// This method switches order of data points in the array of points. 4784 /// </summary> 4785 /// <param name="numOfPoints">Number of data points</param> 4786 /// <param name="points">Array of Data points</param> 4787 /// <param name="newStartAngleList">List of start angles which has to be switched together with data points</param> 4788 /// <param name="newSweepAngleList">List of sweep angles which has to be switched together with data points</param> 4789 /// <param name="newPointIndexList">Indexes (position) of data points in the series</param> 4790 /// <param name="sameBackFront">There is big pie slice which has same back and front pie slice</param> SwitchPoints( int numOfPoints, ref DataPoint [] points, ref float [] newStartAngleList, ref float [] newSweepAngleList, ref int [] newPointIndexList, bool sameBackFront )4791 private void SwitchPoints( int numOfPoints, ref DataPoint [] points, ref float [] newStartAngleList, ref float [] newSweepAngleList, ref int [] newPointIndexList, bool sameBackFront ) 4792 { 4793 float [] tempStartAngles = new float[ numOfPoints ]; 4794 float [] tempSweepAngles = new float[ numOfPoints ]; 4795 int [] tempPointIndexList = new int[ numOfPoints ]; 4796 DataPoint [] tempPoints = new DataPoint[ numOfPoints ]; 4797 int start = 0;; 4798 4799 // The big pie slice (special case) is always on the beginning. 4800 if( sameBackFront ) 4801 { 4802 start = 1; 4803 4804 // Switch order. 4805 tempPoints[0] = points[0]; 4806 tempStartAngles[0] = newStartAngleList[0]; 4807 tempSweepAngles[0] = newSweepAngleList[0]; 4808 tempPointIndexList[0] = newPointIndexList[0]; 4809 } 4810 4811 for( int index = start; index < numOfPoints; index++ ) 4812 { 4813 if( points[ index ] == null ) 4814 { 4815 throw new InvalidOperationException(SR.ExceptionPieOrderOperationInvalid); 4816 } 4817 4818 // Switch order. 4819 tempPoints[ numOfPoints - index - 1 + start] = points[ index ]; 4820 tempStartAngles[ numOfPoints - index - 1 + start] = newStartAngleList[ index ]; 4821 tempSweepAngles[ numOfPoints - index - 1 + start] = newSweepAngleList[ index ]; 4822 tempPointIndexList[ numOfPoints - index - 1 + start] = newPointIndexList[ index ]; 4823 4824 } 4825 4826 points = tempPoints; 4827 newStartAngleList = tempStartAngles; 4828 newSweepAngleList = tempSweepAngles; 4829 newPointIndexList = tempPointIndexList; 4830 4831 } 4832 4833 #endregion 4834 4835 #region 3D Label column class 4836 4837 /// <summary> 4838 /// LabelColumn class is used for labels manipulation - outside label style 4839 /// </summary> 4840 internal class LabelColumn 4841 { 4842 // Fields of Label Column class 4843 private RectangleF _chartAreaPosition; 4844 private RectangleF _innerPlotPosition; 4845 internal float columnHeight; 4846 internal int numOfItems = 0; 4847 private int _numOfInsertedLabels = 0; 4848 private DataPoint [] _points; 4849 private float [] _yPositions; 4850 private bool _rightPosition = true; 4851 private float _labelLineSize; 4852 4853 /// <summary> 4854 /// Constructor 4855 /// </summary> 4856 /// <param name="position">Chart Area position.</param> LabelColumn( RectangleF position )4857 public LabelColumn( RectangleF position ) 4858 { 4859 _chartAreaPosition = position; 4860 } 4861 4862 /// <summary> 4863 /// Return index of label position in the column. 4864 /// </summary> 4865 /// <param name="y">y coordinate</param> 4866 /// <returns>Index of column</returns> GetLabelIndex( float y )4867 internal int GetLabelIndex( float y ) 4868 { 4869 // y coordinate is out of chart area. 4870 if( y < _chartAreaPosition.Y ) 4871 { 4872 y = _chartAreaPosition.Y; 4873 } 4874 else if( y > _chartAreaPosition.Bottom ) 4875 { 4876 y = _chartAreaPosition.Bottom - columnHeight; 4877 } 4878 4879 return (int) (( y - _chartAreaPosition.Y ) / columnHeight ) ; 4880 } 4881 4882 /// <summary> 4883 /// This method sorts labels by y Position 4884 /// </summary> Sort()4885 internal void Sort() 4886 { 4887 for( int indexA = 0; indexA < _points.Length; indexA++ ) 4888 { 4889 for( int indexB = 0; indexB < indexA; indexB++ ) 4890 { 4891 if( _yPositions[indexA] < _yPositions[indexB] && _points[indexA] != null && _points[indexB] != null ) 4892 { 4893 float tempYPos; 4894 DataPoint tempPoint; 4895 tempYPos = _yPositions[indexA]; 4896 tempPoint = _points[indexA]; 4897 _yPositions[indexA] = _yPositions[indexB]; 4898 _points[indexA] = _points[indexB]; 4899 _yPositions[indexB] = tempYPos; 4900 _points[indexB] = tempPoint; 4901 } 4902 } 4903 } 4904 } 4905 4906 /// <summary> 4907 /// Returns label position y coordinate from index position 4908 /// </summary> 4909 /// <param name="index">Index position of the row</param> 4910 /// <returns>Y coordinate row position</returns> GetLabelPosition( int index )4911 internal float GetLabelPosition( int index ) 4912 { 4913 if( index < 0 || index > numOfItems - 1 ) 4914 throw new InvalidOperationException(SR.Exception3DPieLabelsIndexInvalid); 4915 4916 return (float) _chartAreaPosition.Y + columnHeight * index + columnHeight / 2; 4917 } 4918 4919 /// <summary> 4920 /// This method finds X and Y position for outside 4921 /// labels. There is discrete number of cells and 4922 /// Y position depends on cell position. X position 4923 /// is connected with angle between invisible 4924 /// line (which connects center of a pie and label) 4925 /// and any horizontal line. 4926 /// </summary> 4927 /// <param name="dataPoint">Data Point</param> 4928 /// <returns>Position of a label</returns> GetLabelPosition( DataPoint dataPoint )4929 internal PointF GetLabelPosition( DataPoint dataPoint ) 4930 { 4931 PointF position = PointF.Empty; 4932 int pointIndex = 0; 4933 4934 // Find Y position of Data Point 4935 // Loop is necessary to find index of data point in the array list. 4936 foreach( DataPoint point in _points ) 4937 { 4938 if( point == dataPoint ) 4939 { 4940 position.Y = GetLabelPosition( pointIndex ); 4941 break; 4942 } 4943 pointIndex++; 4944 } 4945 4946 // Find initial X position for labels ( All labels are aligne ). 4947 if( _rightPosition ) 4948 { 4949 position.X = _innerPlotPosition.Right + _chartAreaPosition.Width * this._labelLineSize; 4950 } 4951 else 4952 { 4953 position.X = _innerPlotPosition.Left - _chartAreaPosition.Width * this._labelLineSize; 4954 } 4955 4956 // Find angle between invisible line (which connects center of a pie and label) 4957 // and any horizontal line. 4958 float angle; 4959 angle = (float)Math.Atan( ( position.Y - _innerPlotPosition.Top - _innerPlotPosition.Height / 2) / ( position.X - _innerPlotPosition.Left - _innerPlotPosition.Width / 2 )); 4960 4961 // Make Angle correction for X Position 4962 float correct; 4963 if( Math.Cos( angle ) == 0 ) 4964 { 4965 correct = 0; 4966 } 4967 else 4968 { 4969 correct = (float)(_innerPlotPosition.Width * 0.4 - _innerPlotPosition.Width * 0.4 / Math.Cos( angle )); 4970 } 4971 4972 // Set Corrected X Position 4973 if( _rightPosition ) 4974 { 4975 position.X += correct; 4976 } 4977 else 4978 { 4979 position.X -= correct; 4980 } 4981 4982 return position; 4983 } 4984 4985 /// <summary> 4986 /// This method inserts outside labels in Column label list. Column label 4987 /// list has defined number of cells. This method has to put labels on 4988 /// the best position in the list. If two labels according to their 4989 /// positions belong to same cell of the list, this method should 4990 /// assign to them different positions. 4991 /// </summary> 4992 /// <param name="point">Data Point which label has to be inserted</param> 4993 /// <param name="yCoordinate">Y coordinate which is the best position for this label</param> 4994 /// <param name="pointIndx">Point index of this data point in the series</param> InsertLabel( DataPoint point, float yCoordinate, int pointIndx )4995 internal void InsertLabel( DataPoint point, float yCoordinate, int pointIndx ) 4996 { 4997 4998 // Find index of label list by Y value 4999 int indexYValue = GetLabelIndex( yCoordinate ); 5000 5001 // This position is already used. 5002 if( _points[indexYValue] != null ) 5003 { 5004 // All even elements go up and other 5005 // Down (If there are many labels which use this position). 5006 if( pointIndx % 2 == 0 ) 5007 { 5008 // Check if there is space Down 5009 if( CheckFreeSpace( indexYValue, false ) ) 5010 { 5011 // Move labels Down 5012 MoveLabels( indexYValue, false ); 5013 } 5014 else 5015 { 5016 // Move labels Up 5017 MoveLabels( indexYValue, true ); 5018 } 5019 } 5020 else 5021 { 5022 // Check if there is space Up 5023 if( CheckFreeSpace( indexYValue, true ) ) 5024 { 5025 // Move labels Up 5026 MoveLabels( indexYValue, true ); 5027 } 5028 else 5029 { 5030 // Move labels Down 5031 MoveLabels( indexYValue, false ); 5032 } 5033 } 5034 } 5035 5036 // Set label position 5037 _points[indexYValue] = point; 5038 _yPositions[indexYValue] = yCoordinate; 5039 _numOfInsertedLabels++; 5040 } 5041 5042 /// <summary> 5043 /// This method is used for inserting labels. When label is inserted 5044 /// and that position was previously used, labels have to be 5045 /// moved on proper way. 5046 /// </summary> 5047 /// <param name="position">Position which has to be free</param> 5048 /// <param name="upDirection">Direction for moving labels</param> MoveLabels( int position, bool upDirection )5049 private void MoveLabels( int position, bool upDirection ) 5050 { 5051 if( upDirection ) 5052 { 5053 DataPoint point = _points[position]; 5054 float yValue = _yPositions[position]; 5055 _points[position] = null; 5056 _yPositions[position] = 0; 5057 5058 for( int index = position; index > 0; index-- ) 5059 { 5060 // IsEmpty position found. Stop moving cells UP 5061 if( _points[index-1] == null ) 5062 { 5063 _points[index-1] = point; 5064 _yPositions[index-1] = yValue; 5065 break; 5066 } 5067 else 5068 { 5069 DataPoint tempPoint; 5070 float tempYValue; 5071 5072 tempPoint = _points[index-1]; 5073 tempYValue = _yPositions[index-1]; 5074 _points[index-1] = point; 5075 _yPositions[index-1] = yValue; 5076 point = tempPoint; 5077 yValue = tempYValue; 5078 } 5079 } 5080 } 5081 else 5082 { 5083 DataPoint point = _points[position]; 5084 float yValue = _yPositions[position]; 5085 _points[position] = null; 5086 _yPositions[position] = 0; 5087 5088 for( int index = position; index < numOfItems-1; index++ ) 5089 { 5090 // IsEmpty position found. Stop moving cells UP 5091 if( _points[index+1] == null ) 5092 { 5093 _points[index+1] = point; 5094 _yPositions[index+1] = yValue; 5095 break; 5096 } 5097 else 5098 { 5099 DataPoint tempPoint; 5100 float tempYValue; 5101 5102 tempPoint = _points[index+1]; 5103 tempYValue = _yPositions[index+1]; 5104 _points[index+1] = point; 5105 _yPositions[index+1] = yValue; 5106 point = tempPoint; 5107 yValue = tempYValue; 5108 } 5109 } 5110 } 5111 } 5112 5113 /// <summary> 5114 /// This method is used to center labels in 5115 /// the middle of chart area (vertically). 5116 /// </summary> AdjustPositions()5117 internal void AdjustPositions() 5118 { 5119 int numEmptyUp = 0; 5120 int numEmptyDown = 0; 5121 5122 // Adjust position only if there are many labels 5123 if( _numOfInsertedLabels < _points.Length / 2 ) 5124 return; 5125 5126 // Find the number of empty label positions on the top. 5127 for( int point = 0; point < _points.Length && _points[point] == null; point++ ) 5128 { 5129 numEmptyUp++; 5130 } 5131 5132 // Find the number of empty label positions on the bottom. 5133 for( int point = _points.Length - 1; point >= 0 && _points[point] == null; point-- ) 5134 { 5135 numEmptyDown++; 5136 } 5137 5138 // Find where are more empty spaces � on the top or on the bottom. 5139 bool moreEmptyUp = numEmptyUp > numEmptyDown ? true : false; 5140 5141 // Find average number of empty spaces for top and bottom. 5142 int numMove = ( numEmptyUp + numEmptyDown ) / 2; 5143 5144 // If difference between empty spaces on the top and 5145 // the bottom is not bigger then 2 do not adjust labels. 5146 if( Math.Abs( numEmptyUp - numEmptyDown ) < 2 ) 5147 return; 5148 5149 if( moreEmptyUp ) 5150 { 5151 // Move labels UP 5152 int indexPoint = 0; 5153 for( int point = numMove; point < _points.Length; point++ ) 5154 { 5155 if(numEmptyUp+indexPoint > _points.Length - 1) 5156 break; 5157 5158 _points[point] = _points[numEmptyUp+indexPoint]; 5159 _points[numEmptyUp+indexPoint] = null; 5160 indexPoint++; 5161 } 5162 } 5163 else 5164 { 5165 // Move labels DOWN 5166 int indexPoint = _points.Length - 1; 5167 for( int point = _points.Length - 1 - numMove; point >= 0; point-- ) 5168 { 5169 if(indexPoint - numEmptyDown < 0) 5170 break; 5171 5172 _points[point] = _points[indexPoint - numEmptyDown]; 5173 _points[indexPoint - numEmptyDown] = null; 5174 indexPoint--; 5175 } 5176 } 5177 } 5178 5179 5180 /// <summary> 5181 /// Check if there is empty cell Labels column in 5182 /// specified direction from specified position 5183 /// </summary> 5184 /// <param name="position">Start Position for testing</param> 5185 /// <param name="upDirection">True if direction is upward, false if downward</param> 5186 /// <returns>True if there is empty cell</returns> CheckFreeSpace( int position, bool upDirection )5187 private bool CheckFreeSpace( int position, bool upDirection ) 5188 { 5189 if( upDirection ) 5190 { 5191 // Position is on the beginning. There is no empty space. 5192 if( position == 0 ) 5193 { 5194 return false; 5195 } 5196 5197 for( int index = position - 1; index >= 0; index-- ) 5198 { 5199 // There is empty space 5200 if( _points[index] == null ) 5201 { 5202 return true; 5203 } 5204 } 5205 } 5206 else 5207 { 5208 // Position is on the end. There is no empty space. 5209 if( position == numOfItems - 1 ) 5210 { 5211 return false; 5212 } 5213 5214 for( int index = position + 1; index < numOfItems; index++ ) 5215 { 5216 // There is empty space 5217 if( _points[index] == null ) 5218 { 5219 return true; 5220 } 5221 } 5222 } 5223 5224 // There is no empty space 5225 return false; 5226 } 5227 5228 5229 /// <summary> 5230 /// This method initialize label column. 5231 /// </summary> 5232 /// <param name="rectangle">Rectangle used for labels</param> 5233 /// <param name="rightPosition">True if labels are on the right side of chart area.</param> 5234 /// <param name="maxNumOfRows">Maximum nuber of rows.</param> 5235 /// <param name="labelLineSize">Value for label line size from custom attribute.</param> Initialize( RectangleF rectangle, bool rightPosition, int maxNumOfRows, float labelLineSize )5236 internal void Initialize( RectangleF rectangle, bool rightPosition, int maxNumOfRows, float labelLineSize ) 5237 { 5238 5239 // Minimum number of rows. 5240 numOfItems = Math.Max( numOfItems, maxNumOfRows ); 5241 5242 // Find height of rows 5243 columnHeight = _chartAreaPosition.Height / numOfItems; 5244 5245 // Set inner plot position 5246 _innerPlotPosition = rectangle; 5247 5248 // Init data column 5249 _points = new DataPoint[numOfItems]; 5250 5251 // Init y position column 5252 _yPositions = new float[numOfItems]; 5253 5254 // Label column position 5255 this._rightPosition = rightPosition; 5256 5257 // 3D Label line size 5258 this._labelLineSize = labelLineSize; 5259 5260 } 5261 5262 } 5263 5264 #endregion // 3D Label column class 5265 5266 #region 3D Labels 5267 5268 /// <summary> 5269 /// This method calculates initial pie size if outside 3D labels is active. 5270 /// </summary> 5271 /// <param name="graph">Chart Graphics object.</param> 5272 /// <param name="area">Chart Area.</param> 5273 /// <param name="pieRectangle">Rectangle which is used for drawing pie.</param> 5274 /// <param name="pieWidth">Width of pie slice.</param> 5275 /// <param name="dataPoints">List of data points.</param> 5276 /// <param name="startAngleList">List of start angles.</param> 5277 /// <param name="sweepAngleList">List of sweep angles.</param> 5278 /// <param name="series">Data series used for drawing pie chart.</param> 5279 /// <param name="labelLineSize">Custom Attribute for label line size.</param> InitPieSize( ChartGraphics graph, ChartArea area, ref RectangleF pieRectangle, ref float pieWidth, DataPoint [] dataPoints, float [] startAngleList, float [] sweepAngleList, Series series, float labelLineSize )5280 private void InitPieSize( 5281 ChartGraphics graph, 5282 ChartArea area, 5283 ref RectangleF pieRectangle, 5284 ref float pieWidth, 5285 DataPoint [] dataPoints, 5286 float [] startAngleList, 5287 float [] sweepAngleList, 5288 Series series, 5289 float labelLineSize 5290 ) 5291 { 5292 labelColumnLeft = new LabelColumn(area.Position.ToRectangleF()); 5293 labelColumnRight = new LabelColumn(area.Position.ToRectangleF()); 5294 float maxSize = float.MinValue; 5295 float maxSizeVertical = float.MinValue; 5296 5297 int pointIndx = 0; 5298 // Loop which finds max label size and number of label rows. 5299 foreach( DataPoint point in dataPoints ) 5300 { 5301 // Do not process empty points 5302 if( point.IsEmpty ) 5303 { 5304 continue; 5305 } 5306 5307 float midAngle = startAngleList[pointIndx] + sweepAngleList[pointIndx] / 2F; 5308 5309 if( midAngle >= -90 && midAngle < 90 || midAngle >= 270 && midAngle < 450 ) 5310 { 5311 labelColumnRight.numOfItems++; 5312 } 5313 else 5314 { 5315 labelColumnLeft.numOfItems++; 5316 } 5317 5318 // Find size of the maximum label string. 5319 SizeF size = graph.MeasureStringRel( GetLabelText( point ).Replace("\\n", "\n"), point.Font ); 5320 5321 maxSize = Math.Max( size.Width, maxSize ); 5322 maxSizeVertical = Math.Max( size.Height, maxSizeVertical ); 5323 5324 pointIndx++; 5325 } 5326 5327 float oldWidth = pieRectangle.Width; 5328 float oldHeight = pieRectangle.Height; 5329 5330 // Find size of inner plot are 5331 pieRectangle.Width = pieRectangle.Width - 2F * maxSize - 2 * pieRectangle.Width * labelLineSize; 5332 5333 pieRectangle.Height = pieRectangle.Height - pieRectangle.Height * 0.3F; 5334 5335 // Size of pie chart can not be less then MinimumRelativePieSize of chart area. 5336 if( pieRectangle.Width < oldWidth * (float)this.MinimumRelativePieSize( area ) ) 5337 { 5338 pieRectangle.Width = oldWidth * (float)this.MinimumRelativePieSize( area ); 5339 } 5340 5341 // Size of pie chart can not be less then MinimumRelativePieSize of chart area. 5342 if( pieRectangle.Height < oldHeight * (float)this.MinimumRelativePieSize( area ) ) 5343 { 5344 pieRectangle.Height = oldHeight * (float)this.MinimumRelativePieSize( area ); 5345 } 5346 5347 // Size has to be reduce always because of label lines. 5348 if( oldWidth * 0.8F < pieRectangle.Width ) 5349 { 5350 pieRectangle.Width *= 0.8F; 5351 } 5352 5353 pieRectangle.X = pieRectangle.X + ( oldWidth - pieRectangle.Width ) / 2F; 5354 pieWidth = pieRectangle.Width / oldWidth * pieWidth; 5355 5356 pieRectangle.Y = pieRectangle.Y + ( oldHeight - pieRectangle.Height ) / 2F; 5357 5358 // Find maximum number of rows. Number of rows will be changed 5359 // but this is only recommendation, which depends on font size 5360 // and Height of chart area. 5361 SizeF fontSize = new SizeF(1.4F * series.Font.Size,1.4F * series.Font.Size); 5362 fontSize = graph.GetRelativeSize( fontSize ); 5363 int maxNumOfRows = (int)( pieRectangle.Height / maxSizeVertical/*fontSize.Height*/ ); 5364 5365 // Initialize label column 5366 labelColumnRight.Initialize( pieRectangle, true, maxNumOfRows, labelLineSize ); 5367 labelColumnLeft.Initialize( pieRectangle, false, maxNumOfRows, labelLineSize ); 5368 5369 } 5370 5371 /// <summary> 5372 /// This method inserts outside 3D labels into array of Label column class. 5373 /// </summary> 5374 /// <param name="graph">Chart Graphics object.</param> 5375 /// <param name="area">Chart Area.</param> 5376 /// <param name="pieRectangle">Rectangle used for drawing pie slices.</param> 5377 /// <param name="pieWidth">Width of a pie slice.</param> 5378 /// <param name="point">Data Point.</param> 5379 /// <param name="startAngle">Start angle of a pie slice.</param> 5380 /// <param name="sweepAngle">Sweep angle of a pie slice.</param> 5381 /// <param name="pointIndx">Data point index.</param> 5382 /// <param name="doughnutRadius">Inner Radius of the doughnut.</param> 5383 /// <param name="exploded">true if pie slice is exploded.</param> FillPieLabelOutside( ChartGraphics graph, ChartArea area, RectangleF pieRectangle, float pieWidth, DataPoint point, float startAngle, float sweepAngle, int pointIndx, float doughnutRadius, bool exploded )5384 private void FillPieLabelOutside( 5385 ChartGraphics graph, 5386 ChartArea area, 5387 RectangleF pieRectangle, 5388 float pieWidth, 5389 DataPoint point, 5390 float startAngle, 5391 float sweepAngle, 5392 int pointIndx, 5393 float doughnutRadius, 5394 bool exploded 5395 ) 5396 { 5397 float midAngle = startAngle + sweepAngle / 2F; 5398 5399 PointF [] piePoints = GetPiePoints( graph, area, pieWidth, pieRectangle, startAngle, sweepAngle, false, doughnutRadius, exploded ); 5400 5401 float y = piePoints[(int)PiePoints.TopLabelLineout].Y; 5402 if( midAngle >= -90 && midAngle < 90 || midAngle >= 270 && midAngle < 450 ) 5403 { 5404 labelColumnRight.InsertLabel( point, y, pointIndx ); 5405 } 5406 else 5407 { 5408 labelColumnLeft.InsertLabel( point, y, pointIndx ); 5409 } 5410 } 5411 5412 /// <summary> 5413 /// This method draws outside labels with lines, which 5414 /// connect labels with pie slices. 5415 /// </summary> 5416 /// <param name="graph">Chart Graphics object</param> 5417 /// <param name="area">Chart Area</param> 5418 /// <param name="pen">Pen object</param> 5419 /// <param name="points">Important pie points</param> 5420 /// <param name="point">Data point</param> 5421 /// <param name="midAngle">Middle Angle for pie slice</param> 5422 /// <param name="pointIndex">Point Index.</param> Draw3DOutsideLabels( ChartGraphics graph, ChartArea area, Pen pen, PointF [] points, DataPoint point, float midAngle, int pointIndex)5423 private void Draw3DOutsideLabels( 5424 ChartGraphics graph, 5425 ChartArea area, 5426 Pen pen, 5427 PointF [] points, 5428 DataPoint point, 5429 float midAngle, 5430 int pointIndex) 5431 { 5432 // Take label text 5433 string text = GetLabelText( point ); 5434 if(text.Length == 0) 5435 { 5436 return; 5437 } 5438 5439 graph.DrawLine( pen, points[(int)PiePoints.TopLabelLine], points[(int)PiePoints.TopLabelLineout] ); 5440 LabelColumn columnLabel; 5441 5442 using (StringFormat format = new StringFormat()) 5443 { 5444 format.LineAlignment = StringAlignment.Center; 5445 5446 RectangleF chartAreaPosition = graph.GetAbsoluteRectangle(area.Position.ToRectangleF()); 5447 RectangleF labelPosition = RectangleF.Empty; 5448 5449 PointF labelPoint; 5450 5451 if (midAngle >= -90 && midAngle < 90 || midAngle >= 270 && midAngle < 450) 5452 { 5453 columnLabel = labelColumnRight; 5454 format.Alignment = StringAlignment.Near; 5455 5456 float labelVertSize = graph.GetAbsoluteSize(new SizeF(0f, this.labelColumnRight.columnHeight)).Height; 5457 labelPoint = graph.GetAbsolutePoint(columnLabel.GetLabelPosition(point)); 5458 5459 // Label has to be right from TopLabelLineOut 5460 if (points[(int)PiePoints.TopLabelLineout].X > labelPoint.X) 5461 { 5462 labelPoint.X = points[(int)PiePoints.TopLabelLineout].X + 10; 5463 } 5464 5465 labelPosition.X = labelPoint.X; 5466 labelPosition.Width = chartAreaPosition.Right - labelPosition.X; 5467 labelPosition.Y = labelPoint.Y - labelVertSize / 2; 5468 labelPosition.Height = labelVertSize; 5469 5470 } 5471 else 5472 { 5473 columnLabel = labelColumnLeft; 5474 format.Alignment = StringAlignment.Far; 5475 5476 float labelVertSize = graph.GetAbsoluteSize(new SizeF(0f, this.labelColumnLeft.columnHeight)).Height; 5477 labelPoint = graph.GetAbsolutePoint(columnLabel.GetLabelPosition(point)); 5478 5479 // Label has to be left from TopLabelLineOut 5480 if (points[(int)PiePoints.TopLabelLineout].X < labelPoint.X) 5481 { 5482 labelPoint.X = points[(int)PiePoints.TopLabelLineout].X - 10; 5483 } 5484 5485 labelPosition.X = chartAreaPosition.X; 5486 labelPosition.Width = labelPoint.X - labelPosition.X; 5487 labelPosition.Y = labelPoint.Y - labelVertSize / 2; 5488 labelPosition.Height = labelVertSize; 5489 } 5490 format.FormatFlags = StringFormatFlags.NoWrap | StringFormatFlags.LineLimit; 5491 format.Trimming = StringTrimming.EllipsisWord; 5492 5493 graph.DrawLine(pen, points[(int)PiePoints.TopLabelLineout], labelPoint); 5494 5495 // Get label relative position 5496 labelPosition = graph.GetRelativeRectangle(labelPosition); 5497 5498 // Get label background position 5499 SizeF valueTextSize = graph.MeasureStringRel(text.Replace("\\n", "\n"), point.Font); 5500 valueTextSize.Height += valueTextSize.Height / 8; 5501 float spacing = valueTextSize.Width / text.Length / 2; 5502 valueTextSize.Width += spacing; 5503 RectangleF labelBackPosition = new RectangleF( 5504 labelPosition.X, 5505 labelPosition.Y + labelPosition.Height / 2f - valueTextSize.Height / 2f, 5506 valueTextSize.Width, 5507 valueTextSize.Height); 5508 5509 // Adjust position based on alignment 5510 if (format.Alignment == StringAlignment.Near) 5511 { 5512 labelBackPosition.X -= spacing / 2f; 5513 } 5514 else if (format.Alignment == StringAlignment.Center) 5515 { 5516 labelBackPosition.X = labelPosition.X + (labelPosition.Width - valueTextSize.Width) / 2f; 5517 } 5518 else if (format.Alignment == StringAlignment.Far) 5519 { 5520 labelBackPosition.X = labelPosition.Right - valueTextSize.Width - spacing / 2f; 5521 } 5522 5523 // Draw label text 5524 using (Brush brush = new SolidBrush(point.LabelForeColor)) 5525 { 5526 graph.DrawPointLabelStringRel( 5527 graph.Common, 5528 text, 5529 point.Font, 5530 brush, 5531 labelPosition, 5532 format, 5533 0, 5534 labelBackPosition, 5535 point.LabelBackColor, 5536 point.LabelBorderColor, 5537 point.LabelBorderWidth, 5538 point.LabelBorderDashStyle, 5539 point.series, 5540 point, 5541 pointIndex); 5542 } 5543 } 5544 } 5545 5546 /// <summary> 5547 /// This method draws inside labels. 5548 /// </summary> 5549 /// <param name="graph">Chart Graphics object</param> 5550 /// <param name="points">Important pie points</param> 5551 /// <param name="point">Data point</param> 5552 /// <param name="pointIndex">Data point index</param> Draw3DInsideLabels( ChartGraphics graph, PointF [] points, DataPoint point, int pointIndex )5553 private void Draw3DInsideLabels( ChartGraphics graph, PointF [] points, DataPoint point, int pointIndex ) 5554 { 5555 // Set String Alignment 5556 StringFormat format = new StringFormat(); 5557 format.LineAlignment = StringAlignment.Center; 5558 format.Alignment = StringAlignment.Center; 5559 5560 // Take label text 5561 string text = GetLabelText( point ); 5562 5563 // Get label relative position 5564 PointF labelPosition = graph.GetRelativePoint(points[(int)PiePoints.TopLabelCenter]); 5565 5566 // Measure string 5567 SizeF sizeFont = graph.GetRelativeSize( 5568 graph.MeasureString( 5569 text.Replace("\\n", "\n"), 5570 point.Font, 5571 new SizeF(1000f, 1000f), 5572 new StringFormat(StringFormat.GenericTypographic))); 5573 5574 // Get label background position 5575 RectangleF labelBackPosition = RectangleF.Empty; 5576 SizeF sizeLabel = new SizeF(sizeFont.Width, sizeFont.Height); 5577 sizeLabel.Height += sizeFont.Height / 8; 5578 sizeLabel.Width += sizeLabel.Width / text.Length; 5579 labelBackPosition = new RectangleF( 5580 labelPosition.X - sizeLabel.Width/2, 5581 labelPosition.Y - sizeLabel.Height/2 - sizeFont.Height / 10, 5582 sizeLabel.Width, 5583 sizeLabel.Height); 5584 5585 // Draw label text 5586 using (Brush brush = new SolidBrush(point.LabelForeColor)) 5587 { 5588 graph.DrawPointLabelStringRel( 5589 graph.Common, 5590 text, 5591 point.Font, 5592 brush, 5593 labelPosition, 5594 format, 5595 0, 5596 labelBackPosition, 5597 point.LabelBackColor, 5598 point.LabelBorderColor, 5599 point.LabelBorderWidth, 5600 point.LabelBorderDashStyle, 5601 point.series, 5602 point, 5603 pointIndex); 5604 } 5605 } 5606 5607 /// <summary> 5608 /// Gets the point label. 5609 /// </summary> 5610 /// <param name="point">The point.</param> 5611 /// <returns></returns> GetPointLabel(DataPoint point)5612 private String GetPointLabel(DataPoint point) 5613 { 5614 String pointLabel = String.Empty; 5615 5616 // If There is no Label take axis Label 5617 if( point.Label.Length == 0 ) 5618 { 5619 pointLabel = point.AxisLabel; 5620 // remove axis label if is set the CustomPropertyName.PieAutoAxisLabels and is set to false 5621 if (point.series != null && 5622 point.series.IsCustomPropertySet(CustomPropertyName.PieAutoAxisLabels) && 5623 String.Equals(point.series.GetCustomProperty(CustomPropertyName.PieAutoAxisLabels), "false", StringComparison.OrdinalIgnoreCase)) 5624 { 5625 pointLabel = String.Empty; 5626 } 5627 } 5628 else 5629 pointLabel = point.Label; 5630 5631 return point.ReplaceKeywords(pointLabel); 5632 } 5633 5634 /// <summary> 5635 /// Take formated text from label or axis label 5636 /// </summary> 5637 /// <param name="point">Data point which is used.</param> 5638 /// <returns>Formated text</returns> GetLabelText( DataPoint point )5639 private string GetLabelText( DataPoint point ) 5640 { 5641 string pointLabel = this.GetPointLabel(point); 5642 // Get label text 5643 string text; 5644 if( point.Label.Length == 0 && point.IsValueShownAsLabel ) 5645 { 5646 text = ValueConverter.FormatValue( 5647 point.series.Chart, 5648 point, 5649 point.Tag, 5650 point.YValues[0], 5651 point.LabelFormat, 5652 point.series.YValueType, 5653 ChartElementType.DataPoint); 5654 } 5655 else 5656 { 5657 text = pointLabel; 5658 } 5659 5660 // Retuen formated label or axis label text 5661 return text; 5662 } 5663 5664 5665 #endregion 5666 5667 #region SmartLabelStyle methods 5668 5669 /// <summary> 5670 /// Adds markers position to the list. Used to check SmartLabelStyle overlapping. 5671 /// </summary> 5672 /// <param name="common">Common chart elements.</param> 5673 /// <param name="area">Chart area.</param> 5674 /// <param name="series">Series values to be used.</param> 5675 /// <param name="list">List to add to.</param> AddSmartLabelMarkerPositions(CommonElements common, ChartArea area, Series series, ArrayList list)5676 public void AddSmartLabelMarkerPositions(CommonElements common, ChartArea area, Series series, ArrayList list) 5677 { 5678 } 5679 5680 #endregion 5681 5682 #region IDisposable interface implementation 5683 /// <summary> 5684 /// Releases unmanaged and - optionally - managed resources 5685 /// </summary> 5686 /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> Dispose(bool disposing)5687 protected virtual void Dispose(bool disposing) 5688 { 5689 //Nothing to dispose at the base class. 5690 } 5691 5692 /// <summary> 5693 /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. 5694 /// </summary> Dispose()5695 public void Dispose() 5696 { 5697 Dispose(true); 5698 GC.SuppressFinalize(this); 5699 } 5700 #endregion 5701 } 5702 } 5703 5704