1 //------------------------------------------------------------- 2 // <copyright company=�Microsoft Corporation�> 3 // Copyright � Microsoft Corporation. All Rights Reserved. 4 // </copyright> 5 //------------------------------------------------------------- 6 // @owner=alexgor, deliant 7 //================================================================= 8 // File: LineChart.cs 9 // 10 // Namespace: DataVisualization.Charting.ChartTypes 11 // 12 // Classes: LineChart, SplineChart 13 // 14 // Purpose: Provides 2D/3D drawing and hit testing 15 // functionality for the Line and Spline charts. 16 // 17 // Reviewed: AG - August 6, 2002 18 // AG - Microsoft 6, 2007 19 // 20 //=================================================================== 21 22 #region Used namespaces 23 24 using System; 25 using System.Collections; 26 using System.Collections.Generic; 27 using System.Drawing; 28 using System.Drawing.Drawing2D; 29 30 #if Microsoft_CONTROL 31 using System.Windows.Forms.DataVisualization.Charting.Utilities; 32 #else 33 using System.Web.UI.DataVisualization.Charting; 34 using System.Web.UI.DataVisualization.Charting.Utilities; 35 #endif 36 37 #endregion 38 39 #if Microsoft_CONTROL 40 namespace System.Windows.Forms.DataVisualization.Charting.ChartTypes 41 #else 42 namespace System.Web.UI.DataVisualization.Charting.ChartTypes 43 #endif 44 { 45 /// <summary> 46 /// SplineChart class extends the LineChart class by 47 /// providing a different initial tension for the line. 48 /// </summary> 49 internal class SplineChart : LineChart 50 { 51 #region Constructor 52 53 /// <summary> 54 /// Default constructor. 55 /// </summary> SplineChart()56 public SplineChart() 57 { 58 // Set default line tension 59 base.lineTension = 0.5f; 60 } 61 62 #endregion 63 64 #region IChartType interface implementation 65 66 /// <summary> 67 /// Chart type name 68 /// </summary> 69 public override string Name { get{ return ChartTypeNames.Spline;}} 70 71 /// <summary> 72 /// Gets chart type image. 73 /// </summary> 74 /// <param name="registry">Chart types registry object.</param> 75 /// <returns>Chart type image.</returns> GetImage(ChartTypeRegistry registry)76 override public System.Drawing.Image GetImage(ChartTypeRegistry registry) 77 { 78 return (System.Drawing.Image)registry.ResourceManager.GetObject(this.Name + "ChartType"); 79 } 80 81 #endregion 82 83 #region Helper methods 84 85 /// <summary> 86 /// Checks if line tension is supported by the chart type. 87 /// </summary> 88 /// <returns>True if line tension is supported.</returns> IsLineTensionSupported()89 protected override bool IsLineTensionSupported() 90 { 91 return true; 92 } 93 94 /// <summary> 95 /// Fills a PointF array of data points positions. 96 /// </summary> 97 /// <param name="graph">Graphics object.</param> 98 /// <param name="series">Point series.</param> 99 /// <param name="indexedSeries">Indicate that point index should be used as X value.</param> 100 /// <returns>Array of data points position.</returns> GetPointsPosition( ChartGraphics graph, Series series, bool indexedSeries)101 override protected PointF[] GetPointsPosition( 102 ChartGraphics graph, 103 Series series, 104 bool indexedSeries) 105 { 106 // Check tension attribute in the series 107 base.lineTension = GetDefaultTension(); 108 if(IsLineTensionSupported() && series.IsCustomPropertySet(CustomPropertyName.LineTension)) 109 { 110 base.lineTension = CommonElements.ParseFloat(series[CustomPropertyName.LineTension]); 111 } 112 113 // Call base LineChart class 114 return base.GetPointsPosition(graph, series, indexedSeries); 115 } 116 117 /// <summary> 118 /// Gets default line tension. 119 /// </summary> 120 /// <returns>Default line tension.</returns> GetDefaultTension()121 override protected float GetDefaultTension() 122 { 123 return 0.5f; 124 } 125 126 #endregion 127 } 128 129 /// <summary> 130 /// LineChart class provides 2D/3D drawing and hit testing 131 /// functionality for the Line and Spline charts. The only 132 /// difference of the Spline chart is the default tension 133 /// of the line. 134 /// 135 /// PointChart base class provides functionality realted 136 /// to drawing labels and markers. 137 /// </summary> 138 internal class LineChart : PointChart 139 { 140 #region Fields and Constructor 141 142 /// <summary> 143 /// Line tension 144 /// </summary> 145 protected float lineTension = 0f; 146 147 /// <summary> 148 /// Index of the drawing center point. int.MaxValue if drawn from left->right or right->left. 149 /// </summary> 150 protected int centerPointIndex = int.MaxValue; 151 152 /// <summary> 153 /// Inicates that border color attribute must be used to draw the line 154 /// </summary> 155 protected bool useBorderColor = false; 156 157 /// <summary> 158 /// Inicates that line shadow should not be drawn 159 /// </summary> 160 protected bool disableShadow = false; 161 162 /// <summary> 163 /// Inicates that only line shadow must be drawn 164 /// </summary> 165 protected bool drawShadowOnly = false; 166 167 // Pen used to draw the line chart 168 private Pen _linePen = new Pen(Color.Black); 169 170 /// <summary> 171 /// Horizontal axis minimum value 172 /// </summary> 173 protected double hAxisMin = 0.0; 174 175 /// <summary> 176 /// Horizontal axis maximum value 177 /// </summary> 178 protected double hAxisMax = 0.0; 179 180 /// <summary> 181 /// Vertical axis minimum value 182 /// </summary> 183 protected double vAxisMin = 0.0; 184 185 /// <summary> 186 /// Vertical axis maximum value 187 /// </summary> 188 protected double vAxisMax = 0.0; 189 190 /// <summary> 191 /// Clip region indicator 192 /// </summary> 193 protected bool clipRegionSet = false; 194 195 /// <summary> 196 /// Indicates that several series are drawn at the same time. Stacked or Side-by-side. 197 /// </summary> 198 protected bool multiSeries = false; 199 200 /// <summary> 201 /// Indicates which coordinates should be tested against the COP. 202 /// </summary> 203 protected COPCoordinates COPCoordinatesToCheck = COPCoordinates.X; 204 205 /// <summary> 206 /// Number of data points loops required to draw chart. 207 /// </summary> 208 protected int allPointsLoopsNumber = 1; 209 210 /// <summary> 211 /// Indicates that line markers are shown at data point. 212 /// </summary> 213 protected bool showPointLines = false; 214 215 /// <summary> 216 /// Indicates that that lines outside the area should be still processed while drawing. 217 /// </summary> 218 protected bool drawOutsideLines = false; 219 220 221 /// <summary> 222 /// Indicates if base (point) chart type should be processed 223 /// </summary> 224 private bool _processBaseChart = false; 225 226 /// <summary> 227 /// Default constructor 228 /// </summary> LineChart()229 public LineChart() : base(false) 230 { 231 // Draw markers on the front edge 232 middleMarker = false; 233 } 234 235 #endregion 236 237 #region IChartType interface implementation 238 239 /// <summary> 240 /// Chart type name 241 /// </summary> 242 public override string Name { get{ return ChartTypeNames.Line;}} 243 244 /// <summary> 245 /// Gets chart type image. 246 /// </summary> 247 /// <param name="registry">Chart types registry object.</param> 248 /// <returns>Chart type image.</returns> GetImage(ChartTypeRegistry registry)249 override public System.Drawing.Image GetImage(ChartTypeRegistry registry) 250 { 251 return (System.Drawing.Image)registry.ResourceManager.GetObject(this.Name + "ChartType"); 252 } 253 254 /// <summary> 255 /// True if chart type is stacked 256 /// </summary> 257 public override bool Stacked { get{ return false;}} 258 259 /// <summary> 260 /// True if chart type supports axeses 261 /// </summary> 262 public override bool RequireAxes { get{ return true;} } 263 264 /// <summary> 265 /// True if chart type supports logarithmic axes 266 /// </summary> 267 public override bool SupportLogarithmicAxes { get{ return true;} } 268 269 /// <summary> 270 /// True if chart type requires to switch the value (Y) axes position 271 /// </summary> 272 public override bool SwitchValueAxes { get{ return false;} } 273 274 /// <summary> 275 /// True if chart series can be placed side-by-side. 276 /// </summary> 277 override public bool SideBySideSeries { get{ return false;} } 278 279 /// <summary> 280 /// If the crossing value is auto Crossing value should be 281 /// automatically set to zero for some chart 282 /// types (Bar, column, area etc.) 283 /// </summary> 284 override public bool ZeroCrossing { get{ return true;} } 285 286 /// <summary> 287 /// True if each data point of a chart must be represented in the legend 288 /// </summary> 289 public override bool DataPointsInLegend { get{ return false;} } 290 291 /// <summary> 292 /// True if palette colors should be applied for each data paoint. 293 /// Otherwise the color is applied to the series. 294 /// </summary> 295 public override bool ApplyPaletteColorsToPoints { get { return false; } } 296 297 /// <summary> 298 /// How to draw series/points in legend: 299 /// Filled rectangle, Line or Marker 300 /// </summary> 301 /// <param name="series">Legend item series.</param> 302 /// <returns>Legend item style.</returns> GetLegendImageStyle(Series series)303 override public LegendImageStyle GetLegendImageStyle(Series series) 304 { 305 return LegendImageStyle.Line; 306 } 307 308 /// <summary> 309 /// Number of supported Y value(s) per point 310 /// </summary> 311 public override int YValuesPerPoint{ get { return 1; } } 312 313 #endregion 314 315 #region Painting and selection methods 316 317 /// <summary> 318 /// Paint Line Chart. 319 /// </summary> 320 /// <param name="graph">The Chart Graphics object.</param> 321 /// <param name="common">The Common elements object.</param> 322 /// <param name="area">Chart area for this char.t</param> 323 /// <param name="seriesToDraw">Chart series to draw.</param> Paint( ChartGraphics graph, CommonElements common, ChartArea area, Series seriesToDraw )324 public override void Paint( ChartGraphics graph, CommonElements common, ChartArea area, Series seriesToDraw ) 325 { 326 // Save chart area reference 327 this.Area = area; 328 this.Common = common; 329 // Draw lines 330 _processBaseChart = false; 331 ProcessChartType( false, graph, common, area, seriesToDraw ); 332 333 // Draw labels and markers using base class PointChart 334 if(_processBaseChart) 335 { 336 base.ProcessChartType( false, graph, common, area, seriesToDraw ); 337 } 338 } 339 340 /// <summary> 341 /// Draws or perform the hit test for the line chart. 342 /// </summary> 343 /// <param name="selection">If True selection mode is active, otherwise paint mode is active.</param> 344 /// <param name="graph">The Chart Graphics object.</param> 345 /// <param name="common">The Common elements object.</param> 346 /// <param name="area">Chart area for this chart.</param> 347 /// <param name="seriesToDraw">Chart series to draw.</param> ProcessChartType( bool selection, ChartGraphics graph, CommonElements common, ChartArea area, Series seriesToDraw )348 protected override void ProcessChartType( 349 bool selection, 350 ChartGraphics graph, 351 CommonElements common, 352 ChartArea area, 353 Series seriesToDraw ) 354 { 355 this.Common = common; 356 // Prosess 3D chart type 357 if(area.Area3DStyle.Enable3D) 358 { 359 _processBaseChart = true; 360 ProcessLineChartType3D( selection, graph, common, area, seriesToDraw ); 361 return; 362 } 363 364 365 // All data series from chart area which have Bar chart type 366 List<string> typeSeries = area.GetSeriesFromChartType(this.Name); 367 368 // Check if series are indexed 369 bool indexedSeries = ChartHelper.IndexedSeries(this.Common, typeSeries.ToArray()); 370 371 //************************************************************ 372 //** Loop through all series 373 //************************************************************ 374 foreach( Series ser in common.DataManager.Series ) 375 { 376 // Process non empty series of the area with Line chart type 377 if( String.Compare( ser.ChartTypeName, this.Name, true, System.Globalization.CultureInfo.CurrentCulture ) != 0 378 || ser.ChartArea != area.Name || !ser.IsVisible()) 379 { 380 continue; 381 } 382 383 // Check if only 1 specified series must be processed 384 if (seriesToDraw != null && seriesToDraw.Name != ser.Name) 385 { 386 continue; 387 } 388 389 // Set active horizontal/vertical axis 390 HAxis = area.GetAxis(AxisName.X, ser.XAxisType, ser.XSubAxisName); 391 VAxis = area.GetAxis(AxisName.Y, ser.YAxisType, ser.YSubAxisName); 392 hAxisMin = HAxis.ViewMinimum; 393 hAxisMax = HAxis.ViewMaximum; 394 vAxisMin = VAxis.ViewMinimum; 395 vAxisMax = VAxis.ViewMaximum; 396 397 float chartWidthPercentage = (graph.Common.ChartPicture.Width - 1) / 100F; 398 float chartHeightPercentage = (graph.Common.ChartPicture.Height - 1) / 100F; 399 400 // Call Back Paint event 401 if( !selection ) 402 { 403 common.Chart.CallOnPrePaint(new ChartPaintEventArgs(ser, graph, common, area.PlotAreaPosition)); 404 } 405 406 // Check tension attribute in the series 407 this.lineTension = GetDefaultTension(); 408 if (IsLineTensionSupported() && ser.IsCustomPropertySet(CustomPropertyName.LineTension)) 409 { 410 this.lineTension = CommonElements.ParseFloat(ser[CustomPropertyName.LineTension]); 411 } 412 413 // Fill the array of data points coordinates (absolute) 414 bool dataPointPosFilled = false; 415 PointF[] dataPointPos = null; 416 if(this.lineTension == 0 && !common.ProcessModeRegions) 417 { 418 dataPointPos = new PointF[ser.Points.Count]; 419 } 420 else 421 { 422 dataPointPosFilled = true; 423 dataPointPos = GetPointsPosition(graph, ser, indexedSeries); 424 425 //************************************************************************* 426 //** Solution for the "Out of Memory" exception in the DrawCurve method 427 //** All points in the array should be at least 0.1 pixel apart. 428 //************************************************************************* 429 if(this.lineTension != 0) 430 { 431 float minDifference = 0.1f; 432 for(int pointIndex = 1; pointIndex < dataPointPos.Length; pointIndex++) 433 { 434 if( Math.Abs(dataPointPos[pointIndex - 1].X - dataPointPos[pointIndex].X ) < minDifference ) 435 { 436 if(dataPointPos[pointIndex].X > dataPointPos[pointIndex - 1].X) 437 { 438 dataPointPos[pointIndex].X = dataPointPos[pointIndex - 1].X + minDifference; 439 } 440 else 441 { 442 dataPointPos[pointIndex].X = dataPointPos[pointIndex - 1].X - minDifference; 443 } 444 } 445 if( Math.Abs(dataPointPos[pointIndex - 1].Y - dataPointPos[pointIndex].Y ) < minDifference ) 446 { 447 if(dataPointPos[pointIndex].Y > dataPointPos[pointIndex - 1].Y) 448 { 449 dataPointPos[pointIndex].Y = dataPointPos[pointIndex - 1].Y + minDifference; 450 } 451 else 452 { 453 dataPointPos[pointIndex].Y = dataPointPos[pointIndex - 1].Y - minDifference; 454 } 455 } 456 } 457 } 458 459 } 460 461 // Draw line if we have more than one data point 462 if(dataPointPos.Length > 1) 463 { 464 // Draw each data point 465 int index = 0; 466 DataPoint prevDataPoint = null; 467 double yValuePrev = 0.0; 468 double xValuePrev = 0.0; 469 bool showLabelAsValue = ser.IsValueShownAsLabel; 470 bool prevPointInArray = false; 471 foreach( DataPoint point in ser.Points ) 472 { 473 prevPointInArray = false; 474 475 // Reset pre-calculated point position 476 point.positionRel = new PointF(float.NaN, float.NaN); 477 478 //************************************************************ 479 //** Check if point marker or label is visible 480 //************************************************************ 481 if(!_processBaseChart) 482 { 483 string pointMarkerImage = point.MarkerImage; 484 MarkerStyle pointMarkerStyle = point.MarkerStyle; 485 486 if( alwaysDrawMarkers || 487 pointMarkerStyle != MarkerStyle.None || 488 pointMarkerImage.Length > 0 || 489 showLabelAsValue || 490 point.IsValueShownAsLabel || 491 point.Label.Length > 0 ) 492 { 493 _processBaseChart = true; 494 } 495 } 496 497 // Change Y value if line is out of plot area 498 double yValue = GetYValue(common, area, ser, point, index, this.YValueIndex); 499 500 // Recalculates x position 501 double xValue = (indexedSeries) ? index + 1 : point.XValue; 502 503 // If not first point 504 if(index != 0) 505 { 506 // Axes are logarithmic 507 yValue = VAxis.GetLogValue( yValue ); 508 xValue = HAxis.GetLogValue( xValue ); 509 510 // Check if line is completly out of the data scaleView 511 if( (xValue <= hAxisMin && xValuePrev < hAxisMin) || 512 (xValue >= hAxisMax && xValuePrev > hAxisMax) || 513 (yValue <= vAxisMin && yValuePrev < vAxisMin) || 514 (yValue >= vAxisMax && yValuePrev > vAxisMax) ) 515 { 516 if(!drawOutsideLines) 517 { 518 // Check if next point also outside of the scaleView and on the 519 // same side as current point. If not line has to be processed 520 // to correctly handle tooltips. 521 // NOTE: Fixes issue #4961 522 bool skipPoint = true; 523 if( common.ProcessModeRegions && 524 (index + 1) < ser.Points.Count) 525 { 526 DataPoint nextPoint = ser.Points[index + 1]; 527 528 // Recalculates x position 529 double xValueNext = (indexedSeries) ? index + 2 : nextPoint.XValue; 530 531 if( (xValue < hAxisMin && xValueNext > hAxisMin) || 532 (xValue > hAxisMax && xValueNext < hAxisMax) ) 533 { 534 skipPoint = false; 535 } 536 537 538 // Change Y value if line is out of plot area 539 if(skipPoint) 540 { 541 if( (yValue < vAxisMin && xValueNext > vAxisMin) || 542 (yValue > vAxisMax && xValueNext < vAxisMax) ) 543 { 544 skipPoint = false; 545 } 546 } 547 } 548 549 // Skip point 550 if(skipPoint) 551 { 552 ++index; 553 prevDataPoint = point; 554 yValuePrev = yValue; 555 xValuePrev = xValue; 556 continue; 557 } 558 } 559 } 560 561 // Check if line is partialy in the data scaleView 562 clipRegionSet = false; 563 if(this.lineTension != 0.0 || 564 xValuePrev < hAxisMin || xValuePrev > hAxisMax || 565 xValue > hAxisMax || xValue < hAxisMin || 566 yValuePrev < vAxisMin || yValuePrev > vAxisMax || 567 yValue < vAxisMin || yValue > vAxisMax ) 568 { 569 // Set clipping region for line drawing 570 graph.SetClip( area.PlotAreaPosition.ToRectangleF() ); 571 clipRegionSet = true; 572 } 573 574 575 if(this.lineTension == 0 && !dataPointPosFilled) 576 { 577 float yPosition = 0f; 578 float xPosition = 0f; 579 580 // Line reqires two points to draw 581 // Check if previous point is in the array 582 if(!prevPointInArray) 583 { 584 // Recalculates x/y position 585 yPosition = (float)VAxis.GetLinearPosition( yValuePrev ); 586 xPosition = (float)HAxis.GetLinearPosition( xValuePrev ); 587 588 // Add point position into array 589 // IMPORTANT: Rounding was removed from this part of code because of 590 // very bad drawing in Flash. 591 dataPointPos[index - 1] = new PointF( 592 xPosition * chartWidthPercentage, 593 yPosition * chartHeightPercentage); 594 } 595 596 597 // Recalculates x/y position 598 yPosition = (float)VAxis.GetLinearPosition( yValue ); 599 xPosition = (float)HAxis.GetLinearPosition( xValue ); 600 601 // Add point position into array 602 // IMPORTANT: Rounding was removed from this part of code because of 603 // very bad drawing in Flash. 604 dataPointPos[index] = new PointF( 605 xPosition * chartWidthPercentage, 606 yPosition * chartHeightPercentage); 607 608 prevPointInArray = true; 609 } 610 611 // Remeber pre-calculated point position 612 point.positionRel = graph.GetRelativePoint(dataPointPos[index]); 613 614 // Start Svg Selection mode 615 graph.StartHotRegion( point ); 616 617 if( index != 0 && prevDataPoint.IsEmpty ) 618 { 619 // IsEmpty data point - second line 620 DrawLine( 621 graph, 622 common, 623 prevDataPoint, 624 ser, 625 dataPointPos, 626 index, 627 this.lineTension); 628 } 629 else 630 { 631 // Regular data point and empty point - first line 632 DrawLine( 633 graph, 634 common, 635 point, 636 ser, 637 dataPointPos, 638 index, 639 this.lineTension); 640 } 641 642 // End Svg Selection mode 643 graph.EndHotRegion( ); 644 645 // Reset Clip Region 646 if(clipRegionSet) 647 { 648 graph.ResetClip(); 649 } 650 651 // Remember previous point data 652 prevDataPoint = point; 653 yValuePrev = yValue; 654 xValuePrev = xValue; 655 } 656 else 657 { 658 // Get Y values of the current and previous data points 659 prevDataPoint = point; 660 yValuePrev = GetYValue(common, area, ser, point, index, 0); 661 xValuePrev = (indexedSeries) ? index + 1 : point.XValue; 662 yValuePrev = VAxis.GetLogValue( yValuePrev ); 663 xValuePrev = HAxis.GetLogValue( xValuePrev ); 664 665 // Remeber pre-calculated point position 666 point.positionRel = new PointF( 667 (float)HAxis.GetPosition( xValuePrev ), 668 (float)VAxis.GetPosition( yValuePrev ) ); 669 } 670 671 // Process image map selection for the first point 672 if(index == 0) 673 { 674 DrawLine( 675 graph, 676 common, 677 point, 678 ser, 679 dataPointPos, 680 index, 681 this.lineTension); 682 } 683 684 // Increase data point index 685 ++index; 686 } 687 } 688 else if(dataPointPos.Length == 1 && 689 ser.Points.Count == 1) 690 { 691 //************************************************************ 692 //** Check if point marker or label is visible 693 //************************************************************ 694 if(!_processBaseChart) 695 { 696 if( alwaysDrawMarkers || 697 ser.Points[0].MarkerStyle != MarkerStyle.None || 698 ser.Points[0].MarkerImage.Length > 0 || 699 ser.IsValueShownAsLabel || 700 ser.Points[0].IsValueShownAsLabel || 701 ser.Points[0].Label.Length > 0 ) 702 { 703 _processBaseChart = true; 704 } 705 } 706 } 707 708 // Reset points array 709 dataPointPos = null; 710 711 // Call Paint event 712 if( !selection ) 713 { 714 common.Chart.CallOnPostPaint(new ChartPaintEventArgs(ser, graph, common, area.PlotAreaPosition)); 715 } 716 } 717 } 718 719 /// <summary> 720 /// Calculate position and draw one chart line and/or shadow. 721 /// </summary> 722 /// <param name="graph">Graphics object.</param> 723 /// <param name="common">The Common elements object.</param> 724 /// <param name="point">Point to draw the line for.</param> 725 /// <param name="series">Point series.</param> 726 /// <param name="points">Array of oints coordinates.</param> 727 /// <param name="pointIndex">Index of point to draw.</param> 728 /// <param name="tension">Line tension.</param> DrawLine( ChartGraphics graph, CommonElements common, DataPoint point, Series series, PointF[] points, int pointIndex, float tension)729 virtual protected void DrawLine( 730 ChartGraphics graph, 731 CommonElements common, 732 DataPoint point, 733 Series series, 734 PointF[] points, 735 int pointIndex, 736 float tension) 737 { 738 int pointBorderWidth = point.BorderWidth; 739 740 // **************************************************** 741 // Paint Mode 742 // **************************************************** 743 if( common.ProcessModePaint ) 744 { 745 // Start drawing from the second point 746 if(pointIndex > 0) 747 { 748 Color color = (useBorderColor) ? point.BorderColor : point.Color; 749 ChartDashStyle dashStyle = point.BorderDashStyle; 750 751 // Draw line shadow 752 if(!disableShadow && series.ShadowOffset != 0 && series.ShadowColor != Color.Empty) 753 { 754 if(color != Color.Empty && color != Color.Transparent && pointBorderWidth > 0 && dashStyle != ChartDashStyle.NotSet) 755 { 756 Pen shadowPen = new Pen((series.ShadowColor.A != 255) ? series.ShadowColor : Color.FromArgb((useBorderColor) ? point.BorderColor.A/2 : point.Color.A/2, series.ShadowColor), pointBorderWidth); 757 shadowPen.DashStyle = graph.GetPenStyle( point.BorderDashStyle ); 758 shadowPen.StartCap = LineCap.Round; 759 shadowPen.EndCap = LineCap.Round; 760 761 // Translate curve 762 GraphicsState graphicsState = graph.Save(); 763 Matrix transform = graph.Transform.Clone(); 764 transform.Translate(series.ShadowOffset, series.ShadowOffset); 765 graph.Transform = transform; 766 767 // Draw shadow 768 if(this.lineTension == 0) 769 { 770 try 771 { 772 graph.DrawLine(shadowPen, points[pointIndex - 1], points[pointIndex]); 773 } 774 catch (OverflowException) 775 { 776 this.DrawTruncatedLine(graph, shadowPen, points[pointIndex - 1], points[pointIndex]); 777 } 778 } 779 else 780 { 781 graph.DrawCurve(shadowPen, points, pointIndex - 1, 1, tension); 782 } 783 graph.Restore(graphicsState); 784 } 785 } 786 787 // If only shadow must be drawn - return 788 if(drawShadowOnly) 789 { 790 return; 791 } 792 793 //// IsEmpty data ` color and style 794 // DT - removed code - line chart will have MS Office behavior for empty points -> broken line. 795 //if( point.IsEmpty ) 796 //{ 797 // if( point.Color == Color.IsEmpty) 798 // { 799 // color = Color.Black; 800 // } 801 // if( point.BorderDashStyle == ChartDashStyle.NotSet ) 802 // { 803 // dashStyle = ChartDashStyle.Dash; 804 // } 805 //} 806 807 // Draw data point line 808 if(color != Color.Empty && pointBorderWidth > 0 && dashStyle != ChartDashStyle.NotSet) 809 { 810 if(_linePen.Color != color) 811 { 812 _linePen.Color = color; 813 } 814 if(_linePen.Width != pointBorderWidth) 815 { 816 _linePen.Width = pointBorderWidth; 817 } 818 if(_linePen.DashStyle != graph.GetPenStyle( dashStyle )) 819 { 820 _linePen.DashStyle = graph.GetPenStyle( dashStyle ); 821 } 822 823 // Set Rounded Cap 824 if(_linePen.StartCap != LineCap.Round) 825 _linePen.StartCap = LineCap.Round; 826 if(_linePen.EndCap != LineCap.Round) 827 _linePen.EndCap = LineCap.Round; 828 829 if(tension == 0) 830 { 831 // VSTS: 9698 - issue: the line start from X = 0 when GDI overflows (before we expected exception) 832 if (IsLinePointsOverflow(points[pointIndex - 1]) || IsLinePointsOverflow(points[pointIndex])) 833 { 834 this.DrawTruncatedLine(graph, _linePen, points[pointIndex - 1], points[pointIndex]); 835 } 836 else 837 { 838 try 839 { 840 graph.DrawLine(_linePen, points[pointIndex - 1], points[pointIndex]); 841 } 842 catch (OverflowException) 843 { 844 this.DrawTruncatedLine(graph, _linePen, points[pointIndex - 1], points[pointIndex]); 845 } 846 } 847 } 848 else 849 { 850 graph.DrawCurve(_linePen, points, pointIndex - 1, 1, tension); 851 } 852 } 853 } 854 } 855 856 //************************************************************ 857 // Hot Regions mode used for image maps, tool tips and 858 // hit test function 859 //************************************************************ 860 if( common.ProcessModeRegions ) 861 { 862 int width = pointBorderWidth + 2; 863 864 // Create grapics path object dor the curve 865 using (GraphicsPath path = new GraphicsPath()) 866 { 867 868 // If line tension is zero - it's a straight line 869 if (this.lineTension == 0) 870 { 871 // Add half line segment prior to the data point 872 if (pointIndex > 0) 873 { 874 PointF first = points[pointIndex - 1]; 875 PointF second = points[pointIndex]; 876 first.X = (first.X + second.X) / 2f; 877 first.Y = (first.Y + second.Y) / 2f; 878 879 if (Math.Abs(first.X - second.X) > Math.Abs(first.Y - second.Y)) 880 { 881 path.AddLine(first.X, first.Y - width, second.X, second.Y - width); 882 path.AddLine(second.X, second.Y + width, first.X, first.Y + width); 883 path.CloseAllFigures(); 884 } 885 else 886 { 887 path.AddLine(first.X - width, first.Y, second.X - width, second.Y); 888 path.AddLine(second.X + width, second.Y, first.X + width, first.Y); 889 path.CloseAllFigures(); 890 891 } 892 } 893 894 // Add half line segment after the data point 895 if (pointIndex + 1 < points.Length) 896 { 897 PointF first = points[pointIndex]; 898 PointF second = points[pointIndex + 1]; 899 second.X = (first.X + second.X) / 2f; 900 second.Y = (first.Y + second.Y) / 2f; 901 902 // Set a marker in the path to separate from the first line segment 903 if (pointIndex > 0) 904 { 905 path.SetMarkers(); 906 } 907 908 if (Math.Abs(first.X - second.X) > Math.Abs(first.Y - second.Y)) 909 { 910 path.AddLine(first.X, first.Y - width, second.X, second.Y - width); 911 path.AddLine(second.X, second.Y + width, first.X, first.Y + width); 912 path.CloseAllFigures(); 913 } 914 else 915 { 916 path.AddLine(first.X - width, first.Y, second.X - width, second.Y); 917 path.AddLine(second.X + width, second.Y, first.X + width, first.Y); 918 path.CloseAllFigures(); 919 } 920 } 921 922 } 923 else if (pointIndex > 0) 924 { 925 try 926 { 927 path.AddCurve(points, pointIndex - 1, 1, this.lineTension); 928 path.Widen(new Pen(point.Color, pointBorderWidth + 2)); 929 path.Flatten(); 930 } 931 catch (OutOfMemoryException) 932 { 933 // GraphicsPath.Widen incorrectly throws OutOfMemoryException 934 // catching here and reacting by not widening 935 } 936 catch (ArgumentException) 937 { 938 } 939 } 940 941 // Path is empty 942 if (path.PointCount == 0) 943 { 944 return; 945 } 946 947 // Allocate array of floats 948 PointF pointNew = PointF.Empty; 949 float[] coord = new float[path.PointCount * 2]; 950 PointF[] pathPoints = path.PathPoints; 951 for (int i = 0; i < path.PointCount; i++) 952 { 953 pointNew = graph.GetRelativePoint(pathPoints[i]); 954 coord[2 * i] = pointNew.X; 955 coord[2 * i + 1] = pointNew.Y; 956 } 957 958 common.HotRegionsList.AddHotRegion(path, false, coord, point, series.Name, pointIndex); 959 } 960 } 961 } 962 963 964 private const long maxGDIRange = 0x800000; 965 // VSTS: 9698 - issue: the line start from X = 0 when GDI overflows (before we expected exception) IsLinePointsOverflow(PointF point)966 private bool IsLinePointsOverflow(PointF point) 967 { 968 return point.X <= -maxGDIRange || point.X >= maxGDIRange || point.Y <= -maxGDIRange || point.Y >= maxGDIRange; 969 } 970 971 /// <summary> 972 /// During zooming there are scenarios when the line coordinates are extremly large and 973 /// originate outside of the chart pixel boundaries. This cause GDI+ line drawing methods 974 /// to throw stack overflow exceptions. 975 /// This method tries to change the coordinates into the chart boundaries and draw the line. 976 /// </summary> 977 /// <param name="graph">Chart graphics.</param> 978 /// <param name="pen">Pen object that determines the color, width, and style of the line.</param> 979 /// <param name="pt1">PointF structure that represents the first point to connect.</param> 980 /// <param name="pt2">PointF structure that represents the second point to connect.</param> DrawTruncatedLine(ChartGraphics graph, Pen pen, PointF pt1, PointF pt2)981 private void DrawTruncatedLine(ChartGraphics graph, Pen pen, PointF pt1, PointF pt2) 982 { 983 PointF adjustedPoint1 = PointF.Empty; 984 PointF adjustedPoint2 = PointF.Empty; 985 986 // Check line angle. Intersection with vertical or horizontal lines will be done based on the results 987 bool topBottomLine = (Math.Abs(pt2.Y - pt1.Y) > Math.Abs(pt2.X - pt1.X)); 988 RectangleF rect = new RectangleF(0, 0, graph.Common.ChartPicture.Width, graph.Common.ChartPicture.Height); 989 if (topBottomLine) 990 { 991 // Find the intersection point between the original line and Y = 0 and Y = Height lines 992 adjustedPoint1 = rect.Contains(pt1) ? pt1 : GetIntersectionY(pt1, pt2, 0); 993 adjustedPoint2 = rect.Contains(pt2) ? pt2 : GetIntersectionY(pt1, pt2, graph.Common.ChartPicture.Height); 994 } 995 else 996 { 997 // Find the intersection point between the original line and X = 0 and X = Width lines 998 adjustedPoint1 = rect.Contains(pt1) ? pt1 : GetIntersectionX(pt1, pt2, 0); 999 adjustedPoint2 = rect.Contains(pt2) ? pt2 : GetIntersectionX(pt1, pt2, graph.Common.ChartPicture.Width); 1000 } 1001 1002 // Draw Line 1003 graph.DrawLine(pen, adjustedPoint1, adjustedPoint2); 1004 } 1005 1006 /// <summary> 1007 /// Gets intersection point coordinates between point line and and horizontal 1008 /// line specified by Y coordinate. 1009 /// </summary> 1010 /// <param name="firstPoint">First data point.</param> 1011 /// <param name="secondPoint">Second data point.</param> 1012 /// <param name="pointY">Y coordinate.</param> 1013 /// <returns>Intersection point coordinates.</returns> GetIntersectionY(PointF firstPoint, PointF secondPoint, float pointY)1014 internal static PointF GetIntersectionY(PointF firstPoint, PointF secondPoint, float pointY) 1015 { 1016 PointF intersectionPoint = new PointF(); 1017 intersectionPoint.Y = pointY; 1018 intersectionPoint.X = (pointY - firstPoint.Y) * 1019 (secondPoint.X - firstPoint.X) / 1020 (secondPoint.Y - firstPoint.Y) + 1021 firstPoint.X; 1022 return intersectionPoint; 1023 } 1024 1025 /// <summary> 1026 /// Gets intersection point coordinates between point line and and vertical 1027 /// line specified by X coordinate. 1028 /// </summary> 1029 /// <param name="firstPoint">First data point.</param> 1030 /// <param name="secondPoint">Second data point.</param> 1031 /// <param name="pointX">X coordinate.</param> 1032 /// <returns>Intersection point coordinates.</returns> GetIntersectionX(PointF firstPoint, PointF secondPoint, float pointX)1033 internal static PointF GetIntersectionX(PointF firstPoint, PointF secondPoint, float pointX) 1034 { 1035 PointF intersectionPoint = new PointF(); 1036 intersectionPoint.X = pointX; 1037 intersectionPoint.Y = (pointX - firstPoint.X) * 1038 (secondPoint.Y - firstPoint.Y) / 1039 (secondPoint.X - firstPoint.X) + 1040 firstPoint.Y; 1041 return intersectionPoint; 1042 } 1043 1044 /// <summary> 1045 /// Draw chart line. 1046 /// </summary> 1047 /// <param name="graph">Graphics object.</param> 1048 /// <param name="point">Point to draw the line for.</param> 1049 /// <param name="series">Point series.</param> 1050 /// <param name="firstPoint">First line point.</param> 1051 /// <param name="secondPoint">Seconf line point.</param> DrawLine( ChartGraphics graph, DataPoint point, Series series, PointF firstPoint, PointF secondPoint)1052 protected void DrawLine( 1053 ChartGraphics graph, 1054 DataPoint point, 1055 Series series, 1056 PointF firstPoint, 1057 PointF secondPoint) 1058 { 1059 graph.DrawLineRel( point.Color, point.BorderWidth, point.BorderDashStyle, firstPoint, secondPoint, series.ShadowColor, series.ShadowOffset ); 1060 } 1061 1062 /// <summary> 1063 /// Checks if line tension is supported by the chart type. 1064 /// </summary> 1065 /// <returns>True if line tension is supported.</returns> IsLineTensionSupported()1066 protected virtual bool IsLineTensionSupported() 1067 { 1068 return false; 1069 } 1070 1071 #endregion 1072 1073 #region Position helper methods 1074 1075 /// <summary> 1076 /// Gets default line tension. 1077 /// </summary> 1078 /// <returns>Default line tension.</returns> GetDefaultTension()1079 virtual protected float GetDefaultTension() 1080 { 1081 return 0f; 1082 } 1083 1084 /// <summary> 1085 /// Gets label position depending on the prev/next point values. 1086 /// This method will reduce label overlapping with the chart itself (line). 1087 /// </summary> 1088 /// <param name="series">Data series.</param> 1089 /// <param name="pointIndex">Point index.</param> 1090 /// <returns>Return automaticly detected label position.</returns> GetAutoLabelPosition(Series series, int pointIndex)1091 override protected LabelAlignmentStyles GetAutoLabelPosition(Series series, int pointIndex) 1092 { 1093 int pointsCount = series.Points.Count; // Number of data points 1094 double previous; // Y Value from the previous data point 1095 double next; // Y Value from the next data point 1096 1097 // There is only one data point 1098 if( pointsCount == 1 ) 1099 { 1100 return LabelAlignmentStyles.Top; 1101 } 1102 1103 // Y Value from the current data point 1104 double current = GetYValue(Common, Area, series, series.Points[pointIndex], pointIndex, 0); 1105 1106 // The data point is between two data points 1107 if( pointIndex < pointsCount - 1 && pointIndex > 0 ) 1108 { 1109 // Y Value from the previous data point 1110 previous = GetYValue(Common, Area, series, series.Points[pointIndex-1], pointIndex-1, 0); 1111 1112 // Y Value from the next data point 1113 next = GetYValue(Common, Area, series, series.Points[pointIndex+1], pointIndex+1, 0); 1114 1115 // Put the label below lines 1116 if( previous > current && next > current ) 1117 { 1118 return LabelAlignmentStyles.Bottom; 1119 } 1120 } 1121 1122 // This is the last data point 1123 if( pointIndex == pointsCount - 1 ) 1124 { 1125 // Y Value from the previous data point 1126 previous = GetYValue(Common, Area, series, series.Points[pointIndex-1], pointIndex-1, 0); 1127 1128 // Put the label below line 1129 if( previous > current ) 1130 { 1131 return LabelAlignmentStyles.Bottom; 1132 } 1133 } 1134 1135 // This is the first data point 1136 if( pointIndex == 0 ) 1137 { 1138 // Y Value from the next data point 1139 next = GetYValue(Common, Area, series, series.Points[pointIndex + 1], pointIndex + 1, 0); 1140 1141 // Put the label below line 1142 if( next > current ) 1143 { 1144 return LabelAlignmentStyles.Bottom; 1145 } 1146 } 1147 1148 return LabelAlignmentStyles.Top; 1149 } 1150 1151 /// <summary> 1152 /// Fills a PointF array of data points absolute pixel positions. 1153 /// </summary> 1154 /// <param name="graph">Graphics object.</param> 1155 /// <param name="series">Point series.</param> 1156 /// <param name="indexedSeries">Indicate that point index should be used as X value.</param> 1157 /// <returns>Array of data points position.</returns> GetPointsPosition(ChartGraphics graph, Series series, bool indexedSeries)1158 virtual protected PointF[] GetPointsPosition(ChartGraphics graph, Series series, bool indexedSeries) 1159 { 1160 PointF[] pointPos = new PointF[series.Points.Count]; 1161 int index = 0; 1162 foreach( DataPoint point in series.Points ) 1163 { 1164 // Change Y value if line is out of plot area 1165 double yValue = GetYValue(Common, Area, series, point, index, this.YValueIndex); 1166 1167 // Recalculates y position 1168 double yPosition = VAxis.GetPosition( yValue ); 1169 1170 // Recalculates x position 1171 double xPosition = HAxis.GetPosition( point.XValue ); 1172 if( indexedSeries ) 1173 { 1174 xPosition = HAxis.GetPosition( index + 1 ); 1175 } 1176 1177 // Add point position into array 1178 // IMPORTANT: Rounding was removed from this part of code because of 1179 // very bad drawing in Flash. 1180 pointPos[index] = new PointF( 1181 (float)xPosition * (graph.Common.ChartPicture.Width - 1) / 100F, 1182 (float)yPosition * (graph.Common.ChartPicture.Height - 1) / 100F); 1183 1184 index++; 1185 } 1186 1187 return pointPos; 1188 } 1189 1190 #endregion 1191 1192 #region 3D Drawing and selection methods 1193 1194 /// <summary> 1195 /// Draws or perform the hit test for the line chart in 3D. 1196 /// </summary> 1197 /// <param name="selection">If True selection mode is active, otherwise paint mode is active.</param> 1198 /// <param name="graph">The Chart Graphics object.</param> 1199 /// <param name="common">The Common elements object.</param> 1200 /// <param name="area">Chart area for this chart.</param> 1201 /// <param name="seriesToDraw">Chart series to draw.</param> ProcessLineChartType3D( bool selection, ChartGraphics graph, CommonElements common, ChartArea area, Series seriesToDraw )1202 protected void ProcessLineChartType3D( 1203 bool selection, 1204 ChartGraphics graph, 1205 CommonElements common, 1206 ChartArea area, 1207 Series seriesToDraw ) 1208 { 1209 1210 // Reset graphics fields 1211 graph.frontLinePen = null; 1212 graph.frontLinePoint1 = PointF.Empty; 1213 graph.frontLinePoint2 = PointF.Empty; 1214 1215 // Get list of series to draw 1216 List<string> typeSeries = null; 1217 if( (area.Area3DStyle.IsClustered && this.SideBySideSeries) || 1218 this.Stacked) 1219 { 1220 // Draw all series of the same chart type 1221 typeSeries = area.GetSeriesFromChartType(Name); 1222 } 1223 else 1224 { 1225 // Draw just one chart series 1226 typeSeries = new List<string>(); 1227 typeSeries.Add(seriesToDraw.Name); 1228 } 1229 1230 //*************************************************************** 1231 //** Check that data points XValues. Must be sorted or set to 0. 1232 //*************************************************************** 1233 foreach(string seriesName in typeSeries) 1234 { 1235 // Get series object 1236 Series currentSeries = common.DataManager.Series[seriesName]; 1237 1238 // Do not check indexed series 1239 if(currentSeries.IsXValueIndexed) 1240 { 1241 continue; 1242 } 1243 1244 // Loop through all data points in the series 1245 bool allZeros = true; 1246 int order = int.MaxValue; // 0 - Ascending; 1 - Descending; 1247 double prevValue = double.NaN; 1248 foreach(DataPoint dp in currentSeries.Points) 1249 { 1250 // Check if X values were set (or all zeros) 1251 if(allZeros && dp.XValue == 0.0) 1252 { 1253 continue; 1254 } 1255 allZeros = false; 1256 1257 // Check X values order 1258 bool validOrder = true; 1259 if(!double.IsNaN(prevValue) && dp.XValue != prevValue) 1260 { 1261 // Determine sorting order 1262 if(order == int.MaxValue) 1263 { 1264 order = (dp.XValue > prevValue) ? 0 : 1; // 0 - Ascending; 1 - Descending; 1265 } 1266 1267 // Compare current X value with previous 1268 if(dp.XValue > prevValue && order == 1) 1269 { 1270 validOrder = false; 1271 } 1272 if(dp.XValue < prevValue && order == 0) 1273 { 1274 validOrder = false; 1275 } 1276 } 1277 1278 // Throw error exception 1279 if(!validOrder) 1280 { 1281 throw (new InvalidOperationException(SR.Exception3DChartPointsXValuesUnsorted)); 1282 } 1283 1284 // Remember previous value 1285 prevValue = dp.XValue; 1286 } 1287 } 1288 1289 //************************************************************ 1290 //** Get order of data points drawing 1291 //************************************************************ 1292 ArrayList dataPointDrawingOrder = area.GetDataPointDrawingOrder( 1293 typeSeries, 1294 this, 1295 selection, 1296 this.COPCoordinatesToCheck, 1297 null, 1298 0, 1299 false); 1300 1301 1302 //************************************************************ 1303 //** Get line tension attribute 1304 //************************************************************ 1305 this.lineTension = GetDefaultTension(); 1306 if(dataPointDrawingOrder.Count > 0) 1307 { 1308 Series firstSeries = firstSeries = ((DataPoint3D)dataPointDrawingOrder[0]).dataPoint.series; 1309 if(IsLineTensionSupported() && firstSeries.IsCustomPropertySet(CustomPropertyName.LineTension)) 1310 { 1311 this.lineTension = CommonElements.ParseFloat(firstSeries[CustomPropertyName.LineTension]); 1312 } 1313 } 1314 1315 //************************************************************ 1316 //** Check if second ALL points loop is required 1317 //************************************************************ 1318 allPointsLoopsNumber = GetPointLoopNumber(selection, dataPointDrawingOrder); 1319 1320 //************************************************************ 1321 //** Loop through all data poins (one or two times) 1322 //************************************************************ 1323 for(int pointsLoop = 0; pointsLoop < allPointsLoopsNumber; pointsLoop++) 1324 { 1325 int index = 0; 1326 this.centerPointIndex = int.MaxValue; 1327 foreach(object obj in dataPointDrawingOrder) 1328 { 1329 // Get point & series 1330 DataPoint3D pointEx = (DataPoint3D) obj; 1331 DataPoint point = pointEx.dataPoint; 1332 Series ser = point.series; 1333 1334 // Set active horizontal/vertical axis 1335 HAxis = area.GetAxis(AxisName.X, ser.XAxisType, ser.XSubAxisName); 1336 VAxis = area.GetAxis(AxisName.Y, ser.YAxisType, ser.YSubAxisName); 1337 hAxisMin = HAxis.ViewMinimum; 1338 hAxisMax = HAxis.ViewMaximum; 1339 vAxisMin = VAxis.ViewMinimum; 1340 vAxisMax = VAxis.ViewMaximum; 1341 1342 // First point is not drawn as a 3D line 1343 if(pointEx.index > 1) 1344 { 1345 //************************************************************ 1346 //** Get previous data point using the point index in the series 1347 //************************************************************ 1348 int pointArrayIndex = index; 1349 DataPoint3D prevDataPointEx = ChartGraphics.FindPointByIndex( 1350 dataPointDrawingOrder, 1351 pointEx.index - 1, 1352 (this.multiSeries) ? pointEx : null, 1353 ref pointArrayIndex); 1354 1355 //************************************************************ 1356 //** Painting mode 1357 //************************************************************ 1358 GraphicsPath rectPath = null; 1359 1360 // Get Y values of the current and previous data points 1361 double yValue = GetYValue(common, area, ser, pointEx.dataPoint, pointEx.index - 1, 0); 1362 double yValuePrev = GetYValue(common, area, ser, prevDataPointEx.dataPoint, prevDataPointEx.index - 1, 0); 1363 double xValue = (pointEx.indexedSeries) ? pointEx.index : pointEx.dataPoint.XValue; 1364 double xValuePrev = (prevDataPointEx.indexedSeries) ? prevDataPointEx.index : prevDataPointEx.dataPoint.XValue; 1365 1366 // Axes are logarithmic 1367 yValue = VAxis.GetLogValue( yValue ); 1368 yValuePrev = VAxis.GetLogValue( yValuePrev ); 1369 1370 xValue = HAxis.GetLogValue( xValue ); 1371 xValuePrev = HAxis.GetLogValue( xValuePrev ); 1372 1373 //************************************************************ 1374 //** Draw line 1375 //************************************************************ 1376 DataPoint3D pointAttr = (prevDataPointEx.dataPoint.IsEmpty) ? prevDataPointEx : pointEx; 1377 if(pointAttr.dataPoint.Color != Color.Empty) 1378 { 1379 // Detect if we need to get graphical path of drawn object 1380 DrawingOperationTypes drawingOperationType = DrawingOperationTypes.DrawElement; 1381 1382 if( common.ProcessModeRegions ) 1383 { 1384 drawingOperationType |= DrawingOperationTypes.CalcElementPath; 1385 } 1386 1387 // Check if point markers lines should be drawn 1388 this.showPointLines = false; 1389 if(pointAttr.dataPoint.IsCustomPropertySet(CustomPropertyName.ShowMarkerLines)) 1390 { 1391 if(String.Compare(pointAttr.dataPoint[CustomPropertyName.ShowMarkerLines], "TRUE", StringComparison.OrdinalIgnoreCase) == 0) 1392 { 1393 this.showPointLines = true; 1394 } 1395 } 1396 else 1397 { 1398 if(pointAttr.dataPoint.series.IsCustomPropertySet(CustomPropertyName.ShowMarkerLines)) 1399 { 1400 if (String.Compare(pointAttr.dataPoint.series[CustomPropertyName.ShowMarkerLines], "TRUE", StringComparison.OrdinalIgnoreCase) == 0) 1401 { 1402 this.showPointLines = true; 1403 } 1404 } 1405 } 1406 1407 // Start Svg Selection mode 1408 graph.StartHotRegion( point ); 1409 1410 // Draw line surface 1411 area.IterationCounter = 0; 1412 rectPath = Draw3DSurface( 1413 area, 1414 graph, 1415 area.matrix3D, 1416 area.Area3DStyle.LightStyle, 1417 prevDataPointEx, 1418 pointAttr.zPosition, 1419 pointAttr.depth, 1420 dataPointDrawingOrder, 1421 index, 1422 pointsLoop, 1423 lineTension, 1424 drawingOperationType, 1425 0f, 0f, 1426 new PointF(float.NaN, float.NaN), 1427 new PointF(float.NaN, float.NaN), 1428 false); 1429 1430 // End Svg Selection mode 1431 graph.EndHotRegion( ); 1432 } 1433 1434 //************************************************************ 1435 // Hot Regions mode used for image maps, tool tips and 1436 // hit test function 1437 //************************************************************ 1438 if( common.ProcessModeRegions && rectPath != null) 1439 { 1440 common.HotRegionsList.AddHotRegion( 1441 rectPath, 1442 false, 1443 graph, 1444 point, 1445 ser.Name, 1446 pointEx.index - 1 ); 1447 } 1448 if (rectPath != null) 1449 { 1450 rectPath.Dispose(); 1451 } 1452 } 1453 1454 // Increase point index 1455 ++index; 1456 } 1457 } 1458 } 1459 1460 /// <summary> 1461 /// Draws a 3D surface connecting the two specified points in 2D space. 1462 /// Used to draw Line based charts. 1463 /// </summary> 1464 /// <param name="area">Chart area reference.</param> 1465 /// <param name="graph">Chart graphics.</param> 1466 /// <param name="matrix">Coordinates transformation matrix.</param> 1467 /// <param name="lightStyle">LightStyle style (None, Simplistic, Realistic).</param> 1468 /// <param name="prevDataPointEx">Previous data point object.</param> 1469 /// <param name="positionZ">Z position of the back side of the 3D surface.</param> 1470 /// <param name="depth">Depth of the 3D surface.</param> 1471 /// <param name="points">Array of points.</param> 1472 /// <param name="pointIndex">Index of point to draw.</param> 1473 /// <param name="pointLoopIndex">Index of points loop.</param> 1474 /// <param name="tension">Line tension.</param> 1475 /// <param name="operationType">AxisName of operation Drawing, Calculating Path or Both</param> 1476 /// <param name="topDarkening">Darkenning scale for top surface. 0 - None.</param> 1477 /// <param name="bottomDarkening">Darkenning scale for bottom surface. 0 - None.</param> 1478 /// <param name="thirdPointPosition">Position where the third point is actually located or float.NaN if same as in "firstPoint".</param> 1479 /// <param name="fourthPointPosition">Position where the fourth point is actually located or float.NaN if same as in "secondPoint".</param> 1480 /// <param name="clippedSegment">Indicates that drawn segment is 3D clipped. Only top/bottom should be drawn.</param> 1481 /// <returns>Returns elemnt shape path if operationType parameter is set to CalcElementPath, otherwise Null.</returns> Draw3DSurface( ChartArea area, ChartGraphics graph, Matrix3D matrix, LightStyle lightStyle, DataPoint3D prevDataPointEx, float positionZ, float depth, ArrayList points, int pointIndex, int pointLoopIndex, float tension, DrawingOperationTypes operationType, float topDarkening, float bottomDarkening, PointF thirdPointPosition, PointF fourthPointPosition, bool clippedSegment)1482 protected virtual GraphicsPath Draw3DSurface( 1483 ChartArea area, 1484 ChartGraphics graph, 1485 Matrix3D matrix, 1486 LightStyle lightStyle, 1487 DataPoint3D prevDataPointEx, 1488 float positionZ, 1489 float depth, 1490 ArrayList points, 1491 int pointIndex, 1492 int pointLoopIndex, 1493 float tension, 1494 DrawingOperationTypes operationType, 1495 float topDarkening, 1496 float bottomDarkening, 1497 PointF thirdPointPosition, 1498 PointF fourthPointPosition, 1499 bool clippedSegment) 1500 { 1501 // Check if points are drawn from sides to center (do only once) 1502 if(centerPointIndex == int.MaxValue) 1503 { 1504 centerPointIndex = GetCenterPointIndex(points); 1505 } 1506 1507 //************************************************************ 1508 //** Find line first & second points 1509 //************************************************************ 1510 DataPoint3D secondPoint = (DataPoint3D)points[pointIndex]; 1511 int pointArrayIndex = pointIndex; 1512 DataPoint3D firstPoint = ChartGraphics.FindPointByIndex( 1513 points, 1514 secondPoint.index - 1, 1515 (this.multiSeries) ? secondPoint : null, 1516 ref pointArrayIndex); 1517 1518 1519 // Fint point with line properties 1520 DataPoint3D pointAttr = secondPoint; 1521 if(prevDataPointEx.dataPoint.IsEmpty) 1522 { 1523 pointAttr = prevDataPointEx; 1524 } 1525 else if(firstPoint.index > secondPoint.index) 1526 { 1527 pointAttr = firstPoint; 1528 } 1529 1530 // Adjust point visual properties 1531 Color color = (useBorderColor) ? pointAttr.dataPoint.BorderColor : pointAttr.dataPoint.Color; 1532 ChartDashStyle dashStyle = pointAttr.dataPoint.BorderDashStyle; 1533 if( pointAttr.dataPoint.IsEmpty && pointAttr.dataPoint.Color == Color.Empty) 1534 { 1535 color = Color.Gray; 1536 } 1537 if( pointAttr.dataPoint.IsEmpty && pointAttr.dataPoint.BorderDashStyle == ChartDashStyle.NotSet ) 1538 { 1539 dashStyle = ChartDashStyle.Solid; 1540 } 1541 1542 // Draw point using 2 points 1543 return graph.Draw3DSurface( 1544 area, 1545 matrix, 1546 lightStyle, 1547 SurfaceNames.Top, 1548 positionZ, 1549 depth, 1550 color, 1551 pointAttr.dataPoint.BorderColor, 1552 pointAttr.dataPoint.BorderWidth, 1553 dashStyle, 1554 firstPoint, 1555 secondPoint, 1556 points, 1557 pointIndex, 1558 tension, 1559 operationType, 1560 LineSegmentType.Single, 1561 (this.showPointLines) ? true : false, 1562 false, 1563 area.ReverseSeriesOrder, 1564 this.multiSeries, 1565 0, 1566 true); 1567 } 1568 1569 /// <summary> 1570 /// Gets index of center point. 1571 /// </summary> 1572 /// <param name="points">Points list.</param> 1573 /// <returns>Index of center point or int.MaxValue.</returns> GetCenterPointIndex(ArrayList points)1574 protected int GetCenterPointIndex(ArrayList points) 1575 { 1576 for(int pointIndex = 1; pointIndex < points.Count; pointIndex++) 1577 { 1578 DataPoint3D firstPoint = (DataPoint3D)points[pointIndex - 1]; 1579 DataPoint3D secondPoint = (DataPoint3D)points[pointIndex]; 1580 if(Math.Abs(secondPoint.index - firstPoint.index) != 1) 1581 { 1582 return pointIndex - 1; 1583 } 1584 } 1585 return int.MaxValue; 1586 } 1587 1588 /// <summary> 1589 /// Returns how many loops through all data points is required (1 or 2) 1590 /// </summary> 1591 /// <param name="selection">Selection indicator.</param> 1592 /// <param name="pointsArray">Points array list.</param> 1593 /// <returns>Number of loops (1 or 2).</returns> GetPointLoopNumber(bool selection, ArrayList pointsArray)1594 virtual protected int GetPointLoopNumber(bool selection, ArrayList pointsArray) 1595 { 1596 return 1; 1597 } 1598 1599 /// <summary> 1600 /// Clips the top (left and right) points of the segment to plotting area. 1601 /// Used in area and range charts. 1602 /// </summary> 1603 /// <param name="resultPath">Segment area path.</param> 1604 /// <param name="firstPoint">First data point.</param> 1605 /// <param name="secondPoint">Second data point.</param> 1606 /// <param name="reversed">Points are in reversed order.</param> 1607 /// <param name="area">Chart area reference.</param> 1608 /// <param name="graph">Chart graphics.</param> 1609 /// <param name="matrix">Coordinates transformation matrix.</param> 1610 /// <param name="lightStyle">LightStyle style (None, Simplistic, Realistic).</param> 1611 /// <param name="prevDataPointEx">Previous data point object.</param> 1612 /// <param name="positionZ">Z position of the back side of the 3D surface.</param> 1613 /// <param name="depth">Depth of the 3D surface.</param> 1614 /// <param name="points">Array of points.</param> 1615 /// <param name="pointIndex">Index of point to draw.</param> 1616 /// <param name="pointLoopIndex">Index of points loop.</param> 1617 /// <param name="tension">Line tension.</param> 1618 /// <param name="operationType">AxisName of operation Drawing, Calculating Path or Both</param> 1619 /// <param name="surfaceSegmentType">Define surface segment type if it consists of several segments.</param> 1620 /// <param name="topDarkening">Darkenning scale for top surface. 0 - None.</param> 1621 /// <param name="bottomDarkening">Darkenning scale for bottom surface. 0 - None.</param> 1622 /// <returns>Returns element shape path if operationType parameter is set to CalcElementPath, otherwise Null.</returns> ClipTopPoints( GraphicsPath resultPath, ref DataPoint3D firstPoint, ref DataPoint3D secondPoint, bool reversed, ChartArea area, ChartGraphics graph, Matrix3D matrix, LightStyle lightStyle, DataPoint3D prevDataPointEx, float positionZ, float depth, ArrayList points, int pointIndex, int pointLoopIndex, float tension, DrawingOperationTypes operationType, LineSegmentType surfaceSegmentType, float topDarkening, float bottomDarkening)1623 protected bool ClipTopPoints( 1624 GraphicsPath resultPath, 1625 ref DataPoint3D firstPoint, 1626 ref DataPoint3D secondPoint, 1627 bool reversed, 1628 ChartArea area, 1629 ChartGraphics graph, 1630 Matrix3D matrix, 1631 LightStyle lightStyle, 1632 DataPoint3D prevDataPointEx, 1633 float positionZ, 1634 float depth, 1635 ArrayList points, 1636 int pointIndex, 1637 int pointLoopIndex, 1638 float tension, 1639 DrawingOperationTypes operationType, 1640 LineSegmentType surfaceSegmentType, 1641 float topDarkening, 1642 float bottomDarkening) 1643 { 1644 // Do not allow recursion to go too deep 1645 ++area.IterationCounter; 1646 if(area.IterationCounter > 20) 1647 { 1648 area.IterationCounter = 0; 1649 return true; 1650 } 1651 1652 //**************************************************************** 1653 //** Check point values 1654 //**************************************************************** 1655 if( double.IsNaN(firstPoint.xPosition) || 1656 double.IsNaN(firstPoint.yPosition) || 1657 double.IsNaN(secondPoint.xPosition) || 1658 double.IsNaN(secondPoint.yPosition) ) 1659 { 1660 return true; 1661 } 1662 1663 //**************************************************************** 1664 //** Round plot are position and point coordinates 1665 //**************************************************************** 1666 int decimals = 3; 1667 decimal plotAreaPositionX = Math.Round((decimal)area.PlotAreaPosition.X, decimals); 1668 decimal plotAreaPositionY = Math.Round((decimal)area.PlotAreaPosition.Y, decimals); 1669 decimal plotAreaPositionRight = Math.Round((decimal)area.PlotAreaPosition.Right, decimals); 1670 decimal plotAreaPositionBottom = Math.Round((decimal)area.PlotAreaPosition.Bottom, decimals); 1671 1672 // Make area a little bit bigger 1673 plotAreaPositionX -= 0.001M; 1674 plotAreaPositionY -= 0.001M; 1675 plotAreaPositionRight += 0.001M; 1676 plotAreaPositionBottom += 0.001M; 1677 1678 // Round top points coordinates 1679 firstPoint.xPosition = Math.Round(firstPoint.xPosition, decimals); 1680 firstPoint.yPosition = Math.Round(firstPoint.yPosition, decimals); 1681 secondPoint.xPosition = Math.Round(secondPoint.xPosition, decimals); 1682 secondPoint.yPosition = Math.Round(secondPoint.yPosition, decimals); 1683 1684 1685 //**************************************************************** 1686 //** Clip area data points inside the plotting area 1687 //**************************************************************** 1688 1689 // Chech data points X values 1690 if((decimal)firstPoint.xPosition < plotAreaPositionX || 1691 (decimal)firstPoint.xPosition > plotAreaPositionRight || 1692 (decimal)secondPoint.xPosition < plotAreaPositionX || 1693 (decimal)secondPoint.xPosition > plotAreaPositionRight ) 1694 { 1695 // Check if surface completly out of the plot area 1696 if((decimal)firstPoint.xPosition < plotAreaPositionX && 1697 (decimal)secondPoint.xPosition < plotAreaPositionX) 1698 { 1699 return true; 1700 } 1701 // Check if surface completly out of the plot area 1702 if((decimal)firstPoint.xPosition > plotAreaPositionRight && 1703 (decimal)secondPoint.xPosition > plotAreaPositionRight) 1704 { 1705 return true; 1706 } 1707 1708 // Only part of the surface is outside - fix X value and adjust Y value 1709 if((decimal)firstPoint.xPosition < plotAreaPositionX) 1710 { 1711 firstPoint.yPosition = ((double)plotAreaPositionX - secondPoint.xPosition) / 1712 (firstPoint.xPosition - secondPoint.xPosition) * 1713 (firstPoint.yPosition - secondPoint.yPosition) + 1714 secondPoint.yPosition; 1715 firstPoint.xPosition = (double)plotAreaPositionX; 1716 } 1717 else if((decimal)firstPoint.xPosition > plotAreaPositionRight) 1718 { 1719 firstPoint.yPosition = ((double)plotAreaPositionRight - secondPoint.xPosition) / 1720 (firstPoint.xPosition - secondPoint.xPosition) * 1721 (firstPoint.yPosition - secondPoint.yPosition) + 1722 secondPoint.yPosition; 1723 firstPoint.xPosition = (double)plotAreaPositionRight; 1724 } 1725 if((decimal)secondPoint.xPosition < plotAreaPositionX) 1726 { 1727 secondPoint.yPosition = ((double)plotAreaPositionX - secondPoint.xPosition) / 1728 (firstPoint.xPosition - secondPoint.xPosition) * 1729 (firstPoint.yPosition - secondPoint.yPosition) + 1730 secondPoint.yPosition; 1731 secondPoint.xPosition = (double)plotAreaPositionX; 1732 } 1733 else if((decimal)secondPoint.xPosition > plotAreaPositionRight) 1734 { 1735 secondPoint.yPosition = ((double)plotAreaPositionRight - secondPoint.xPosition) / 1736 (firstPoint.xPosition - secondPoint.xPosition) * 1737 (firstPoint.yPosition - secondPoint.yPosition) + 1738 secondPoint.yPosition; 1739 secondPoint.xPosition = (double)plotAreaPositionRight; 1740 } 1741 } 1742 1743 // Chech data points Y values 1744 if((decimal)firstPoint.yPosition < plotAreaPositionY || 1745 (decimal)firstPoint.yPosition > plotAreaPositionBottom || 1746 (decimal)secondPoint.yPosition < plotAreaPositionY || 1747 (decimal)secondPoint.yPosition > plotAreaPositionBottom ) 1748 { 1749 // Remember previous y positions 1750 double prevFirstPointY = firstPoint.yPosition; 1751 double prevSecondPointY = secondPoint.yPosition; 1752 1753 // Check if whole line is outside plotting region 1754 bool surfaceCompletlyOutside = false; 1755 bool outsideBottom = false; 1756 if((decimal)firstPoint.yPosition < plotAreaPositionY && 1757 (decimal)secondPoint.yPosition < plotAreaPositionY) 1758 { 1759 surfaceCompletlyOutside = true; 1760 firstPoint.yPosition = (double)plotAreaPositionY; 1761 secondPoint.yPosition = (double)plotAreaPositionY; 1762 } 1763 if((decimal)firstPoint.yPosition > plotAreaPositionBottom && 1764 (decimal)secondPoint.yPosition > plotAreaPositionBottom) 1765 { 1766 surfaceCompletlyOutside = true; 1767 outsideBottom = true; 1768 firstPoint.yPosition = (double)plotAreaPositionBottom; 1769 secondPoint.yPosition = (double)plotAreaPositionBottom; 1770 } 1771 1772 // Draw just one surface 1773 if(surfaceCompletlyOutside) 1774 { 1775 resultPath = Draw3DSurface( firstPoint, secondPoint, reversed, 1776 area, graph, matrix, lightStyle, prevDataPointEx, 1777 positionZ, depth, points, pointIndex, pointLoopIndex, 1778 tension, operationType, surfaceSegmentType, 1779 0.5f, 0f, 1780 new PointF(float.NaN, float.NaN), 1781 new PointF(float.NaN, float.NaN), 1782 outsideBottom, 1783 false, true); 1784 1785 // Restore previous y positions 1786 firstPoint.yPosition = prevFirstPointY; 1787 secondPoint.yPosition = prevSecondPointY; 1788 1789 return true; 1790 } 1791 1792 // Get intersection point 1793 DataPoint3D intersectionPoint = new DataPoint3D(); 1794 intersectionPoint.yPosition = (double)plotAreaPositionY; 1795 if((decimal)firstPoint.yPosition > plotAreaPositionBottom || 1796 (decimal)secondPoint.yPosition > plotAreaPositionBottom ) 1797 { 1798 intersectionPoint.yPosition = (double)plotAreaPositionBottom; 1799 } 1800 intersectionPoint.xPosition = (intersectionPoint.yPosition - secondPoint.yPosition) * 1801 (firstPoint.xPosition - secondPoint.xPosition) / 1802 (firstPoint.yPosition - secondPoint.yPosition) + 1803 secondPoint.xPosition; 1804 1805 if(double.IsNaN(intersectionPoint.xPosition) || 1806 double.IsInfinity(intersectionPoint.xPosition) || 1807 double.IsNaN(intersectionPoint.yPosition) || 1808 double.IsInfinity(intersectionPoint.yPosition) ) 1809 { 1810 return true; 1811 } 1812 1813 // Check if there are 2 intersection points (3 segments) 1814 int segmentNumber = 2; 1815 DataPoint3D intersectionPoint2 = null; 1816 if( ((decimal)firstPoint.yPosition < plotAreaPositionY && 1817 (decimal)secondPoint.yPosition > plotAreaPositionBottom) || 1818 ((decimal)firstPoint.yPosition > plotAreaPositionBottom && 1819 (decimal)secondPoint.yPosition < plotAreaPositionY)) 1820 { 1821 segmentNumber = 3; 1822 intersectionPoint2 = new DataPoint3D(); 1823 if((decimal)intersectionPoint.yPosition == plotAreaPositionY) 1824 { 1825 intersectionPoint2.yPosition = (double)plotAreaPositionBottom; 1826 } 1827 else 1828 { 1829 intersectionPoint2.yPosition = (double)plotAreaPositionY; 1830 } 1831 intersectionPoint2.xPosition = (intersectionPoint2.yPosition - secondPoint.yPosition) * 1832 (firstPoint.xPosition - secondPoint.xPosition) / 1833 (firstPoint.yPosition - secondPoint.yPosition) + 1834 secondPoint.xPosition; 1835 1836 if(double.IsNaN(intersectionPoint2.xPosition) || 1837 double.IsInfinity(intersectionPoint2.xPosition) || 1838 double.IsNaN(intersectionPoint2.yPosition) || 1839 double.IsInfinity(intersectionPoint2.yPosition) ) 1840 { 1841 return true; 1842 } 1843 1844 // Switch intersection points 1845 if((decimal)firstPoint.yPosition > plotAreaPositionBottom) 1846 { 1847 DataPoint3D tempPoint = new DataPoint3D(); 1848 tempPoint.xPosition = intersectionPoint.xPosition; 1849 tempPoint.yPosition = intersectionPoint.yPosition; 1850 intersectionPoint.xPosition = intersectionPoint2.xPosition; 1851 intersectionPoint.yPosition = intersectionPoint2.yPosition; 1852 intersectionPoint2.xPosition = tempPoint.xPosition; 1853 intersectionPoint2.yPosition = tempPoint.yPosition; 1854 } 1855 } 1856 1857 1858 // Adjust points Y values 1859 bool firstSegmentVisible = true; 1860 bool firstSegmentOutsideBottom = false; 1861 bool secondSegmentOutsideBottom = false; 1862 if((decimal)firstPoint.yPosition < plotAreaPositionY) 1863 { 1864 firstSegmentVisible = false; 1865 firstPoint.yPosition = (double)plotAreaPositionY; 1866 } 1867 else if((decimal)firstPoint.yPosition > plotAreaPositionBottom) 1868 { 1869 firstSegmentOutsideBottom = true; 1870 firstSegmentVisible = false; 1871 firstPoint.yPosition = (double)plotAreaPositionBottom; 1872 } 1873 if((decimal)secondPoint.yPosition < plotAreaPositionY) 1874 { 1875 secondPoint.yPosition = (double)plotAreaPositionY; 1876 } 1877 else if((decimal)secondPoint.yPosition > plotAreaPositionBottom) 1878 { 1879 secondSegmentOutsideBottom = true; 1880 secondPoint.yPosition = (double)plotAreaPositionBottom; 1881 } 1882 1883 // Draw surfaces in 2 or 3 segments 1884 for(int segmentIndex = 0; segmentIndex < 3; segmentIndex++) 1885 { 1886 GraphicsPath segmentPath = null; 1887 if(segmentIndex == 0 && !reversed || 1888 segmentIndex == 2 && reversed) 1889 { 1890 // Draw first segment 1891 if(intersectionPoint2 == null) 1892 { 1893 intersectionPoint2 = intersectionPoint; 1894 } 1895 intersectionPoint2.dataPoint = secondPoint.dataPoint; 1896 intersectionPoint2.index = secondPoint.index; 1897 intersectionPoint2.xCenterVal = secondPoint.xCenterVal; 1898 1899 segmentPath = Draw3DSurface( firstPoint, intersectionPoint2, reversed, 1900 area, graph, matrix, lightStyle, prevDataPointEx, 1901 positionZ, depth, points, pointIndex, pointLoopIndex, 1902 tension, operationType, 1903 (surfaceSegmentType == LineSegmentType.Middle) ? LineSegmentType.Middle : LineSegmentType.First, 1904 (firstSegmentVisible && segmentNumber != 3) ? 0f : 0.5f, 0f, 1905 new PointF(float.NaN, float.NaN), 1906 new PointF((float)intersectionPoint2.xPosition, float.NaN), 1907 firstSegmentOutsideBottom, 1908 false, true); 1909 1910 } 1911 1912 if(segmentIndex == 1 && intersectionPoint2 != null && segmentNumber == 3) 1913 { 1914 // Draw middle segment 1915 intersectionPoint2.dataPoint = secondPoint.dataPoint; 1916 intersectionPoint2.index = secondPoint.index; 1917 intersectionPoint2.xCenterVal = secondPoint.xCenterVal; 1918 1919 intersectionPoint.xCenterVal = firstPoint.xCenterVal; 1920 intersectionPoint.index = firstPoint.index; 1921 intersectionPoint.dataPoint = firstPoint.dataPoint; 1922 1923 segmentPath = Draw3DSurface( intersectionPoint, intersectionPoint2, reversed, 1924 area, graph, matrix, lightStyle, prevDataPointEx, 1925 positionZ, depth, points, pointIndex, pointLoopIndex, 1926 tension, operationType, LineSegmentType.Middle, 1927 topDarkening, bottomDarkening, 1928 new PointF((float)intersectionPoint.xPosition, float.NaN), 1929 new PointF((float)intersectionPoint2.xPosition, float.NaN), 1930 false, 1931 false, true); 1932 1933 } 1934 1935 if(segmentIndex == 2 && !reversed || 1936 segmentIndex == 0 && reversed) 1937 { 1938 // Draw second segment 1939 intersectionPoint.dataPoint = firstPoint.dataPoint; 1940 intersectionPoint.index = firstPoint.index; 1941 intersectionPoint.xCenterVal = firstPoint.xCenterVal; 1942 1943 segmentPath = Draw3DSurface( intersectionPoint, secondPoint, reversed, 1944 area, graph, matrix, lightStyle, prevDataPointEx, 1945 positionZ, depth, points, pointIndex, pointLoopIndex, 1946 tension, operationType, 1947 (surfaceSegmentType == LineSegmentType.Middle) ? LineSegmentType.Middle : LineSegmentType.Last, 1948 (!firstSegmentVisible && segmentNumber != 3) ? 0f : 0.5f, 0f, 1949 new PointF((float)intersectionPoint.xPosition, float.NaN), 1950 new PointF(float.NaN, float.NaN), 1951 secondSegmentOutsideBottom, 1952 false, true); 1953 1954 } 1955 1956 // Add segment path 1957 if(resultPath != null && segmentPath != null && segmentPath.PointCount > 0) 1958 { 1959 resultPath.AddPath(segmentPath, true); 1960 } 1961 } 1962 1963 // Restore previous y positions 1964 firstPoint.yPosition = prevFirstPointY; 1965 secondPoint.yPosition = prevSecondPointY; 1966 1967 return true; 1968 } 1969 return false; 1970 } 1971 1972 /// <summary> 1973 /// Clips the bottom (left and right) points of the segment to plotting area. 1974 /// Used in area and range charts. 1975 /// </summary> 1976 /// <param name="resultPath"></param> 1977 /// <param name="firstPoint">First data point.</param> 1978 /// <param name="secondPoint">Second data point.</param> 1979 /// <param name="thirdPoint">Coordinates of the bottom left point.</param> 1980 /// <param name="fourthPoint">Coordinates of the bottom right point.</param> 1981 /// <param name="reversed">Points are in reversed order.</param> 1982 /// <param name="area">Chart area reference.</param> 1983 /// <param name="graph">Chart graphics.</param> 1984 /// <param name="matrix">Coordinates transformation matrix.</param> 1985 /// <param name="lightStyle">LightStyle style (None, Simplistic, Realistic).</param> 1986 /// <param name="prevDataPointEx">Previous data point object.</param> 1987 /// <param name="positionZ">Z position of the back side of the 3D surface.</param> 1988 /// <param name="depth">Depth of the 3D surface.</param> 1989 /// <param name="points">Array of points.</param> 1990 /// <param name="pointIndex">Index of point to draw.</param> 1991 /// <param name="pointLoopIndex">Index of points loop.</param> 1992 /// <param name="tension">Line tension.</param> 1993 /// <param name="operationType">AxisName of operation Drawing, Calculating Path or Both</param> 1994 /// <param name="surfaceSegmentType">Define surface segment type if it consists of several segments.</param> 1995 /// <param name="topDarkening">Darkenning scale for top surface. 0 - None.</param> 1996 /// <param name="bottomDarkening">Darkenning scale for bottom surface. 0 - None.</param> 1997 /// <returns>Returns element shape path if operationType parameter is set to CalcElementPath, otherwise Null.</returns> ClipBottomPoints( GraphicsPath resultPath, ref DataPoint3D firstPoint, ref DataPoint3D secondPoint, ref PointF thirdPoint, ref PointF fourthPoint, bool reversed, ChartArea area, ChartGraphics graph, Matrix3D matrix, LightStyle lightStyle, DataPoint3D prevDataPointEx, float positionZ, float depth, ArrayList points, int pointIndex, int pointLoopIndex, float tension, DrawingOperationTypes operationType, LineSegmentType surfaceSegmentType, float topDarkening, float bottomDarkening)1998 protected bool ClipBottomPoints( 1999 GraphicsPath resultPath, 2000 ref DataPoint3D firstPoint, 2001 ref DataPoint3D secondPoint, 2002 ref PointF thirdPoint, 2003 ref PointF fourthPoint, 2004 bool reversed, 2005 ChartArea area, 2006 ChartGraphics graph, 2007 Matrix3D matrix, 2008 LightStyle lightStyle, 2009 DataPoint3D prevDataPointEx, 2010 float positionZ, 2011 float depth, 2012 ArrayList points, 2013 int pointIndex, 2014 int pointLoopIndex, 2015 float tension, 2016 DrawingOperationTypes operationType, 2017 LineSegmentType surfaceSegmentType, 2018 float topDarkening, 2019 float bottomDarkening) 2020 { 2021 // Do not allow recursion to go too deep 2022 ++area.IterationCounter; 2023 if(area.IterationCounter > 20) 2024 { 2025 area.IterationCounter = 0; 2026 return true; 2027 } 2028 2029 //**************************************************************** 2030 //** Round plot are position and point coordinates 2031 //**************************************************************** 2032 int decimals = 3; 2033 decimal plotAreaPositionX = Math.Round((decimal)area.PlotAreaPosition.X, decimals); 2034 decimal plotAreaPositionY = Math.Round((decimal)area.PlotAreaPosition.Y, decimals); 2035 decimal plotAreaPositionRight = Math.Round((decimal)area.PlotAreaPosition.Right, decimals); 2036 decimal plotAreaPositionBottom = Math.Round((decimal)area.PlotAreaPosition.Bottom, decimals); 2037 2038 // Make area a little bit bigger 2039 plotAreaPositionX -= 0.001M; 2040 plotAreaPositionY -= 0.001M; 2041 plotAreaPositionRight += 0.001M; 2042 plotAreaPositionBottom += 0.001M; 2043 2044 2045 2046 // Round top points coordinates 2047 firstPoint.xPosition = Math.Round(firstPoint.xPosition, decimals); 2048 firstPoint.yPosition = Math.Round(firstPoint.yPosition, decimals); 2049 secondPoint.xPosition = Math.Round(secondPoint.xPosition, decimals); 2050 secondPoint.yPosition = Math.Round(secondPoint.yPosition, decimals); 2051 2052 thirdPoint.X = (float)Math.Round(thirdPoint.X, decimals); 2053 thirdPoint.Y = (float)Math.Round(thirdPoint.Y, decimals); 2054 2055 fourthPoint.X = (float)Math.Round(fourthPoint.X, decimals); 2056 fourthPoint.Y = (float)Math.Round(fourthPoint.Y, decimals); 2057 2058 //**************************************************************** 2059 //** Clip area data points inside the plotting area 2060 //**************************************************************** 2061 2062 // Chech data points Y values 2063 if((decimal)thirdPoint.Y < plotAreaPositionY || 2064 (decimal)thirdPoint.Y > plotAreaPositionBottom || 2065 (decimal)fourthPoint.Y < plotAreaPositionY || 2066 (decimal)fourthPoint.Y > plotAreaPositionBottom ) 2067 { 2068 // Remember previous y positions 2069 PointF prevThirdPoint = new PointF(thirdPoint.X, thirdPoint.Y); 2070 PointF prevFourthPoint = new PointF(fourthPoint.X, fourthPoint.Y); 2071 2072 // Check if whole line is outside plotting region 2073 bool surfaceCompletlyOutside = false; 2074 bool outsideTop = false; 2075 if((decimal)thirdPoint.Y < plotAreaPositionY && 2076 (decimal)fourthPoint.Y < plotAreaPositionY) 2077 { 2078 outsideTop = true; 2079 surfaceCompletlyOutside = true; 2080 thirdPoint.Y = area.PlotAreaPosition.Y; 2081 fourthPoint.Y = area.PlotAreaPosition.Y; 2082 } 2083 if((decimal)thirdPoint.Y > plotAreaPositionBottom && 2084 (decimal)fourthPoint.Y > plotAreaPositionBottom) 2085 { 2086 surfaceCompletlyOutside = true; 2087 thirdPoint.Y = area.PlotAreaPosition.Bottom; 2088 fourthPoint.Y = area.PlotAreaPosition.Bottom; 2089 } 2090 2091 // Draw just one surface 2092 if(surfaceCompletlyOutside) 2093 { 2094 resultPath = Draw3DSurface( firstPoint, secondPoint, reversed, 2095 area, graph, matrix, lightStyle, prevDataPointEx, 2096 positionZ, depth, points, pointIndex, pointLoopIndex, 2097 tension, operationType, surfaceSegmentType, 2098 topDarkening, 0.5f, 2099 new PointF(thirdPoint.X, thirdPoint.Y), 2100 new PointF(fourthPoint.X, fourthPoint.Y), 2101 outsideTop, 2102 false, false); 2103 2104 // Restore previous x\y positions 2105 thirdPoint = new PointF(prevThirdPoint.X, prevThirdPoint.Y); 2106 fourthPoint = new PointF(prevFourthPoint.X, prevFourthPoint.Y); 2107 2108 return true; 2109 } 2110 2111 // Get intersection point 2112 DataPoint3D intersectionPoint = new DataPoint3D(); 2113 bool firstIntersectionOnBottom = false; 2114 intersectionPoint.yPosition = (double)plotAreaPositionY; 2115 if((decimal)thirdPoint.Y > plotAreaPositionBottom || 2116 (decimal)fourthPoint.Y > plotAreaPositionBottom ) 2117 { 2118 intersectionPoint.yPosition = (double)area.PlotAreaPosition.Bottom; 2119 firstIntersectionOnBottom = true; 2120 } 2121 intersectionPoint.xPosition = (intersectionPoint.yPosition - fourthPoint.Y) * 2122 (thirdPoint.X - fourthPoint.X) / 2123 (thirdPoint.Y - fourthPoint.Y) + 2124 fourthPoint.X; 2125 2126 // Intersection point must be between first and second points 2127 intersectionPoint.yPosition = (intersectionPoint.xPosition - secondPoint.xPosition) / 2128 (firstPoint.xPosition - secondPoint.xPosition) * 2129 (firstPoint.yPosition - secondPoint.yPosition) + 2130 secondPoint.yPosition; 2131 2132 if(double.IsNaN(intersectionPoint.xPosition) || 2133 double.IsInfinity(intersectionPoint.xPosition) || 2134 double.IsNaN(intersectionPoint.yPosition) || 2135 double.IsInfinity(intersectionPoint.yPosition) ) 2136 { 2137 return true; 2138 } 2139 2140 // Check if there are 2 intersection points (3 segments) 2141 int segmentNumber = 2; 2142 DataPoint3D intersectionPoint2 = null; 2143 bool switchPoints = false; 2144 if( ((decimal)thirdPoint.Y < plotAreaPositionY && 2145 (decimal)fourthPoint.Y > plotAreaPositionBottom) || 2146 ((decimal)thirdPoint.Y > plotAreaPositionBottom && 2147 (decimal)fourthPoint.Y < plotAreaPositionY)) 2148 { 2149 segmentNumber = 3; 2150 intersectionPoint2 = new DataPoint3D(); 2151 if(!firstIntersectionOnBottom) 2152 { 2153 intersectionPoint2.yPosition = (double)area.PlotAreaPosition.Bottom; 2154 } 2155 else 2156 { 2157 intersectionPoint2.yPosition = (double)area.PlotAreaPosition.Y; 2158 } 2159 intersectionPoint2.xPosition = (intersectionPoint2.yPosition - fourthPoint.Y) * 2160 (thirdPoint.X - fourthPoint.X) / 2161 (thirdPoint.Y - fourthPoint.Y) + 2162 fourthPoint.X; 2163 2164 intersectionPoint2.yPosition = (intersectionPoint2.xPosition - secondPoint.xPosition) / 2165 (firstPoint.xPosition - secondPoint.xPosition) * 2166 (firstPoint.yPosition - secondPoint.yPosition) + 2167 secondPoint.yPosition; 2168 2169 if(double.IsNaN(intersectionPoint2.xPosition) || 2170 double.IsInfinity(intersectionPoint2.xPosition) || 2171 double.IsNaN(intersectionPoint2.yPosition) || 2172 double.IsInfinity(intersectionPoint2.yPosition) ) 2173 { 2174 return true; 2175 } 2176 2177 2178 // Switch intersection points 2179 //if(firstPoint.yPosition > plotAreaPositionBottom) 2180 if((decimal)thirdPoint.Y > plotAreaPositionBottom) 2181 { 2182 switchPoints = true; 2183 /* 2184 DataPoint3D tempPoint = new DataPoint3D(); 2185 tempPoint.xPosition = intersectionPoint.xPosition; 2186 tempPoint.yPosition = intersectionPoint.yPosition; 2187 intersectionPoint.xPosition = intersectionPoint2.xPosition; 2188 intersectionPoint.yPosition = intersectionPoint2.yPosition; 2189 intersectionPoint2.xPosition = tempPoint.xPosition; 2190 intersectionPoint2.yPosition = tempPoint.yPosition; 2191 */ 2192 } 2193 } 2194 2195 2196 // Adjust points Y values 2197 bool firstSegmentVisible = true; 2198 float bottomDarken = bottomDarkening; 2199 bool firstSegmentOutsideTop = false; 2200 bool secondSegmentOutsideTop = false; 2201 if((decimal)thirdPoint.Y < plotAreaPositionY) 2202 { 2203 firstSegmentOutsideTop = true; 2204 firstSegmentVisible = false; 2205 thirdPoint.Y = area.PlotAreaPosition.Y; 2206 bottomDarken = 0.5f; 2207 } 2208 else if((decimal)thirdPoint.Y > plotAreaPositionBottom) 2209 { 2210 firstSegmentVisible = false; 2211 thirdPoint.Y = area.PlotAreaPosition.Bottom; 2212 if(firstPoint.yPosition >= thirdPoint.Y) 2213 { 2214 bottomDarken = 0.5f; 2215 } 2216 } 2217 if((decimal)fourthPoint.Y < plotAreaPositionY) 2218 { 2219 secondSegmentOutsideTop = true; 2220 fourthPoint.Y = area.PlotAreaPosition.Y; 2221 bottomDarken = 0.5f; 2222 } 2223 else if((decimal)fourthPoint.Y > plotAreaPositionBottom) 2224 { 2225 fourthPoint.Y = area.PlotAreaPosition.Bottom; 2226 if(fourthPoint.Y <= secondPoint.yPosition) 2227 { 2228 bottomDarken = 0.5f; 2229 } 2230 } 2231 2232 // Draw surfaces in 2 or 3 segments 2233 for(int segmentIndex = 0; segmentIndex < 3; segmentIndex++) 2234 { 2235 GraphicsPath segmentPath = null; 2236 if(segmentIndex == 0 && !reversed || 2237 segmentIndex == 2 && reversed) 2238 { 2239 // Draw first segment 2240 if(intersectionPoint2 == null) 2241 { 2242 intersectionPoint2 = intersectionPoint; 2243 } 2244 2245 if(switchPoints) 2246 { 2247 DataPoint3D tempPoint = new DataPoint3D(); 2248 tempPoint.xPosition = intersectionPoint.xPosition; 2249 tempPoint.yPosition = intersectionPoint.yPosition; 2250 intersectionPoint.xPosition = intersectionPoint2.xPosition; 2251 intersectionPoint.yPosition = intersectionPoint2.yPosition; 2252 intersectionPoint2.xPosition = tempPoint.xPosition; 2253 intersectionPoint2.yPosition = tempPoint.yPosition; 2254 } 2255 2256 2257 intersectionPoint2.dataPoint = secondPoint.dataPoint; 2258 intersectionPoint2.index = secondPoint.index; 2259 intersectionPoint2.xCenterVal = secondPoint.xCenterVal; 2260 2261 segmentPath = Draw3DSurface( firstPoint, intersectionPoint2, reversed, 2262 area, graph, matrix, lightStyle, prevDataPointEx, 2263 positionZ, depth, points, pointIndex, pointLoopIndex, 2264 tension, operationType, 2265 (surfaceSegmentType == LineSegmentType.Middle) ? LineSegmentType.Middle : LineSegmentType.First, 2266 topDarkening, bottomDarken, 2267 new PointF(float.NaN, thirdPoint.Y), 2268 new PointF((float)intersectionPoint2.xPosition, (!firstSegmentVisible || segmentNumber == 3) ? thirdPoint.Y : fourthPoint.Y), 2269 firstSegmentOutsideTop, 2270 false, false); 2271 2272 if(switchPoints) 2273 { 2274 DataPoint3D tempPoint = new DataPoint3D(); 2275 tempPoint.xPosition = intersectionPoint.xPosition; 2276 tempPoint.yPosition = intersectionPoint.yPosition; 2277 intersectionPoint.xPosition = intersectionPoint2.xPosition; 2278 intersectionPoint.yPosition = intersectionPoint2.yPosition; 2279 intersectionPoint2.xPosition = tempPoint.xPosition; 2280 intersectionPoint2.yPosition = tempPoint.yPosition; 2281 } 2282 2283 } 2284 2285 if(segmentIndex == 1 && intersectionPoint2 != null && segmentNumber == 3) 2286 { 2287 if(!switchPoints) 2288 { 2289 DataPoint3D tempPoint = new DataPoint3D(); 2290 tempPoint.xPosition = intersectionPoint.xPosition; 2291 tempPoint.yPosition = intersectionPoint.yPosition; 2292 intersectionPoint.xPosition = intersectionPoint2.xPosition; 2293 intersectionPoint.yPosition = intersectionPoint2.yPosition; 2294 intersectionPoint2.xPosition = tempPoint.xPosition; 2295 intersectionPoint2.yPosition = tempPoint.yPosition; 2296 } 2297 2298 // Draw middle segment 2299 intersectionPoint2.dataPoint = secondPoint.dataPoint; 2300 intersectionPoint2.index = secondPoint.index; 2301 intersectionPoint2.xCenterVal = secondPoint.xCenterVal; 2302 2303 intersectionPoint.xCenterVal = firstPoint.xCenterVal; 2304 intersectionPoint.index = firstPoint.index; 2305 intersectionPoint.dataPoint = firstPoint.dataPoint; 2306 2307 segmentPath = Draw3DSurface( intersectionPoint, intersectionPoint2, reversed, 2308 area, graph, matrix, lightStyle, prevDataPointEx, 2309 positionZ, depth, points, pointIndex, pointLoopIndex, 2310 tension, operationType, LineSegmentType.Middle, 2311 topDarkening, bottomDarkening, 2312 new PointF((float)intersectionPoint.xPosition, thirdPoint.Y), 2313 new PointF((float)intersectionPoint2.xPosition, fourthPoint.Y), 2314 false, 2315 false, false); 2316 2317 if(!switchPoints) 2318 { 2319 DataPoint3D tempPoint = new DataPoint3D(); 2320 tempPoint.xPosition = intersectionPoint.xPosition; 2321 tempPoint.yPosition = intersectionPoint.yPosition; 2322 intersectionPoint.xPosition = intersectionPoint2.xPosition; 2323 intersectionPoint.yPosition = intersectionPoint2.yPosition; 2324 intersectionPoint2.xPosition = tempPoint.xPosition; 2325 intersectionPoint2.yPosition = tempPoint.yPosition; 2326 } 2327 2328 } 2329 2330 if(segmentIndex == 2 && !reversed || 2331 segmentIndex == 0 && reversed) 2332 { 2333 if(switchPoints) 2334 { 2335 DataPoint3D tempPoint = new DataPoint3D(); 2336 tempPoint.xPosition = intersectionPoint.xPosition; 2337 tempPoint.yPosition = intersectionPoint.yPosition; 2338 intersectionPoint.xPosition = intersectionPoint2.xPosition; 2339 intersectionPoint.yPosition = intersectionPoint2.yPosition; 2340 intersectionPoint2.xPosition = tempPoint.xPosition; 2341 intersectionPoint2.yPosition = tempPoint.yPosition; 2342 } 2343 2344 // Draw second segment 2345 intersectionPoint.dataPoint = firstPoint.dataPoint; 2346 intersectionPoint.index = firstPoint.index; 2347 intersectionPoint.xCenterVal = firstPoint.xCenterVal; 2348 2349 float thirdPointNewY = (!firstSegmentVisible || segmentNumber == 3) ? thirdPoint.Y : fourthPoint.Y; 2350 if(segmentNumber == 3) 2351 { 2352 thirdPointNewY = (secondSegmentOutsideTop) ? thirdPoint.Y : fourthPoint.Y; 2353 } 2354 2355 segmentPath = Draw3DSurface( intersectionPoint, secondPoint, reversed, 2356 area, graph, matrix, lightStyle, prevDataPointEx, 2357 positionZ, depth, points, pointIndex, pointLoopIndex, 2358 tension, operationType, 2359 (surfaceSegmentType == LineSegmentType.Middle) ? LineSegmentType.Middle : LineSegmentType.Last, 2360 topDarkening, bottomDarken, 2361 new PointF((float)intersectionPoint.xPosition, thirdPointNewY), 2362 new PointF(float.NaN, fourthPoint.Y), 2363 secondSegmentOutsideTop, 2364 false, false); 2365 2366 if(switchPoints) 2367 { 2368 DataPoint3D tempPoint = new DataPoint3D(); 2369 tempPoint.xPosition = intersectionPoint.xPosition; 2370 tempPoint.yPosition = intersectionPoint.yPosition; 2371 intersectionPoint.xPosition = intersectionPoint2.xPosition; 2372 intersectionPoint.yPosition = intersectionPoint2.yPosition; 2373 intersectionPoint2.xPosition = tempPoint.xPosition; 2374 intersectionPoint2.yPosition = tempPoint.yPosition; 2375 } 2376 2377 } 2378 2379 // Add segment path 2380 if(resultPath != null && segmentPath != null && segmentPath.PointCount > 0) 2381 { 2382 resultPath.AddPath(segmentPath, true); 2383 } 2384 } 2385 2386 // Restore previous x\y positions 2387 thirdPoint = new PointF(prevThirdPoint.X, prevThirdPoint.Y); 2388 fourthPoint = new PointF(prevFourthPoint.X, prevFourthPoint.Y); 2389 return true; 2390 } 2391 return false; 2392 } 2393 2394 /// <summary> 2395 /// Draws a 3D surface connecting the two specified points in 2D space. 2396 /// Used to draw Line based charts. 2397 /// </summary> 2398 /// <param name="firstPoint">First data point.</param> 2399 /// <param name="secondPoint">Second data point.</param> 2400 /// <param name="reversed">Points are in reversed order.</param> 2401 /// <param name="area">Chart area reference.</param> 2402 /// <param name="graph">Chart graphics.</param> 2403 /// <param name="matrix">Coordinates transformation matrix.</param> 2404 /// <param name="lightStyle">LightStyle style (None, Simplistic, Realistic).</param> 2405 /// <param name="prevDataPointEx">Previous data point object.</param> 2406 /// <param name="positionZ">Z position of the back side of the 3D surface.</param> 2407 /// <param name="depth">Depth of the 3D surface.</param> 2408 /// <param name="points">Array of points.</param> 2409 /// <param name="pointIndex">Index of point to draw.</param> 2410 /// <param name="pointLoopIndex">Index of points loop.</param> 2411 /// <param name="tension">Line tension.</param> 2412 /// <param name="operationType">AxisName of operation Drawing, Calculating Path or Both</param> 2413 /// <param name="surfaceSegmentType">Define surface segment type if it consists of several segments.</param> 2414 /// <param name="topDarkening">Darkenning scale for top surface. 0 - None.</param> 2415 /// <param name="bottomDarkening">Darkenning scale for bottom surface. 0 - None.</param> 2416 /// <param name="thirdPointPosition">Position where the third point is actually located or float.NaN if same as in "firstPoint".</param> 2417 /// <param name="fourthPointPosition">Position where the fourth point is actually located or float.NaN if same as in "secondPoint".</param> 2418 /// <param name="clippedSegment">Indicates that drawn segment is 3D clipped. Only top/bottom should be drawn.</param> 2419 /// <param name="clipOnTop">Indicates that top segment line should be clipped to the pkot area.</param> 2420 /// <param name="clipOnBottom">Indicates that bottom segment line should be clipped to the pkot area.</param> 2421 /// <returns>Returns elemnt shape path if operationType parameter is set to CalcElementPath, otherwise Null.</returns> Draw3DSurface( DataPoint3D firstPoint, DataPoint3D secondPoint, bool reversed, ChartArea area, ChartGraphics graph, Matrix3D matrix, LightStyle lightStyle, DataPoint3D prevDataPointEx, float positionZ, float depth, ArrayList points, int pointIndex, int pointLoopIndex, float tension, DrawingOperationTypes operationType, LineSegmentType surfaceSegmentType, float topDarkening, float bottomDarkening, PointF thirdPointPosition, PointF fourthPointPosition, bool clippedSegment, bool clipOnTop, bool clipOnBottom)2422 protected virtual GraphicsPath Draw3DSurface( 2423 DataPoint3D firstPoint, 2424 DataPoint3D secondPoint, 2425 bool reversed, 2426 ChartArea area, 2427 ChartGraphics graph, 2428 Matrix3D matrix, 2429 LightStyle lightStyle, 2430 DataPoint3D prevDataPointEx, 2431 float positionZ, 2432 float depth, 2433 ArrayList points, 2434 int pointIndex, 2435 int pointLoopIndex, 2436 float tension, 2437 DrawingOperationTypes operationType, 2438 LineSegmentType surfaceSegmentType, 2439 float topDarkening, 2440 float bottomDarkening, 2441 PointF thirdPointPosition, 2442 PointF fourthPointPosition, 2443 bool clippedSegment, 2444 bool clipOnTop, 2445 bool clipOnBottom) 2446 { 2447 // Implemented in area and range chart 2448 return null; 2449 } 2450 #endregion 2451 2452 #region IDisposable overrides 2453 /// <summary> 2454 /// Releases unmanaged and - optionally - managed resources 2455 /// </summary> 2456 /// <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)2457 protected override void Dispose(bool disposing) 2458 { 2459 if (disposing) 2460 { 2461 // Dispose managed resources 2462 if (this._linePen != null) 2463 { 2464 this._linePen.Dispose(); 2465 this._linePen = null; 2466 } 2467 } 2468 base.Dispose(disposing); 2469 } 2470 #endregion 2471 } 2472 } 2473