1 //------------------------------------------------------------- 2 // <copyright company=�Microsoft Corporation�> 3 // Copyright � Microsoft Corporation. All Rights Reserved. 4 // </copyright> 5 //------------------------------------------------------------- 6 // @owner=alexgor, deliant 7 //================================================================= 8 // File: ChartGraphics3D.cs 9 // 10 // Namespace: System.Web.UI.WebControls[Windows.Forms].Charting 11 // 12 // Classes: ChartGraphics3D, Point3D 13 // 14 // Purpose: ChartGraphics3D class is 3D chart rendering engine. 15 // All chart 3D shapes are drawn in specific order so 16 // that correct Z order of all shapes is achieved. 3D 17 // graphics engine do not support shapes intersection. 18 // 3D shapes are transformed into one or more 2D shapes 19 // and then drawn with 2D chart graphics engine. 20 // 21 // Reviewed: AG - Microsoft 16, 2007 22 // 23 //=================================================================== 24 25 #region Used namespaces 26 27 using System; 28 using System.Drawing; 29 using System.Drawing.Drawing2D; 30 using System.Drawing.Text; 31 using System.Drawing.Imaging; 32 using System.ComponentModel; 33 using System.Collections; 34 using System.Diagnostics.CodeAnalysis; 35 36 #endregion 37 38 #if Microsoft_CONTROL 39 namespace System.Windows.Forms.DataVisualization.Charting 40 #else 41 namespace System.Web.UI.DataVisualization.Charting 42 43 #endif 44 { 45 #region 3D enumerations 46 47 /// <summary> 48 /// 3D cube surfaces names. 49 /// </summary> 50 [Flags] 51 internal enum SurfaceNames 52 { 53 /// <summary> 54 /// Front. 55 /// </summary> 56 Front = 1, 57 /// <summary> 58 /// Back. 59 /// </summary> 60 Back = 2, 61 /// <summary> 62 /// Left. 63 /// </summary> 64 Left = 4, 65 /// <summary> 66 /// Right. 67 /// </summary> 68 Right = 8, 69 /// <summary> 70 /// Top. 71 /// </summary> 72 Top = 16, 73 /// <summary> 74 /// Bottom. 75 /// </summary> 76 Bottom = 32 77 } 78 79 /// <summary> 80 /// This enumeration defines all significant points in a pie 81 /// slice. Only these points should be transformed for pie 82 /// chart using Matrix object. 83 /// </summary> 84 internal enum PiePoints 85 { 86 /// <summary> 87 /// Angle 180 Top point on the arc 88 /// </summary> 89 Top180, 90 91 /// <summary> 92 /// Angle 180 Bottom point on the arc 93 /// </summary> 94 Bottom180, 95 96 /// <summary> 97 /// Angle 0 Top point on the arc 98 /// </summary> 99 Top0, 100 101 /// <summary> 102 /// Angle 0 Bottom point on the arc 103 /// </summary> 104 Bottom0, 105 106 /// <summary> 107 /// Top Start Angle point on the arc 108 /// </summary> 109 TopStart, 110 111 /// <summary> 112 /// Top End Angle point on the arc 113 /// </summary> 114 TopEnd, 115 116 /// <summary> 117 /// Bottom Start Angle point on the arc 118 /// </summary> 119 BottomStart, 120 121 /// <summary> 122 /// Bottom End Angle point on the arc 123 /// </summary> 124 BottomEnd, 125 126 /// <summary> 127 /// Center Top 128 /// </summary> 129 TopCenter, 130 131 /// <summary> 132 /// Center Bottom 133 /// </summary> 134 BottomCenter, 135 136 /// <summary> 137 /// Top Label Line 138 /// </summary> 139 TopLabelLine, 140 141 /// <summary> 142 /// Top Label Line Out 143 /// </summary> 144 TopLabelLineout, 145 146 /// <summary> 147 /// Top Label Center 148 /// </summary> 149 TopLabelCenter, 150 151 /// <summary> 152 /// Top Rectangle Top Left Point 153 /// </summary> 154 TopRectTopLeftPoint, 155 156 /// <summary> 157 /// Top Rectangle Right Bottom Point 158 /// </summary> 159 TopRectBottomRightPoint, 160 161 /// <summary> 162 /// Bottom Rectangle Top Left Point 163 /// </summary> 164 BottomRectTopLeftPoint, 165 166 /// <summary> 167 /// Bottom Rectangle Right Bottom Point 168 /// </summary> 169 BottomRectBottomRightPoint, 170 171 /// <summary> 172 /// Angle 180 Top point on the Doughnut arc 173 /// </summary> 174 DoughnutTop180, 175 176 /// <summary> 177 /// Angle 180 Bottom point on the Doughnut arc 178 /// </summary> 179 DoughnutBottom180, 180 181 /// <summary> 182 /// Angle 0 Top point on the Doughnut arc 183 /// </summary> 184 DoughnutTop0, 185 186 /// <summary> 187 /// Angle 0 Bottom point on the Doughnut arc 188 /// </summary> 189 DoughnutBottom0, 190 191 /// <summary> 192 /// Top Start Angle point on the Doughnut arc 193 /// </summary> 194 DoughnutTopStart, 195 196 /// <summary> 197 /// Top End Angle point on the Doughnut arc 198 /// </summary> 199 DoughnutTopEnd, 200 201 /// <summary> 202 /// Bottom Start Angle point on the Doughnut arc 203 /// </summary> 204 DoughnutBottomStart, 205 206 /// <summary> 207 /// Bottom End Angle point on the Doughnut arc 208 /// </summary> 209 DoughnutBottomEnd, 210 211 /// <summary> 212 /// Doughnut Top Rectangle Top Left Point 213 /// </summary> 214 DoughnutTopRectTopLeftPoint, 215 216 /// <summary> 217 /// Doughnut Top Rectangle Right Bottom Point 218 /// </summary> 219 DoughnutTopRectBottomRightPoint, 220 221 /// <summary> 222 /// Doughnut Bottom Rectangle Top Left Point 223 /// </summary> 224 DoughnutBottomRectTopLeftPoint, 225 226 /// <summary> 227 /// Doughnut Bottom Rectangle Right Bottom Point 228 /// </summary> 229 DoughnutBottomRectBottomRightPoint, 230 } 231 232 233 /// <summary> 234 /// AxisName of drawing operation. 235 /// </summary> 236 [Flags] 237 internal enum DrawingOperationTypes 238 { 239 /// <summary> 240 /// Draw element. 241 /// </summary> 242 DrawElement = 1, 243 244 /// <summary> 245 /// Calculate element path. (for selection or tooltips) 246 /// </summary> 247 CalcElementPath = 2, 248 } 249 250 /// <summary> 251 /// AxisName of line segment. 252 /// </summary> 253 internal enum LineSegmentType 254 { 255 /// <summary> 256 /// Only one segment exists. 257 /// </summary> 258 Single, 259 260 /// <summary> 261 /// First segment. 262 /// </summary> 263 First, 264 265 /// <summary> 266 /// Middle segment. 267 /// </summary> 268 Middle, 269 270 /// <summary> 271 /// Last segment. 272 /// </summary> 273 Last 274 } 275 276 #endregion 277 278 /// <summary> 279 /// The ChartGraphics class is 3D chart rendering engine. All chart 280 /// 3D shapes are drawn in specific order so that correct Z order 281 /// of all shapes is achieved. 3D graphics engine do not support 282 /// shapes intersection. 3D shapes are transformed into one or 283 /// more 2D shapes and then drawn with 2D chart graphics engine. 284 /// </summary> 285 public partial class ChartGraphics 286 { 287 #region Fields 288 289 /// <summary> 290 /// Helper field used to store the index of cylinder left/bottom side coordinate. 291 /// </summary> 292 private int _oppLeftBottomPoint = -1; 293 294 /// <summary> 295 /// Helper field used to store the index of cylinder right/top side coordinate. 296 /// </summary> 297 private int _oppRigthTopPoint = -1; 298 299 /// <summary> 300 /// Point of the front line from the previous line segment. 301 /// </summary> 302 internal PointF frontLinePoint1 = PointF.Empty; 303 304 /// <summary> 305 /// Point of the front line from the previous line segment. 306 /// </summary> 307 internal PointF frontLinePoint2 = PointF.Empty; 308 309 /// <summary> 310 /// Previous line segment pen. 311 /// </summary> 312 internal Pen frontLinePen = null; 313 314 #endregion 315 316 #region 3D Line drawing methods 317 318 /// <summary> 319 /// Draws grid line in 3D space (on two area scene walls) 320 /// </summary> 321 /// <param name="area">Chart area.</param> 322 /// <param name="color">Line color.</param> 323 /// <param name="width">Line width.</param> 324 /// <param name="style">Line style.</param> 325 /// <param name="point1">First line point.</param> 326 /// <param name="point2">Second line point.</param> 327 /// <param name="horizontal">Indicates that grid line is horizontal</param> 328 /// <param name="common">Common Elements</param> 329 /// <param name="obj">Selected object</param> Draw3DGridLine( ChartArea area, Color color, int width, ChartDashStyle style, PointF point1, PointF point2, bool horizontal, CommonElements common, object obj )330 internal void Draw3DGridLine( 331 ChartArea area, 332 Color color, 333 int width, 334 ChartDashStyle style, 335 PointF point1, 336 PointF point2, 337 bool horizontal, 338 CommonElements common, 339 object obj 340 ) 341 { 342 float zPositon = area.IsMainSceneWallOnFront() ? area.areaSceneDepth : 0f; 343 344 ChartElementType chartElementType = obj is StripLine ? ChartElementType.StripLines : ChartElementType.Gridlines; 345 346 // Draw strip line on the back/front wall 347 ((ChartGraphics)this).Draw3DLine( 348 area.matrix3D, 349 color, width, style, 350 new Point3D(point1.X, point1.Y, zPositon), 351 new Point3D(point2.X, point2.Y, zPositon), 352 common, 353 obj, 354 chartElementType 355 ); 356 357 if(horizontal) 358 { 359 // Draw strip line on the side wall (left or right) 360 if(area.IsSideSceneWallOnLeft()) 361 { 362 point1.X = Math.Min(point1.X, point2.X); 363 } 364 else 365 { 366 point1.X = Math.Max(point1.X, point2.X); 367 } 368 369 ((ChartGraphics)this).Draw3DLine( 370 area.matrix3D, 371 color, width, style, 372 new Point3D(point1.X, point1.Y, 0f), 373 new Point3D(point1.X, point1.Y, area.areaSceneDepth), 374 common, 375 obj, 376 chartElementType 377 ); 378 379 } 380 else if(area.IsBottomSceneWallVisible()) 381 { 382 // Draw strip line on the bottom wall (if visible) 383 point1.Y = Math.Max(point1.Y, point2.Y); 384 385 ((ChartGraphics)this).Draw3DLine( 386 area.matrix3D, 387 color, width, style, 388 new Point3D(point1.X, point1.Y, 0f), 389 new Point3D(point1.X, point1.Y, area.areaSceneDepth), 390 common, 391 obj, 392 chartElementType 393 ); 394 } 395 } 396 397 /// <summary> 398 /// Draws a line connecting the two specified points. 399 /// </summary> 400 /// <param name="matrix">Coordinates transformation matrix.</param> 401 /// <param name="color">Line color.</param> 402 /// <param name="width">Line width.</param> 403 /// <param name="style">Line style.</param> 404 /// <param name="firstPoint">A Point that represents the first point to connect.</param> 405 /// <param name="secondPoint">A Point that represents the second point to connect.</param> 406 /// <param name="common">Common elements</param> 407 /// <param name="obj">Selected object</param> 408 /// <param name="type">Selected chart element</param> Draw3DLine( Matrix3D matrix, Color color, int width, ChartDashStyle style, Point3D firstPoint, Point3D secondPoint, CommonElements common, object obj, ChartElementType type )409 internal void Draw3DLine( 410 Matrix3D matrix, 411 Color color, 412 int width, 413 ChartDashStyle style, 414 Point3D firstPoint, 415 Point3D secondPoint, 416 CommonElements common, 417 object obj, 418 ChartElementType type 419 ) 420 { 421 422 // Transform coordinates 423 Point3D [] points = new Point3D[] {firstPoint, secondPoint}; 424 matrix.TransformPoints( points ); 425 426 // Selection mode 427 if (common.ProcessModeRegions && type != ChartElementType.Nothing) 428 { 429 using (GraphicsPath path = new GraphicsPath()) 430 { 431 432 if (Math.Abs(points[0].X - points[1].X) > Math.Abs(points[0].Y - points[1].Y)) 433 { 434 path.AddLine(points[0].X, points[0].Y - 1, points[1].X, points[1].Y - 1); 435 path.AddLine(points[1].X, points[1].Y + 1, points[0].X, points[0].Y + 1); 436 path.CloseAllFigures(); 437 } 438 else 439 { 440 path.AddLine(points[0].X - 1, points[0].Y, points[1].X - 1, points[1].Y); 441 path.AddLine(points[1].X + 1, points[1].Y, points[0].X + 1, points[0].Y); 442 path.CloseAllFigures(); 443 444 } 445 common.HotRegionsList.AddHotRegion(path, true, type, obj); 446 } 447 } 448 449 if( common.ProcessModePaint ) 450 { 451 // Draw 2D line in 3D space 452 ((ChartGraphics)this).DrawLineRel(color, width, style, points[0].PointF, points[1].PointF); 453 } 454 455 } 456 457 #endregion 458 459 #region 3D Pie Drawing methods and enumerations 460 461 462 /// <summary> 463 /// This method draw and fill four point polygons which 464 /// represents sides of a pie slice. 465 /// </summary> 466 /// <param name="area">Chart Area</param> 467 /// <param name="inclination">X angle rotation</param> 468 /// <param name="startAngle">Start Angle of a pie slice</param> 469 /// <param name="sweepAngle">Sweep angle of a pie slice</param> 470 /// <param name="points">Significant points of a pie slice</param> 471 /// <param name="brush">Brush used for fill</param> 472 /// <param name="pen">Pen used for drawing</param> 473 /// <param name="doughnut">Chart AxisName is Doughnut</param> FillPieSides( ChartArea area, float inclination, float startAngle, float sweepAngle, PointF [] points, SolidBrush brush, Pen pen, bool doughnut )474 internal void FillPieSides( 475 ChartArea area, 476 float inclination, 477 float startAngle, 478 float sweepAngle, 479 PointF [] points, 480 SolidBrush brush, 481 Pen pen, 482 bool doughnut 483 ) 484 { 485 486 // Create a graphics path 487 GraphicsPath path = new GraphicsPath(); 488 489 // Significant Points for Side polygons 490 PointF topCenter = points[(int)PiePoints.TopCenter]; 491 PointF bottomCenter = points[(int)PiePoints.BottomCenter]; 492 PointF topStart = points[(int)PiePoints.TopStart]; 493 PointF bottomStart = points[(int)PiePoints.BottomStart]; 494 PointF topEnd = points[(int)PiePoints.TopEnd]; 495 PointF bottomEnd = points[(int)PiePoints.BottomEnd]; 496 497 // For Doughnut 498 PointF topDoughnutStart = PointF.Empty; 499 PointF bottomDoughnutStart = PointF.Empty; 500 PointF topDoughnutEnd = PointF.Empty; 501 PointF bottomDoughnutEnd = PointF.Empty; 502 503 if( doughnut ) 504 { 505 // For Doughnut 506 topDoughnutStart = points[(int)PiePoints.DoughnutTopStart]; 507 bottomDoughnutStart = points[(int)PiePoints.DoughnutBottomStart]; 508 topDoughnutEnd = points[(int)PiePoints.DoughnutTopEnd]; 509 bottomDoughnutEnd = points[(int)PiePoints.DoughnutBottomEnd]; 510 } 511 512 bool startSide = false; 513 bool endSide = false; 514 float endAngle = startAngle + sweepAngle; 515 516 // If X angle is negative different side of pie slice is visible. 517 if (inclination > 0) 518 { 519 // Enable start or/and the end side 520 if( startAngle > -90 && startAngle < 90 || startAngle > 270 && startAngle < 450 ) 521 { 522 startSide = true; 523 } 524 if( endAngle >= -180 && endAngle < -90 || endAngle > 90 && endAngle < 270 || endAngle > 450 && endAngle <= 540 ) 525 { 526 endSide = true; 527 } 528 } 529 else 530 { 531 // Enable start or/and the end side 532 if( startAngle >= -180 && startAngle < -90 || startAngle > 90 && startAngle < 270 || startAngle > 450 && startAngle <= 540 ) 533 { 534 startSide = true; 535 536 } 537 if( endAngle > -90 && endAngle < 90 || endAngle > 270 && endAngle < 450 ) 538 { 539 endSide = true; 540 } 541 } 542 543 if( startSide ) 544 { 545 // ***************************************** 546 // Draw Start Angle side 547 // ***************************************** 548 using (path = new GraphicsPath()) 549 { 550 551 if (doughnut) 552 { 553 // Add Line between The Doughnut Arc and Arc 554 path.AddLine(topDoughnutStart, topStart); 555 556 // Add Line between The Top Start and Bottom Start 557 path.AddLine(topStart, bottomStart); 558 559 // Add Line between The Bottom Start and Doughnut Start 560 path.AddLine(bottomStart, bottomDoughnutStart); 561 562 // Add Line between The Bottom Doughnut Start and The Top Doughnut Start 563 path.AddLine(bottomDoughnutStart, topDoughnutStart); 564 } 565 else 566 { 567 // Add Line between The Center and Arc 568 path.AddLine(topCenter, topStart); 569 570 // Add Line between The Top Start and Bottom Start 571 path.AddLine(topStart, bottomStart); 572 573 // Add Line between The Bottom Start and The Center Bottom 574 path.AddLine(bottomStart, bottomCenter); 575 576 // Add Line between The Bottom Center and The Top Center 577 path.AddLine(bottomCenter, topCenter); 578 } 579 // Get surface colors 580 Color frontLightColor, leftLightColor, topLightColor, backLightColor, rightLightColor, bottomLightColor; 581 area.matrix3D.GetLight(brush.Color, out frontLightColor, out backLightColor, out leftLightColor, out rightLightColor, out topLightColor, out bottomLightColor); 582 583 Color lightColor; 584 if (area.Area3DStyle.Inclination < 0) 585 { 586 lightColor = bottomLightColor; 587 } 588 else 589 { 590 lightColor = topLightColor; 591 } 592 593 // Draw Path 594 using (Brush lightBrush = new SolidBrush(lightColor)) 595 { 596 FillPath(lightBrush, path); 597 } 598 599 DrawGraphicsPath(pen, path); 600 } 601 } 602 603 if( endSide ) 604 { 605 // ***************************************** 606 // Draw End Angle side 607 // ***************************************** 608 using (path = new GraphicsPath()) 609 { 610 if (doughnut) 611 { 612 // Add Line between The Doughnut Arc and Arc 613 path.AddLine(topDoughnutEnd, topEnd); 614 615 // Add Line between The Top End and Bottom End 616 path.AddLine(topEnd, bottomEnd); 617 618 // Add Line between The Bottom End and Doughnut End 619 path.AddLine(bottomEnd, bottomDoughnutEnd); 620 621 // Add Line between The Bottom Doughnut End and The Top Doughnut End 622 path.AddLine(bottomDoughnutEnd, topDoughnutEnd); 623 } 624 else 625 { 626 // Add Line between The Center and Arc 627 path.AddLine(topCenter, topEnd); 628 629 // Add Line between The Top End and Bottom End 630 path.AddLine(topEnd, bottomEnd); 631 632 // Add Line between The Bottom End and The Center Bottom 633 path.AddLine(bottomEnd, bottomCenter); 634 635 // Add Line between The Bottom Center and The Top Center 636 path.AddLine(bottomCenter, topCenter); 637 638 } 639 640 // Get surface colors 641 Color frontLightColor, leftLightColor, topLightColor, backLightColor, rightLightColor, bottomLightColor; 642 area.matrix3D.GetLight(brush.Color, out frontLightColor, out backLightColor, out leftLightColor, out rightLightColor, out topLightColor, out bottomLightColor); 643 644 Color lightColor; 645 if (area.Area3DStyle.Inclination < 0) 646 { 647 lightColor = bottomLightColor; 648 } 649 else 650 { 651 lightColor = topLightColor; 652 } 653 654 // Draw Path 655 using (Brush lightBrush = new SolidBrush(lightColor)) 656 { 657 FillPath(lightBrush, path); 658 } 659 660 DrawGraphicsPath(pen, path); 661 } 662 } 663 } 664 665 /// <summary> 666 /// This method Draw and fill pie curves. 667 /// </summary> 668 /// <param name="area">Chart area used for drawing</param> 669 /// <param name="point">Data Point</param> 670 /// <param name="brush">Graphic Brush used for drawing</param> 671 /// <param name="pen">Graphic Pen used for drawing</param> 672 /// <param name="topFirstRectPoint">Rotated bounded rectangle points</param> 673 /// <param name="topSecondRectPoint">Rotated bounded rectangle points</param> 674 /// <param name="bottomFirstRectPoint">Rotated bounded rectangle points</param> 675 /// <param name="bottomSecondRectPoint">Rotated bounded rectangle points</param> 676 /// <param name="topFirstPoint">Significant pie points</param> 677 /// <param name="topSecondPoint">Significant pie points</param> 678 /// <param name="bottomFirstPoint">Significant pie points</param> 679 /// <param name="bottomSecondPoint">Significant pie points</param> 680 /// <param name="startAngle">Start pie angle</param> 681 /// <param name="sweepAngle">End pie angle</param> 682 /// <param name="pointIndex">Data Point Index</param> FillPieCurve( ChartArea area, DataPoint point, Brush brush, Pen pen, PointF topFirstRectPoint, PointF topSecondRectPoint, PointF bottomFirstRectPoint, PointF bottomSecondRectPoint, PointF topFirstPoint, PointF topSecondPoint, PointF bottomFirstPoint, PointF bottomSecondPoint, float startAngle, float sweepAngle, int pointIndex )683 internal void FillPieCurve( 684 ChartArea area, 685 DataPoint point, 686 Brush brush, 687 Pen pen, 688 PointF topFirstRectPoint, 689 PointF topSecondRectPoint, 690 PointF bottomFirstRectPoint, 691 PointF bottomSecondRectPoint, 692 PointF topFirstPoint, 693 PointF topSecondPoint, 694 PointF bottomFirstPoint, 695 PointF bottomSecondPoint, 696 float startAngle, 697 float sweepAngle, 698 int pointIndex 699 ) 700 { 701 // Common Elements 702 CommonElements common = area.Common; 703 704 // Create a graphics path 705 using (GraphicsPath path = new GraphicsPath()) 706 { 707 708 // It is enough to transform only two points from 709 // rectangle. This code will create RectangleF from 710 // top left and bottom right points. 711 RectangleF pieTopRectangle = new RectangleF(); 712 pieTopRectangle.X = topFirstRectPoint.X; 713 pieTopRectangle.Y = topFirstRectPoint.Y; 714 pieTopRectangle.Height = topSecondRectPoint.Y - topFirstRectPoint.Y; 715 pieTopRectangle.Width = topSecondRectPoint.X - topFirstRectPoint.X; 716 717 RectangleF pieBottomRectangle = new RectangleF(); 718 pieBottomRectangle.X = bottomFirstRectPoint.X; 719 pieBottomRectangle.Y = bottomFirstRectPoint.Y; 720 pieBottomRectangle.Height = bottomSecondRectPoint.Y - bottomFirstRectPoint.Y; 721 pieBottomRectangle.Width = bottomSecondRectPoint.X - bottomFirstRectPoint.X; 722 723 // Angle correction algorithm. After rotation AddArc method should used 724 // different transformed angles. This method transforms angles. 725 double angleCorrection = pieTopRectangle.Height / pieTopRectangle.Width; 726 727 float endAngle; 728 endAngle = AngleCorrection(startAngle + sweepAngle, angleCorrection); 729 startAngle = AngleCorrection(startAngle, angleCorrection); 730 731 sweepAngle = endAngle - startAngle; 732 733 // Add Line between first points 734 path.AddLine(topFirstPoint, bottomFirstPoint); 735 736 if (pieBottomRectangle.Height <= 0) 737 { 738 // If x angle is 0 this arc will be line in projection. 739 path.AddLine(bottomFirstPoint.X, bottomFirstPoint.Y, bottomSecondPoint.X, bottomSecondPoint.Y); 740 } 741 else 742 { 743 // Add Arc 744 path.AddArc(pieBottomRectangle.X, pieBottomRectangle.Y, pieBottomRectangle.Width, pieBottomRectangle.Height, startAngle, sweepAngle); 745 } 746 747 // Add Line between second points 748 path.AddLine(bottomSecondPoint, topSecondPoint); 749 750 if (pieTopRectangle.Height <= 0) 751 { 752 // If x angle is 0 this arc will be line in projection. 753 path.AddLine(topFirstPoint.X, topFirstPoint.Y, topSecondPoint.X, topSecondPoint.Y); 754 } 755 else 756 { 757 path.AddArc(pieTopRectangle.X, pieTopRectangle.Y, pieTopRectangle.Width, pieTopRectangle.Height, startAngle + sweepAngle, -sweepAngle); 758 } 759 760 if (common.ProcessModePaint) 761 { 762 // Drawing Mode 763 FillPath(brush, path); 764 765 if (point.BorderColor != Color.Empty && 766 point.BorderWidth > 0 && 767 point.BorderDashStyle != ChartDashStyle.NotSet) 768 { 769 DrawGraphicsPath(pen, path); 770 } 771 772 } 773 if (common.ProcessModeRegions) 774 { 775 776 777 // Check if processing collected data point 778 if (point.IsCustomPropertySet("_COLLECTED_DATA_POINT")) 779 { 780 // Add point to the map area 781 common.HotRegionsList.AddHotRegion( 782 (ChartGraphics)this, 783 path, 784 false, 785 point.ReplaceKeywords(point.ToolTip), 786 #if Microsoft_CONTROL 787 string.Empty, 788 string.Empty, 789 string.Empty, 790 #else // Microsoft_CONTROL 791 point.ReplaceKeywords(point.Url), 792 point.ReplaceKeywords(point.MapAreaAttributes), 793 point.ReplaceKeywords(point.PostBackValue), 794 #endif // Microsoft_CONTROL 795 point, 796 ChartElementType.DataPoint); 797 798 return; 799 } 800 801 802 803 common.HotRegionsList.AddHotRegion( 804 path, 805 false, 806 (ChartGraphics)this, 807 point, 808 point.series.Name, 809 pointIndex); 810 } 811 } 812 } 813 814 /// <summary> 815 /// This method draws projection of 3D pie slice. 816 /// </summary> 817 /// <param name="area">Chart area used for drawing</param> 818 /// <param name="point">Data point which creates this pie slice</param> 819 /// <param name="brush">Graphic Brush used for drawing</param> 820 /// <param name="pen">Graphic Pen used for drawing</param> 821 /// <param name="firstRectPoint">The first point of transformed bounding rectangle</param> 822 /// <param name="firstPoint">The first arc point of pie slice</param> 823 /// <param name="secondRectPoint">The second point of transformed bounding rectangle</param> 824 /// <param name="secondPoint">The second arc point of pie slice</param> 825 /// <param name="center">The center point of pie slice</param> 826 /// <param name="startAngle">Start angle of pie slice</param> 827 /// <param name="sweepAngle">The end angle of pie slice</param> 828 /// <param name="fill">Fill pie slice with brush</param> 829 /// <param name="pointIndex"></param> FillPieSlice( ChartArea area, DataPoint point, SolidBrush brush, Pen pen, PointF firstRectPoint, PointF firstPoint, PointF secondRectPoint, PointF secondPoint, PointF center, float startAngle, float sweepAngle, bool fill, int pointIndex )830 internal void FillPieSlice( ChartArea area, DataPoint point, SolidBrush brush, Pen pen, PointF firstRectPoint, PointF firstPoint, PointF secondRectPoint, PointF secondPoint, PointF center, float startAngle, float sweepAngle, bool fill, int pointIndex ) 831 { 832 // Common elements 833 CommonElements common = area.Common; 834 835 // Create a graphics path 836 using (GraphicsPath path = new GraphicsPath()) 837 { 838 839 // It is enough to transform only two points from 840 // rectangle. This code will create RectangleF from 841 // top left and bottom right points. 842 RectangleF pieRectangle = new RectangleF(); 843 pieRectangle.X = firstRectPoint.X; 844 pieRectangle.Y = firstRectPoint.Y; 845 pieRectangle.Height = secondRectPoint.Y - firstRectPoint.Y; 846 pieRectangle.Width = secondRectPoint.X - firstRectPoint.X; 847 848 // Angle correction algorithm. After rotation AddArc method should used 849 // different transformed angles. This method transforms angles. 850 double angleCorrection = pieRectangle.Height / pieRectangle.Width; 851 852 float endAngle; 853 endAngle = AngleCorrection(startAngle + sweepAngle, angleCorrection); 854 startAngle = AngleCorrection(startAngle, angleCorrection); 855 856 sweepAngle = endAngle - startAngle; 857 858 // Add Line between The Center and Arc 859 path.AddLine(center, firstPoint); 860 861 // Add Arc 862 if (pieRectangle.Height > 0) 863 { 864 // If x angle is 0 this arc will be line in projection. 865 path.AddArc(pieRectangle.X, pieRectangle.Y, pieRectangle.Width, pieRectangle.Height, startAngle, sweepAngle); 866 } 867 868 // Add Line between the end of the arc and the centre. 869 path.AddLine(secondPoint, center); 870 871 if (common.ProcessModePaint) 872 { 873 // Get surface colors 874 Color frontLightColor, leftLightColor, topLightColor, backLightColor, rightLightColor, bottomLightColor; 875 area.matrix3D.GetLight(brush.Color, out frontLightColor, out backLightColor, out leftLightColor, out rightLightColor, out topLightColor, out bottomLightColor); 876 877 Pen newPen = (Pen)pen.Clone(); 878 879 if (area.Area3DStyle.LightStyle == LightStyle.Realistic && point.BorderColor == Color.Empty) 880 { 881 newPen.Color = frontLightColor; 882 } 883 884 // Drawing Mode 885 if (fill) 886 { 887 using (Brush lightBrush = new SolidBrush(frontLightColor)) 888 { 889 FillPath(lightBrush, path); 890 } 891 } 892 893 if (point.BorderColor != Color.Empty && 894 point.BorderWidth > 0 && 895 point.BorderDashStyle != ChartDashStyle.NotSet) 896 { 897 DrawGraphicsPath(newPen, path); 898 } 899 } 900 901 if (common.ProcessModeRegions && fill) 902 { 903 904 905 // Check if processing collected data point 906 if (point.IsCustomPropertySet("_COLLECTED_DATA_POINT")) 907 { 908 // Add point to the map area 909 common.HotRegionsList.AddHotRegion( 910 (ChartGraphics)this, 911 path, 912 false, 913 point.ReplaceKeywords(point.ToolTip), 914 #if Microsoft_CONTROL 915 string.Empty, 916 string.Empty, 917 string.Empty, 918 #else // Microsoft_CONTROL 919 point.ReplaceKeywords(point.Url), 920 point.ReplaceKeywords(point.MapAreaAttributes), 921 point.ReplaceKeywords(point.PostBackValue), 922 #endif // Microsoft_CONTROL 923 point, 924 ChartElementType.DataPoint); 925 926 return; 927 } 928 929 930 931 common.HotRegionsList.AddHotRegion(path, false, (ChartGraphics)this, point, point.series.Name, pointIndex); 932 } 933 } 934 } 935 936 /// <summary> 937 /// This method draws projection of 3D pie slice. 938 /// </summary> 939 /// <param name="area">Chart area used for drawing</param> 940 /// <param name="point">Data point which creates this Doughnut slice</param> 941 /// <param name="brush">Graphic Brush used for drawing</param> 942 /// <param name="pen">Graphic Pen used for drawing</param> 943 /// <param name="firstRectPoint">The first point of transformed bounding rectangle</param> 944 /// <param name="firstPoint">The first arc point of Doughnut slice</param> 945 /// <param name="secondRectPoint">The second point of transformed bounding rectangle</param> 946 /// <param name="secondPoint">The second arc point of Doughnut slice</param> 947 /// <param name="threePoint">The three point of Doughnut slice</param> 948 /// <param name="fourPoint">The four point of Doughnut slice</param> 949 /// <param name="startAngle">Start angle of Doughnut slice</param> 950 /// <param name="sweepAngle">The end angle of Doughnut slice</param> 951 /// <param name="fill">Fill Doughnut slice with brush</param> 952 /// <param name="doughnutRadius">Radius for doughnut chart</param> 953 /// <param name="pointIndex">Data Point Index</param> FillDoughnutSlice( ChartArea area, DataPoint point, SolidBrush brush, Pen pen, PointF firstRectPoint, PointF firstPoint, PointF secondRectPoint, PointF secondPoint, PointF threePoint, PointF fourPoint, float startAngle, float sweepAngle, bool fill, float doughnutRadius, int pointIndex )954 internal void FillDoughnutSlice( ChartArea area, DataPoint point, SolidBrush brush, Pen pen, PointF firstRectPoint, PointF firstPoint, PointF secondRectPoint, PointF secondPoint, PointF threePoint, PointF fourPoint, float startAngle, float sweepAngle, bool fill, float doughnutRadius, int pointIndex ) 955 { 956 // Common Elements 957 CommonElements common = area.Common; 958 959 doughnutRadius = 1F - doughnutRadius / 100F; 960 961 // Create a graphics path 962 using (GraphicsPath path = new GraphicsPath()) 963 { 964 965 // It is enough to transform only two points from 966 // rectangle. This code will create RectangleF from 967 // top left and bottom right points. 968 RectangleF pieRectangle = new RectangleF(); 969 pieRectangle.X = firstRectPoint.X; 970 pieRectangle.Y = firstRectPoint.Y; 971 pieRectangle.Height = secondRectPoint.Y - firstRectPoint.Y; 972 pieRectangle.Width = secondRectPoint.X - firstRectPoint.X; 973 974 RectangleF pieDoughnutRectangle = new RectangleF(); 975 pieDoughnutRectangle.X = pieRectangle.X + pieRectangle.Width * (1F - doughnutRadius) / 2F; 976 pieDoughnutRectangle.Y = pieRectangle.Y + pieRectangle.Height * (1F - doughnutRadius) / 2F; 977 pieDoughnutRectangle.Height = pieRectangle.Height * doughnutRadius; 978 pieDoughnutRectangle.Width = pieRectangle.Width * doughnutRadius; 979 980 // Angle correction algorithm. After rotation AddArc method should used 981 // different transformed angles. This method transforms angles. 982 double angleCorrection = pieRectangle.Height / pieRectangle.Width; 983 984 float endAngle; 985 endAngle = AngleCorrection(startAngle + sweepAngle, angleCorrection); 986 startAngle = AngleCorrection(startAngle, angleCorrection); 987 988 sweepAngle = endAngle - startAngle; 989 990 // Add Line between The Doughnut Arc and Arc 991 path.AddLine(fourPoint, firstPoint); 992 993 // Add Arc 994 if (pieRectangle.Height > 0) 995 { 996 // If x angle is 0 this arc will be line in projection. 997 path.AddArc(pieRectangle.X, pieRectangle.Y, pieRectangle.Width, pieRectangle.Height, startAngle, sweepAngle); 998 } 999 1000 // Add Line between the end of the arc and The Doughnut Arc. 1001 path.AddLine(secondPoint, threePoint); 1002 1003 // Add Doughnut Arc 1004 if (pieDoughnutRectangle.Height > 0) 1005 { 1006 path.AddArc(pieDoughnutRectangle.X, pieDoughnutRectangle.Y, pieDoughnutRectangle.Width, pieDoughnutRectangle.Height, startAngle + sweepAngle, -sweepAngle); 1007 } 1008 1009 if (common.ProcessModePaint) 1010 { 1011 // Get surface colors 1012 Color frontLightColor, leftLightColor, topLightColor, backLightColor, rightLightColor, bottomLightColor; 1013 area.matrix3D.GetLight(brush.Color, out frontLightColor, out backLightColor, out leftLightColor, out rightLightColor, out topLightColor, out bottomLightColor); 1014 1015 Pen newPen = (Pen)pen.Clone(); 1016 1017 if (area.Area3DStyle.LightStyle == LightStyle.Realistic && point.BorderColor == Color.Empty) 1018 { 1019 newPen.Color = frontLightColor; 1020 } 1021 1022 // Drawing Mode 1023 if (fill) 1024 { 1025 using (Brush lightBrush = new SolidBrush(frontLightColor)) 1026 { 1027 FillPath(lightBrush, path); 1028 } 1029 } 1030 1031 if (point.BorderColor != Color.Empty && 1032 point.BorderWidth > 0 && 1033 point.BorderDashStyle != ChartDashStyle.NotSet) 1034 { 1035 DrawGraphicsPath(newPen, path); 1036 } 1037 } 1038 1039 if (common.ProcessModeRegions && fill) 1040 { 1041 1042 1043 // Check if processing collected data point 1044 if (point.IsCustomPropertySet("_COLLECTED_DATA_POINT")) 1045 { 1046 // Add point to the map area 1047 common.HotRegionsList.AddHotRegion( 1048 (ChartGraphics)this, 1049 path, 1050 false, 1051 point.ReplaceKeywords(point.ToolTip), 1052 #if Microsoft_CONTROL 1053 string.Empty, 1054 string.Empty, 1055 string.Empty, 1056 #else // Microsoft_CONTROL 1057 point.ReplaceKeywords(point.Url), 1058 point.ReplaceKeywords(point.MapAreaAttributes), 1059 point.ReplaceKeywords(point.PostBackValue), 1060 #endif // Microsoft_CONTROL 1061 point, 1062 ChartElementType.DataPoint); 1063 1064 return; 1065 } 1066 1067 1068 1069 // Add points to the map area 1070 common.HotRegionsList.AddHotRegion( 1071 path, 1072 false, 1073 (ChartGraphics)this, 1074 point, 1075 point.series.Name, 1076 pointIndex); 1077 } 1078 } 1079 } 1080 1081 /// <summary> 1082 /// Draw Graphics Path. This method is introduced because of 1083 /// bug in DrawPath method when Pen Width is bigger then 1. 1084 /// </summary> 1085 /// <param name="pen">Pen</param> 1086 /// <param name="path">Graphics Path</param> DrawGraphicsPath( Pen pen, GraphicsPath path )1087 private void DrawGraphicsPath( Pen pen, GraphicsPath path ) 1088 { 1089 // Normal case. Very fast Drawing. 1090 if( pen.Width < 2 ) 1091 { 1092 DrawPath( pen, path ); 1093 } 1094 else 1095 { 1096 1097 // Converts each curve in this path into a sequence 1098 // of connected line segments. Slow Drawing. 1099 path.Flatten(); 1100 1101 // Set Pen cap 1102 pen.EndCap = LineCap.Round; 1103 pen.StartCap = LineCap.Round; 1104 1105 PointF [] pathPoints; 1106 1107 pathPoints = path.PathPoints; 1108 1109 // Draw any segment as a line. 1110 for( int point = 0; point < path.PathPoints.Length - 1; point++ ) 1111 { 1112 PointF [] points; 1113 1114 points = new PointF[2]; 1115 points[0] = pathPoints[point]; 1116 points[1] = pathPoints[point+1]; 1117 1118 DrawLine( pen, points[0], points[1] ); 1119 } 1120 } 1121 } 1122 1123 /// <summary> 1124 /// Angle correction algorithm. After rotation different 1125 /// transformed angle should be used. This method transforms angles. 1126 /// </summary> 1127 /// <param name="angle">Not transformed angle</param> 1128 /// <param name="correction">Correction of bounding rectangle (change between width and height)</param> 1129 /// <returns>Transformed angle</returns> AngleCorrection( float angle, double correction )1130 private float AngleCorrection( float angle, double correction ) 1131 { 1132 // Make all angles to be between -90 and 90. 1133 if( angle > -90 && angle < 90 ) 1134 { 1135 angle = (float)(Math.Atan( Math.Tan( ( angle ) * Math.PI / 180 ) * correction ) * 180 / Math.PI); 1136 } 1137 else if( angle > -270 && angle < -90 ) 1138 { 1139 angle = angle + 180; 1140 angle = (float)(Math.Atan( Math.Tan( ( angle ) * Math.PI / 180 ) * correction ) * 180 / Math.PI); 1141 angle = angle - 180; 1142 } 1143 else if( angle > 90 && angle < 270 ) 1144 { 1145 angle = angle - 180; 1146 angle = (float)(Math.Atan( Math.Tan( ( angle ) * Math.PI / 180 ) * correction ) * 180 / Math.PI); 1147 angle = angle + 180; 1148 } 1149 else if( angle > 270 && angle < 450 ) 1150 { 1151 angle = angle - 360; 1152 angle = (float)(Math.Atan( Math.Tan( ( angle ) * Math.PI / 180 ) * correction ) * 180 / Math.PI); 1153 angle = angle + 360; 1154 } 1155 else if( angle > 450 ) 1156 { 1157 angle = angle - 540; 1158 angle = (float)(Math.Atan( Math.Tan( ( angle ) * Math.PI / 180 ) * correction ) * 180 / Math.PI); 1159 angle = angle + 540; 1160 } 1161 return angle; 1162 } 1163 1164 #endregion 1165 1166 #region 3D Surface drawing methods (used in Line charts) 1167 1168 /// <summary> 1169 /// Draws a 3D polygon defined by 4 points in 2D space. 1170 /// </summary> 1171 /// <param name="area">Chart area reference.</param> 1172 /// <param name="matrix">Coordinates transformation matrix.</param> 1173 /// <param name="surfaceName">Name of the surface to draw.</param> 1174 /// <param name="positionZ">Z position of the back side of the 3D surface.</param> 1175 /// <param name="backColor">Color of rectangle</param> 1176 /// <param name="borderColor">Border Color</param> 1177 /// <param name="borderWidth">Border Width</param> 1178 /// <param name="firstPoint">First point.</param> 1179 /// <param name="secondPoint">Second point.</param> 1180 /// <param name="thirdPoint">Third point.</param> 1181 /// <param name="fourthPoint">Fourth point.</param> 1182 /// <param name="operationType">AxisName of operation Drawing, Calculating Path or Both</param> 1183 /// <param name="lineSegmentType">AxisName of line segment. Used for step lines and splines.</param> 1184 /// <param name="thinBorders">Thin border will be drawn on specified sides.</param> 1185 /// <returns>Returns elemnt shape path if operationType parameter is set to CalcElementPath, otherwise Null.</returns> Draw3DPolygon( ChartArea area, Matrix3D matrix, SurfaceNames surfaceName, float positionZ, Color backColor, Color borderColor, int borderWidth, DataPoint3D firstPoint, DataPoint3D secondPoint, DataPoint3D thirdPoint, DataPoint3D fourthPoint, DrawingOperationTypes operationType, LineSegmentType lineSegmentType, SurfaceNames thinBorders)1186 internal GraphicsPath Draw3DPolygon( 1187 ChartArea area, 1188 Matrix3D matrix, 1189 SurfaceNames surfaceName, 1190 float positionZ, 1191 Color backColor, 1192 Color borderColor, 1193 int borderWidth, 1194 DataPoint3D firstPoint, 1195 DataPoint3D secondPoint, 1196 DataPoint3D thirdPoint, 1197 DataPoint3D fourthPoint, 1198 DrawingOperationTypes operationType, 1199 LineSegmentType lineSegmentType, 1200 SurfaceNames thinBorders) 1201 { 1202 // Create graphics path for selection 1203 bool drawElements = ((operationType & DrawingOperationTypes.DrawElement) == DrawingOperationTypes.DrawElement); 1204 GraphicsPath resultPath = ((operationType & DrawingOperationTypes.CalcElementPath) == DrawingOperationTypes.CalcElementPath) 1205 ? new GraphicsPath() : null; 1206 1207 //********************************************************************** 1208 //** Prepare, transform polygon coordinates 1209 //********************************************************************** 1210 1211 // Define 4 points polygon 1212 Point3D [] points3D = new Point3D[4]; 1213 points3D[0] = new Point3D((float)firstPoint.xPosition, (float)firstPoint.yPosition, positionZ); 1214 points3D[1] = new Point3D((float)secondPoint.xPosition, (float)secondPoint.yPosition, positionZ); 1215 points3D[2] = new Point3D((float)thirdPoint.xPosition, (float)thirdPoint.yPosition, positionZ); 1216 points3D[3] = new Point3D((float)fourthPoint.xPosition, (float)fourthPoint.yPosition, positionZ); 1217 1218 // Transform coordinates 1219 matrix.TransformPoints( points3D ); 1220 1221 // Get absolute coordinates and create array of PointF 1222 PointF[] polygonPoints = new PointF[4]; 1223 polygonPoints[0] = GetAbsolutePoint(points3D[0].PointF); 1224 polygonPoints[1] = GetAbsolutePoint(points3D[1].PointF); 1225 polygonPoints[2] = GetAbsolutePoint(points3D[2].PointF); 1226 polygonPoints[3] = GetAbsolutePoint(points3D[3].PointF); 1227 1228 1229 //********************************************************************** 1230 //** Define drawing colors 1231 //********************************************************************** 1232 bool topIsVisible = IsSurfaceVisible( points3D[0], points3D[1], points3D[2]); 1233 Color polygonColor = matrix.GetPolygonLight( points3D, backColor, topIsVisible, area.Area3DStyle.Rotation, surfaceName, area.ReverseSeriesOrder ); 1234 Color surfaceBorderColor = borderColor; 1235 if(surfaceBorderColor == Color.Empty) 1236 { 1237 // If border color is emty use color slightly darker than main back color 1238 surfaceBorderColor = ChartGraphics.GetGradientColor( backColor, Color.Black, 0.2 ); 1239 } 1240 1241 //********************************************************************** 1242 //** Draw elements if required. 1243 //********************************************************************** 1244 Pen thickBorderPen = null; 1245 if(drawElements) 1246 { 1247 // Remember SmoothingMode and turn off anti aliasing 1248 SmoothingMode oldSmoothingMode = SmoothingMode; 1249 SmoothingMode = SmoothingMode.Default; 1250 1251 // Draw the polygon 1252 using (Brush brush = new SolidBrush(polygonColor)) 1253 { 1254 FillPolygon(brush, polygonPoints); 1255 } 1256 1257 // Return old smoothing mode 1258 SmoothingMode = oldSmoothingMode; 1259 1260 // Draw thin polygon border of darker color around the whole polygon 1261 if(thinBorders != 0) 1262 { 1263 Pen thinLinePen = new Pen(surfaceBorderColor, 1); 1264 if( (thinBorders & SurfaceNames.Left) != 0 ) 1265 DrawLine(thinLinePen, polygonPoints[3], polygonPoints[0]); 1266 if( (thinBorders & SurfaceNames.Right) != 0 ) 1267 DrawLine(thinLinePen, polygonPoints[1], polygonPoints[2]); 1268 if( (thinBorders & SurfaceNames.Top) != 0 ) 1269 DrawLine(thinLinePen, polygonPoints[0], polygonPoints[1]); 1270 if( (thinBorders & SurfaceNames.Bottom) != 0 ) 1271 DrawLine(thinLinePen, polygonPoints[2], polygonPoints[3]); 1272 } 1273 else if(polygonColor.A == 255) 1274 { 1275 DrawPolygon(new Pen(polygonColor, 1), polygonPoints); 1276 } 1277 1278 // Create thick border line pen 1279 thickBorderPen = new Pen(surfaceBorderColor, borderWidth); 1280 thickBorderPen.StartCap = LineCap.Round; 1281 thickBorderPen.EndCap = LineCap.Round; 1282 1283 // Draw thick Top & Bottom lines 1284 DrawLine(thickBorderPen, polygonPoints[0], polygonPoints[1]); 1285 DrawLine(thickBorderPen, polygonPoints[2], polygonPoints[3]); 1286 1287 // Draw thick Right & Left lines on first & last segments of the line 1288 if(lineSegmentType == LineSegmentType.First) 1289 { 1290 DrawLine(thickBorderPen, polygonPoints[3], polygonPoints[0]); 1291 } 1292 else if(lineSegmentType == LineSegmentType.Last) 1293 { 1294 DrawLine(thickBorderPen, polygonPoints[1], polygonPoints[2]); 1295 } 1296 } 1297 1298 //********************************************************************** 1299 //** Redraw front line of the previuos line segment. 1300 //********************************************************************** 1301 if(area.Area3DStyle.Perspective == 0) 1302 { 1303 if(frontLinePoint1 != PointF.Empty && frontLinePen != null) 1304 { 1305 if( (frontLinePoint1.X == polygonPoints[0].X && 1306 frontLinePoint1.Y == polygonPoints[0].Y || 1307 frontLinePoint2.X == polygonPoints[1].X && 1308 frontLinePoint2.Y == polygonPoints[1].Y ) || 1309 1310 (frontLinePoint1.X == polygonPoints[1].X && 1311 frontLinePoint1.Y == polygonPoints[1].Y || 1312 frontLinePoint2.X == polygonPoints[0].X && 1313 frontLinePoint2.Y == polygonPoints[0].Y ) || 1314 1315 (frontLinePoint1.X == polygonPoints[3].X && 1316 frontLinePoint1.Y == polygonPoints[3].Y || 1317 frontLinePoint2.X == polygonPoints[2].X && 1318 frontLinePoint2.Y == polygonPoints[2].Y) || 1319 1320 (frontLinePoint1.X == polygonPoints[2].X && 1321 frontLinePoint1.Y == polygonPoints[2].Y || 1322 frontLinePoint2.X == polygonPoints[3].X && 1323 frontLinePoint2.Y == polygonPoints[3].Y) ) 1324 { 1325 // Do not draw the line if it will be overlapped with current 1326 } 1327 else 1328 { 1329 // Draw line !!!! 1330 DrawLine( 1331 frontLinePen, 1332 (float)Math.Round(frontLinePoint1.X), 1333 (float)Math.Round(frontLinePoint1.Y), 1334 (float)Math.Round(frontLinePoint2.X), 1335 (float)Math.Round(frontLinePoint2.Y) ); 1336 } 1337 1338 // Reset line properties 1339 frontLinePen = null; 1340 frontLinePoint1 = PointF.Empty; 1341 frontLinePoint2 = PointF.Empty; 1342 } 1343 1344 //********************************************************************** 1345 //** Check if front line should be redrawn whith the next segment. 1346 //********************************************************************** 1347 if(drawElements) 1348 { 1349 // Add top line 1350 frontLinePen = thickBorderPen; 1351 frontLinePoint1 = polygonPoints[0]; 1352 frontLinePoint2 = polygonPoints[1]; 1353 } 1354 } 1355 1356 // Calculate path for selection 1357 if(resultPath != null) 1358 { 1359 // Add polygon to the path 1360 resultPath.AddPolygon(polygonPoints); 1361 } 1362 1363 return resultPath; 1364 } 1365 1366 /// <summary> 1367 /// Helper method which returns the splines flatten path. 1368 /// </summary> 1369 /// <param name="area">Chart area reference.</param> 1370 /// <param name="positionZ">Z position of the back side of the 3D surface.</param> 1371 /// <param name="firstPoint">First point.</param> 1372 /// <param name="secondPoint">Second point.</param> 1373 /// <param name="points">Array of points.</param> 1374 /// <param name="tension">Line tension.</param> 1375 /// <param name="flatten">Flatten result path.</param> 1376 /// <param name="translateCoordinates">Indicates that points coordinates should be translated.</param> 1377 /// <param name="yValueIndex">Index of the Y value to use.</param> 1378 /// <returns>Spline path.</returns> GetSplineFlattenPath( ChartArea area, float positionZ, DataPoint3D firstPoint, DataPoint3D secondPoint, ArrayList points, float tension, bool flatten, bool translateCoordinates, int yValueIndex)1379 internal GraphicsPath GetSplineFlattenPath( 1380 ChartArea area, 1381 float positionZ, 1382 DataPoint3D firstPoint, 1383 DataPoint3D secondPoint, 1384 ArrayList points, 1385 float tension, 1386 bool flatten, 1387 bool translateCoordinates, 1388 int yValueIndex) 1389 { 1390 // Find first spline point index 1391 int firtsSplinePointIndex = (firstPoint.index < secondPoint.index) ? firstPoint.index : secondPoint.index; 1392 --firtsSplinePointIndex; 1393 if(firtsSplinePointIndex >= (points.Count - 2) ) 1394 { 1395 --firtsSplinePointIndex; 1396 } 1397 if(firtsSplinePointIndex < 1) 1398 { 1399 firtsSplinePointIndex = 1; 1400 } 1401 1402 // Find four points which are required to draw the spline 1403 int pointArrayIndex = int.MinValue; 1404 DataPoint3D [] splineDataPoints = new DataPoint3D[4]; 1405 splineDataPoints[0] = FindPointByIndex(points, firtsSplinePointIndex, null, ref pointArrayIndex); 1406 splineDataPoints[1] = FindPointByIndex(points, firtsSplinePointIndex + 1, null, ref pointArrayIndex); 1407 splineDataPoints[2] = FindPointByIndex(points, firtsSplinePointIndex + 2, null, ref pointArrayIndex); 1408 splineDataPoints[3] = FindPointByIndex(points, firtsSplinePointIndex + 3, null, ref pointArrayIndex); 1409 1410 // Get offset of spline segment in array 1411 int splineSegmentOffset = 0; 1412 while(splineSegmentOffset < 4) 1413 { 1414 if(splineDataPoints[splineSegmentOffset].index == firstPoint.index || 1415 splineDataPoints[splineSegmentOffset].index == secondPoint.index) 1416 { 1417 break; 1418 } 1419 ++splineSegmentOffset; 1420 } 1421 1422 // Get number of found points 1423 int nonNullPoints = 2; 1424 if(splineDataPoints[2] != null) 1425 ++nonNullPoints; 1426 if(splineDataPoints[3] != null) 1427 ++nonNullPoints; 1428 1429 1430 // Get coordinates and create array of PointF for the front spline 1431 PointF[] polygonPointsFront = new PointF[nonNullPoints]; 1432 if(yValueIndex == 0) 1433 { 1434 polygonPointsFront[0] = new PointF((float)splineDataPoints[0].xPosition, (float)splineDataPoints[0].yPosition); 1435 polygonPointsFront[1] = new PointF((float)splineDataPoints[1].xPosition, (float)splineDataPoints[1].yPosition); 1436 if(nonNullPoints > 2) 1437 polygonPointsFront[2] = new PointF((float)splineDataPoints[2].xPosition, (float)splineDataPoints[2].yPosition); 1438 if(nonNullPoints > 3) 1439 polygonPointsFront[3] = new PointF((float)splineDataPoints[3].xPosition, (float)splineDataPoints[3].yPosition); 1440 } 1441 else 1442 { 1443 // Set active vertical axis 1444 Axis vAxis = (firstPoint.dataPoint.series.YAxisType == AxisType.Primary) ? area.AxisY : area.AxisY2; 1445 1446 float secondYValue = (float)vAxis.GetPosition(splineDataPoints[0].dataPoint.YValues[yValueIndex]); 1447 polygonPointsFront[0] = new PointF((float)splineDataPoints[0].xPosition, secondYValue); 1448 secondYValue = (float)vAxis.GetPosition(splineDataPoints[1].dataPoint.YValues[yValueIndex]); 1449 polygonPointsFront[1] = new PointF((float)splineDataPoints[1].xPosition, secondYValue); 1450 if(nonNullPoints > 2) 1451 { 1452 secondYValue = (float)vAxis.GetPosition(splineDataPoints[2].dataPoint.YValues[yValueIndex]); 1453 polygonPointsFront[2] = new PointF((float)splineDataPoints[2].xPosition, secondYValue); 1454 } 1455 if(nonNullPoints > 3) 1456 { 1457 secondYValue = (float)vAxis.GetPosition(splineDataPoints[3].dataPoint.YValues[yValueIndex]); 1458 polygonPointsFront[3] = new PointF((float)splineDataPoints[3].xPosition, secondYValue); 1459 } 1460 } 1461 1462 // Translate points coordinates in 3D space and get absolute coordinate 1463 if(translateCoordinates) 1464 { 1465 // Prepare array of points 1466 Point3D[] points3D = new Point3D[nonNullPoints]; 1467 for(int index = 0; index < nonNullPoints; index++) 1468 { 1469 points3D[index] = new Point3D(polygonPointsFront[index].X, polygonPointsFront[index].Y, positionZ); 1470 } 1471 1472 // Make coordinates transformation 1473 area.matrix3D.TransformPoints( points3D ); 1474 1475 // Get absolute values 1476 for(int index = 0; index < nonNullPoints; index++) 1477 { 1478 polygonPointsFront[index] = GetAbsolutePoint(points3D[index].PointF); 1479 } 1480 1481 } 1482 1483 // Create graphics path for the front spline surface and flatten it. 1484 GraphicsPath splineSurfacePath = new GraphicsPath(); 1485 splineSurfacePath.AddCurve(polygonPointsFront, splineSegmentOffset, 1, tension); 1486 if(flatten) 1487 { 1488 splineSurfacePath.Flatten(); 1489 } 1490 1491 // IsReversed points order 1492 if(firstPoint.index > secondPoint.index) 1493 { 1494 splineSurfacePath.Reverse(); 1495 } 1496 1497 return splineSurfacePath; 1498 } 1499 1500 /// <summary> 1501 /// Draws a 3D spline surface connecting the two specified points in 2D space. 1502 /// Used to draw Spline based charts. 1503 /// </summary> 1504 /// <param name="area">Chart area reference.</param> 1505 /// <param name="matrix">Coordinates transformation matrix.</param> 1506 /// <param name="lightStyle">LightStyle style (None, Simplistic, Realistic).</param> 1507 /// <param name="surfaceName">Name of the surface to draw.</param> 1508 /// <param name="positionZ">Z position of the back side of the 3D surface.</param> 1509 /// <param name="depth">Depth of the 3D surface.</param> 1510 /// <param name="backColor">Color of rectangle</param> 1511 /// <param name="borderColor">Border Color</param> 1512 /// <param name="borderWidth">Border Width</param> 1513 /// <param name="borderDashStyle">Border Style</param> 1514 /// <param name="firstPoint">First point.</param> 1515 /// <param name="secondPoint">Second point.</param> 1516 /// <param name="points">Array of points.</param> 1517 /// <param name="pointIndex">Index of point to draw.</param> 1518 /// <param name="tension">Line tension.</param> 1519 /// <param name="operationType">AxisName of operation Drawing, Calculating Path or Both</param> 1520 /// <param name="forceThinBorder">Thin border will be drawn on all segments.</param> 1521 /// <param name="forceThickBorder">Thick border will be drawn on all segments.</param> 1522 /// <param name="reversedSeriesOrder">Series are drawn in reversed order.</param> 1523 /// <param name="multiSeries">Multiple series are drawn at the same time.</param> 1524 /// <param name="yValueIndex">Index of the Y value to use.</param> 1525 /// <param name="clipInsideArea">Surface should be clipped inside plotting area.</param> 1526 /// <returns>Returns elemnt shape path if operationType parameter is set to CalcElementPath, otherwise Null.</returns> Draw3DSplineSurface( ChartArea area, Matrix3D matrix, LightStyle lightStyle, SurfaceNames surfaceName, float positionZ, float depth, Color backColor, Color borderColor, int borderWidth, ChartDashStyle borderDashStyle, DataPoint3D firstPoint, DataPoint3D secondPoint, ArrayList points, int pointIndex, float tension, DrawingOperationTypes operationType, bool forceThinBorder, bool forceThickBorder, bool reversedSeriesOrder, bool multiSeries, int yValueIndex, bool clipInsideArea)1527 internal GraphicsPath Draw3DSplineSurface( 1528 ChartArea area, 1529 Matrix3D matrix, 1530 LightStyle lightStyle, 1531 SurfaceNames surfaceName, 1532 float positionZ, 1533 float depth, 1534 Color backColor, 1535 Color borderColor, 1536 int borderWidth, 1537 ChartDashStyle borderDashStyle, 1538 DataPoint3D firstPoint, 1539 DataPoint3D secondPoint, 1540 ArrayList points, 1541 int pointIndex, 1542 float tension, 1543 DrawingOperationTypes operationType, 1544 bool forceThinBorder, 1545 bool forceThickBorder, 1546 bool reversedSeriesOrder, 1547 bool multiSeries, 1548 int yValueIndex, 1549 bool clipInsideArea) 1550 { 1551 // If zero tension is specified - draw a Line Surface 1552 if(tension == 0f) 1553 { 1554 return Draw3DSurface( 1555 area, 1556 matrix, 1557 lightStyle, 1558 surfaceName, 1559 positionZ, 1560 depth, 1561 backColor, 1562 borderColor, 1563 borderWidth, 1564 borderDashStyle, 1565 firstPoint, 1566 secondPoint, 1567 points, 1568 pointIndex, 1569 tension, 1570 operationType, 1571 LineSegmentType.Single, 1572 forceThinBorder, 1573 forceThickBorder, 1574 reversedSeriesOrder, 1575 multiSeries, 1576 yValueIndex, 1577 clipInsideArea); 1578 } 1579 1580 // Create graphics path for selection 1581 GraphicsPath resultPath = ((operationType & DrawingOperationTypes.CalcElementPath) == DrawingOperationTypes.CalcElementPath) 1582 ? new GraphicsPath() : null; 1583 1584 // Get spline flatten path 1585 GraphicsPath splineSurfacePath = GetSplineFlattenPath( 1586 area, positionZ, 1587 firstPoint, secondPoint, points, tension, true, false, yValueIndex); 1588 1589 // Check if reversed drawing order required 1590 bool reversed = false; 1591 if((pointIndex + 1) < points.Count) 1592 { 1593 DataPoint3D p = (DataPoint3D)points[pointIndex + 1]; 1594 if(p.index == firstPoint.index) 1595 { 1596 reversed = true; 1597 } 1598 } 1599 1600 if(reversed) 1601 { 1602 splineSurfacePath.Reverse(); 1603 } 1604 1605 // Loop through all segment lines the spline consists off 1606 PointF[] splinePathPoints = splineSurfacePath.PathPoints; 1607 DataPoint3D dp1 = new DataPoint3D(); 1608 DataPoint3D dp2 = new DataPoint3D(); 1609 LineSegmentType lineSegmentType = LineSegmentType.Middle; 1610 for(int pIndex = 1; pIndex < splinePathPoints.Length; pIndex++) 1611 { 1612 bool forceSegmentThinBorder = false; 1613 bool forceSegmentThickBorder = false; 1614 1615 // Calculate surface coordinates 1616 if(!reversed) 1617 { 1618 dp1.index = firstPoint.index; 1619 dp1.dataPoint = firstPoint.dataPoint; 1620 dp1.xPosition = splinePathPoints[pIndex - 1].X; 1621 dp1.yPosition = splinePathPoints[pIndex - 1].Y; 1622 1623 dp2.index = secondPoint.index; 1624 dp2.index = secondPoint.index; 1625 dp2.xPosition = splinePathPoints[pIndex].X; 1626 dp2.yPosition = splinePathPoints[pIndex].Y; 1627 } 1628 else 1629 { 1630 dp2.index = firstPoint.index; 1631 dp2.dataPoint = firstPoint.dataPoint; 1632 dp2.xPosition = splinePathPoints[pIndex - 1].X; 1633 dp2.yPosition = splinePathPoints[pIndex - 1].Y; 1634 1635 dp1.index = secondPoint.index; 1636 dp1.dataPoint = secondPoint.dataPoint; 1637 dp1.xPosition = splinePathPoints[pIndex].X; 1638 dp1.yPosition = splinePathPoints[pIndex].Y; 1639 } 1640 1641 // Get sefment type 1642 lineSegmentType = LineSegmentType.Middle; 1643 if(pIndex == 1) 1644 { 1645 if(!reversed) 1646 lineSegmentType = LineSegmentType.First; 1647 else 1648 lineSegmentType = LineSegmentType.Last; 1649 1650 forceSegmentThinBorder = forceThinBorder; 1651 forceSegmentThickBorder = forceThickBorder; 1652 } 1653 else if(pIndex == splinePathPoints.Length - 1) 1654 { 1655 if(!reversed) 1656 lineSegmentType = LineSegmentType.Last; 1657 else 1658 lineSegmentType = LineSegmentType.First; 1659 1660 forceSegmentThinBorder = forceThinBorder; 1661 forceSegmentThickBorder = forceThickBorder; 1662 } 1663 1664 // Draw flat surface 1665 GraphicsPath segmentResultPath = Draw3DSurface( 1666 area, 1667 matrix, 1668 lightStyle, 1669 surfaceName, 1670 positionZ, 1671 depth, 1672 backColor, 1673 borderColor, 1674 borderWidth, 1675 borderDashStyle, 1676 dp1, 1677 dp2, 1678 points, 1679 pointIndex, 1680 0f, 1681 operationType, 1682 lineSegmentType, 1683 forceSegmentThinBorder, 1684 forceSegmentThickBorder, 1685 reversedSeriesOrder, 1686 multiSeries, 1687 yValueIndex, 1688 clipInsideArea); 1689 1690 // Add selection path 1691 if(resultPath != null && segmentResultPath != null && segmentResultPath.PointCount > 0) 1692 { 1693 resultPath.AddPath(segmentResultPath, true); 1694 } 1695 1696 } 1697 1698 return resultPath; 1699 } 1700 1701 1702 /// <summary> 1703 /// Draws a 3D surface connecting the two specified points in 2D space. 1704 /// Used to draw Line based charts. 1705 /// </summary> 1706 /// <param name="area">Chart area reference.</param> 1707 /// <param name="matrix">Coordinates transformation matrix.</param> 1708 /// <param name="lightStyle">LightStyle style (None, Simplistic, Realistic).</param> 1709 /// <param name="surfaceName">Name of the surface to draw.</param> 1710 /// <param name="positionZ">Z position of the back side of the 3D surface.</param> 1711 /// <param name="depth">Depth of the 3D surface.</param> 1712 /// <param name="backColor">Color of rectangle</param> 1713 /// <param name="borderColor">Border Color</param> 1714 /// <param name="borderWidth">Border Width</param> 1715 /// <param name="borderDashStyle">Border Style</param> 1716 /// <param name="firstPoint">First point.</param> 1717 /// <param name="secondPoint">Second point.</param> 1718 /// <param name="points">Array of points.</param> 1719 /// <param name="pointIndex">Index of point to draw.</param> 1720 /// <param name="tension">Line tension.</param> 1721 /// <param name="operationType">AxisName of operation Drawing, Calculating Path or Both</param> 1722 /// <param name="lineSegmentType">AxisName of line segment. Used for step lines and splines.</param> 1723 /// <param name="forceThinBorder">Thin border will be drawn on all segments.</param> 1724 /// <param name="forceThickBorder">Thick border will be drawn on all segments.</param> 1725 /// <param name="reversedSeriesOrder">Series are drawn in reversed order.</param> 1726 /// <param name="multiSeries">Multiple series are drawn at the same time.</param> 1727 /// <param name="yValueIndex">Index of the Y value to use.</param> 1728 /// <param name="clipInsideArea">Surface should be clipped inside plotting area.</param> 1729 /// <returns>Returns elemnt shape path if operationType parameter is set to CalcElementPath, otherwise Null.</returns> Draw3DSurface( ChartArea area, Matrix3D matrix, LightStyle lightStyle, SurfaceNames surfaceName, float positionZ, float depth, Color backColor, Color borderColor, int borderWidth, ChartDashStyle borderDashStyle, DataPoint3D firstPoint, DataPoint3D secondPoint, ArrayList points, int pointIndex, float tension, DrawingOperationTypes operationType, LineSegmentType lineSegmentType, bool forceThinBorder, bool forceThickBorder, bool reversedSeriesOrder, bool multiSeries, int yValueIndex, bool clipInsideArea)1730 internal GraphicsPath Draw3DSurface( 1731 ChartArea area, 1732 Matrix3D matrix, 1733 LightStyle lightStyle, 1734 SurfaceNames surfaceName, 1735 float positionZ, 1736 float depth, 1737 Color backColor, 1738 Color borderColor, 1739 int borderWidth, 1740 ChartDashStyle borderDashStyle, 1741 DataPoint3D firstPoint, 1742 DataPoint3D secondPoint, 1743 ArrayList points, 1744 int pointIndex, 1745 float tension, 1746 DrawingOperationTypes operationType, 1747 LineSegmentType lineSegmentType, 1748 bool forceThinBorder, 1749 bool forceThickBorder, 1750 bool reversedSeriesOrder, 1751 bool multiSeries, 1752 int yValueIndex, 1753 bool clipInsideArea) 1754 { 1755 // If non-zero tension is specified - draw a Spline Surface 1756 if(tension != 0f) 1757 { 1758 return Draw3DSplineSurface( 1759 area, 1760 matrix, 1761 lightStyle, 1762 surfaceName, 1763 positionZ, 1764 depth, 1765 backColor, 1766 borderColor, 1767 borderWidth, 1768 borderDashStyle, 1769 firstPoint, 1770 secondPoint, 1771 points, 1772 pointIndex, 1773 tension, 1774 operationType, 1775 forceThinBorder, 1776 forceThickBorder, 1777 reversedSeriesOrder, 1778 multiSeries, 1779 yValueIndex, 1780 clipInsideArea); 1781 } 1782 1783 //********************************************************************** 1784 //** Create graphics path for selection 1785 //********************************************************************** 1786 bool drawElements = ((operationType & DrawingOperationTypes.DrawElement) == DrawingOperationTypes.DrawElement); 1787 GraphicsPath resultPath = ((operationType & DrawingOperationTypes.CalcElementPath) == DrawingOperationTypes.CalcElementPath) 1788 ? new GraphicsPath() : null; 1789 1790 //********************************************************************** 1791 //** Check surface coordinates 1792 //********************************************************************** 1793 if((decimal)firstPoint.xPosition == (decimal)secondPoint.xPosition && 1794 (decimal)firstPoint.yPosition == (decimal)secondPoint.yPosition) 1795 { 1796 return resultPath; 1797 } 1798 1799 //********************************************************************** 1800 //** Clip surface 1801 //********************************************************************** 1802 1803 // Check if line between the first and second points intersects with 1804 // plotting area top or bottom boundary 1805 if(clipInsideArea) 1806 { 1807 //**************************************************************** 1808 //** Round plot are position and point coordinates 1809 //**************************************************************** 1810 int decimals = 3; 1811 decimal plotAreaPositionX = Math.Round((decimal)area.PlotAreaPosition.X, decimals); 1812 decimal plotAreaPositionY = Math.Round((decimal)area.PlotAreaPosition.Y, decimals); 1813 decimal plotAreaPositionRight = Math.Round((decimal)area.PlotAreaPosition.Right, decimals); 1814 decimal plotAreaPositionBottom = Math.Round((decimal)area.PlotAreaPosition.Bottom, decimals); 1815 1816 // Make area a little bit bigger 1817 plotAreaPositionX -= 0.001M; 1818 plotAreaPositionY -= 0.001M; 1819 plotAreaPositionRight += 0.001M; 1820 plotAreaPositionBottom += 0.001M; 1821 1822 // Chech data points X values 1823 if((decimal)firstPoint.xPosition < plotAreaPositionX || 1824 (decimal)firstPoint.xPosition > plotAreaPositionRight || 1825 (decimal)secondPoint.xPosition < plotAreaPositionX || 1826 (decimal)secondPoint.xPosition > plotAreaPositionRight ) 1827 { 1828 // Check if surface completly out of the plot area 1829 if((decimal)firstPoint.xPosition < plotAreaPositionX && 1830 (decimal)secondPoint.xPosition < plotAreaPositionX) 1831 { 1832 return resultPath; 1833 } 1834 // Check if surface completly out of the plot area 1835 if((decimal)firstPoint.xPosition > plotAreaPositionRight && 1836 (decimal)secondPoint.xPosition > plotAreaPositionRight) 1837 { 1838 return resultPath; 1839 } 1840 1841 // Only part of the surface is outside - fix X value and adjust Y value 1842 if((decimal)firstPoint.xPosition < plotAreaPositionX) 1843 { 1844 firstPoint.yPosition = ((double)plotAreaPositionX - secondPoint.xPosition) / 1845 (firstPoint.xPosition - secondPoint.xPosition) * 1846 (firstPoint.yPosition - secondPoint.yPosition) + 1847 secondPoint.yPosition; 1848 firstPoint.xPosition = (double)plotAreaPositionX; 1849 } 1850 else if((decimal)firstPoint.xPosition > plotAreaPositionRight) 1851 { 1852 firstPoint.yPosition = ((double)plotAreaPositionRight - secondPoint.xPosition) / 1853 (firstPoint.xPosition - secondPoint.xPosition) * 1854 (firstPoint.yPosition - secondPoint.yPosition) + 1855 secondPoint.yPosition; 1856 firstPoint.xPosition = (double)plotAreaPositionRight; 1857 } 1858 if((decimal)secondPoint.xPosition < plotAreaPositionX) 1859 { 1860 secondPoint.yPosition = ((double)plotAreaPositionX - secondPoint.xPosition) / 1861 (firstPoint.xPosition - secondPoint.xPosition) * 1862 (firstPoint.yPosition - secondPoint.yPosition) + 1863 secondPoint.yPosition; 1864 secondPoint.xPosition = (double)plotAreaPositionX; 1865 } 1866 else if((decimal)secondPoint.xPosition > plotAreaPositionRight) 1867 { 1868 secondPoint.yPosition = ((double)plotAreaPositionRight - secondPoint.xPosition) / 1869 (firstPoint.xPosition - secondPoint.xPosition) * 1870 (firstPoint.yPosition - secondPoint.yPosition) + 1871 secondPoint.yPosition; 1872 secondPoint.xPosition = (double)plotAreaPositionRight; 1873 } 1874 } 1875 1876 // Chech data points Y values 1877 if((decimal)firstPoint.yPosition < plotAreaPositionY || 1878 (decimal)firstPoint.yPosition > plotAreaPositionBottom || 1879 (decimal)secondPoint.yPosition < plotAreaPositionY || 1880 (decimal)secondPoint.yPosition > plotAreaPositionBottom ) 1881 { 1882 // Remember previous y positions 1883 double prevFirstPointY = firstPoint.yPosition; 1884 double prevSecondPointY = secondPoint.yPosition; 1885 1886 // Check if whole line is outside plotting region 1887 bool surfaceCompletlyOutside = false; 1888 if((decimal)firstPoint.yPosition < plotAreaPositionY && 1889 (decimal)secondPoint.yPosition < plotAreaPositionY) 1890 { 1891 surfaceCompletlyOutside = true; 1892 firstPoint.yPosition = (double)plotAreaPositionY; 1893 secondPoint.yPosition = (double)plotAreaPositionY; 1894 } 1895 if((decimal)firstPoint.yPosition > plotAreaPositionBottom && 1896 (decimal)secondPoint.yPosition > plotAreaPositionBottom) 1897 { 1898 surfaceCompletlyOutside = true; 1899 firstPoint.yPosition = (double)plotAreaPositionBottom; 1900 secondPoint.yPosition = (double)plotAreaPositionBottom; 1901 } 1902 1903 // Calculate color used to draw "cut" surfaces 1904 Color cutSurfaceBackColor = ChartGraphics.GetGradientColor(backColor, Color.Black, 0.5); 1905 Color cutSurfaceBorderColor = ChartGraphics.GetGradientColor(borderColor, Color.Black, 0.5); 1906 1907 // Draw just one surface 1908 if(surfaceCompletlyOutside) 1909 { 1910 resultPath = this.Draw3DSurface( 1911 area, matrix, lightStyle, surfaceName, positionZ, depth, 1912 cutSurfaceBackColor, cutSurfaceBorderColor, borderWidth, borderDashStyle, 1913 firstPoint, secondPoint, 1914 points, pointIndex, tension, operationType, lineSegmentType, 1915 forceThinBorder, forceThickBorder, reversedSeriesOrder, 1916 multiSeries, yValueIndex, clipInsideArea); 1917 1918 // Restore previous y positions 1919 firstPoint.yPosition = prevFirstPointY; 1920 secondPoint.yPosition = prevSecondPointY; 1921 1922 return resultPath; 1923 } 1924 1925 // Get intersection point 1926 DataPoint3D intersectionPoint = new DataPoint3D(); 1927 intersectionPoint.yPosition = (double)plotAreaPositionY; 1928 if((decimal)firstPoint.yPosition > plotAreaPositionBottom || 1929 (decimal)secondPoint.yPosition > plotAreaPositionBottom ) 1930 { 1931 intersectionPoint.yPosition = (double)plotAreaPositionBottom; 1932 } 1933 intersectionPoint.xPosition = (intersectionPoint.yPosition - secondPoint.yPosition) * 1934 (firstPoint.xPosition - secondPoint.xPosition) / 1935 (firstPoint.yPosition - secondPoint.yPosition) + 1936 secondPoint.xPosition; 1937 1938 // Check if there are 2 intersection points (3 segments) 1939 int segmentNumber = 2; 1940 DataPoint3D intersectionPoint2 = null; 1941 if( ((decimal)firstPoint.yPosition < plotAreaPositionY && 1942 (decimal)secondPoint.yPosition > plotAreaPositionBottom) || 1943 ((decimal)firstPoint.yPosition > plotAreaPositionBottom && 1944 (decimal)secondPoint.yPosition < plotAreaPositionY)) 1945 { 1946 segmentNumber = 3; 1947 intersectionPoint2 = new DataPoint3D(); 1948 if((decimal)intersectionPoint.yPosition == plotAreaPositionY) 1949 { 1950 intersectionPoint2.yPosition = (double)plotAreaPositionBottom; 1951 } 1952 else 1953 { 1954 intersectionPoint2.yPosition = (double)plotAreaPositionY; 1955 } 1956 intersectionPoint2.xPosition = (intersectionPoint2.yPosition - secondPoint.yPosition) * 1957 (firstPoint.xPosition - secondPoint.xPosition) / 1958 (firstPoint.yPosition - secondPoint.yPosition) + 1959 secondPoint.xPosition; 1960 1961 // Switch intersection points 1962 if((decimal)firstPoint.yPosition > plotAreaPositionBottom) 1963 { 1964 DataPoint3D tempPoint = new DataPoint3D(); 1965 tempPoint.xPosition = intersectionPoint.xPosition; 1966 tempPoint.yPosition = intersectionPoint.yPosition; 1967 intersectionPoint.xPosition = intersectionPoint2.xPosition; 1968 intersectionPoint.yPosition = intersectionPoint2.yPosition; 1969 intersectionPoint2.xPosition = tempPoint.xPosition; 1970 intersectionPoint2.yPosition = tempPoint.yPosition; 1971 } 1972 } 1973 1974 1975 // Adjust points Y values 1976 bool firstSegmentVisible = true; 1977 if((decimal)firstPoint.yPosition < plotAreaPositionY) 1978 { 1979 firstSegmentVisible = false; 1980 firstPoint.yPosition = (double)plotAreaPositionY; 1981 } 1982 else if((decimal)firstPoint.yPosition > plotAreaPositionBottom) 1983 { 1984 firstSegmentVisible = false; 1985 firstPoint.yPosition = (double)plotAreaPositionBottom; 1986 } 1987 if((decimal)secondPoint.yPosition < plotAreaPositionY) 1988 { 1989 secondPoint.yPosition = (double)plotAreaPositionY; 1990 } 1991 else if((decimal)secondPoint.yPosition > plotAreaPositionBottom) 1992 { 1993 secondPoint.yPosition = (double)plotAreaPositionBottom; 1994 } 1995 1996 // Check if reversed drawing order required 1997 bool reversed = false; 1998 if((pointIndex + 1) < points.Count) 1999 { 2000 DataPoint3D p = (DataPoint3D)points[pointIndex + 1]; 2001 if(p.index == firstPoint.index) 2002 { 2003 reversed = true; 2004 } 2005 } 2006 2007 // Draw surfaces in 2 or 3 segments 2008 for(int segmentIndex = 0; segmentIndex < 3; segmentIndex++) 2009 { 2010 GraphicsPath segmentPath = null; 2011 if(segmentIndex == 0 && !reversed || 2012 segmentIndex == 2 && reversed) 2013 { 2014 // Draw first segment 2015 if(intersectionPoint2 == null) 2016 { 2017 intersectionPoint2 = intersectionPoint; 2018 } 2019 intersectionPoint2.dataPoint = secondPoint.dataPoint; 2020 intersectionPoint2.index = secondPoint.index; 2021 2022 segmentPath = this.Draw3DSurface( 2023 area, matrix, lightStyle, surfaceName, positionZ, depth, 2024 (firstSegmentVisible && segmentNumber != 3) ? backColor : cutSurfaceBackColor, 2025 (firstSegmentVisible && segmentNumber != 3) ? borderColor : cutSurfaceBorderColor, 2026 borderWidth, borderDashStyle, 2027 firstPoint, intersectionPoint2, 2028 points, pointIndex, tension, operationType, lineSegmentType, 2029 forceThinBorder, forceThickBorder, reversedSeriesOrder, 2030 multiSeries, yValueIndex, clipInsideArea); 2031 } 2032 2033 if(segmentIndex == 1 && intersectionPoint2 != null && segmentNumber == 3) 2034 { 2035 // Draw middle segment 2036 intersectionPoint2.dataPoint = secondPoint.dataPoint; 2037 intersectionPoint2.index = secondPoint.index; 2038 2039 segmentPath = this.Draw3DSurface( 2040 area, matrix, lightStyle, surfaceName, positionZ, depth, 2041 backColor, 2042 borderColor, 2043 borderWidth, borderDashStyle, 2044 intersectionPoint, intersectionPoint2, 2045 points, pointIndex, tension, operationType, lineSegmentType, 2046 forceThinBorder, forceThickBorder, reversedSeriesOrder, 2047 multiSeries, yValueIndex, clipInsideArea); 2048 } 2049 2050 if(segmentIndex == 2 && !reversed || 2051 segmentIndex == 0 && reversed) 2052 { 2053 // Draw second segment 2054 intersectionPoint.dataPoint = firstPoint.dataPoint; 2055 intersectionPoint.index = firstPoint.index; 2056 2057 segmentPath = this.Draw3DSurface( 2058 area, matrix, lightStyle, surfaceName, positionZ, depth, 2059 (!firstSegmentVisible && segmentNumber != 3) ? backColor : cutSurfaceBackColor, 2060 (!firstSegmentVisible && segmentNumber != 3) ? borderColor : cutSurfaceBorderColor, 2061 borderWidth, borderDashStyle, 2062 intersectionPoint, secondPoint, 2063 points, pointIndex, tension, operationType, lineSegmentType, 2064 forceThinBorder, forceThickBorder, reversedSeriesOrder, 2065 multiSeries, yValueIndex, clipInsideArea); 2066 } 2067 2068 // Add segment path 2069 if(resultPath != null && segmentPath != null && segmentPath.PointCount > 0) 2070 { 2071 resultPath.SetMarkers(); 2072 resultPath.AddPath(segmentPath, true); 2073 } 2074 } 2075 2076 // Restore previous y positions 2077 firstPoint.yPosition = prevFirstPointY; 2078 secondPoint.yPosition = prevSecondPointY; 2079 2080 return resultPath; 2081 } 2082 } 2083 2084 //********************************************************************** 2085 //** Prepare, transform polygon coordinates 2086 //********************************************************************** 2087 2088 // Define 4 points polygon 2089 Point3D [] points3D = new Point3D[4]; 2090 points3D[0] = new Point3D((float)firstPoint.xPosition, (float)firstPoint.yPosition, positionZ + depth); 2091 points3D[1] = new Point3D((float)secondPoint.xPosition, (float)secondPoint.yPosition, positionZ + depth); 2092 points3D[2] = new Point3D((float)secondPoint.xPosition, (float)secondPoint.yPosition, positionZ); 2093 points3D[3] = new Point3D((float)firstPoint.xPosition, (float)firstPoint.yPosition, positionZ); 2094 2095 // Transform coordinates 2096 matrix.TransformPoints( points3D ); 2097 2098 // Get absolute coordinates and create array of PointF 2099 PointF[] polygonPoints = new PointF[4]; 2100 polygonPoints[0] = GetAbsolutePoint(points3D[0].PointF); 2101 polygonPoints[1] = GetAbsolutePoint(points3D[1].PointF); 2102 polygonPoints[2] = GetAbsolutePoint(points3D[2].PointF); 2103 polygonPoints[3] = GetAbsolutePoint(points3D[3].PointF); 2104 2105 //********************************************************************** 2106 //** Define drawing colors 2107 //********************************************************************** 2108 bool topIsVisible = IsSurfaceVisible( points3D[0], points3D[1], points3D[2]); 2109 Color polygonColor = matrix.GetPolygonLight( points3D, backColor, topIsVisible, area.Area3DStyle.Rotation, surfaceName, area.ReverseSeriesOrder ); 2110 Color surfaceBorderColor = borderColor; 2111 if(surfaceBorderColor == Color.Empty) 2112 { 2113 // If border color is emty use color slightly darker than main back color 2114 surfaceBorderColor = ChartGraphics.GetGradientColor( backColor, Color.Black, 0.2 ); 2115 } 2116 2117 //********************************************************************** 2118 //** Draw elements if required. 2119 //********************************************************************** 2120 Pen thinBorderPen = new Pen(surfaceBorderColor, 1); 2121 if(drawElements) 2122 { 2123 // Draw the polygon 2124 if(backColor != Color.Transparent) 2125 { 2126 // Remember SmoothingMode and turn off anti aliasing 2127 SmoothingMode oldSmoothingMode = SmoothingMode; 2128 SmoothingMode = SmoothingMode.Default; 2129 2130 // Draw the polygon 2131 using (Brush brush = new SolidBrush(polygonColor)) 2132 { 2133 FillPolygon(brush, polygonPoints); 2134 } 2135 2136 // Return old smoothing mode 2137 SmoothingMode = oldSmoothingMode; 2138 } 2139 2140 // Draw thin polygon border of darker color 2141 if(forceThinBorder || forceThickBorder) 2142 { 2143 if(forceThickBorder) 2144 { 2145 Pen linePen = new Pen(surfaceBorderColor, borderWidth); 2146 linePen.StartCap = LineCap.Round; 2147 linePen.EndCap = LineCap.Round; 2148 2149 DrawLine(linePen, polygonPoints[0], polygonPoints[1]); 2150 DrawLine(linePen, polygonPoints[2], polygonPoints[3]); 2151 DrawLine(linePen, polygonPoints[3], polygonPoints[0]); 2152 DrawLine(linePen, polygonPoints[1], polygonPoints[2]); 2153 } 2154 else 2155 { 2156 // Front & Back lines 2157 DrawLine(thinBorderPen, polygonPoints[0], polygonPoints[1]); 2158 DrawLine(thinBorderPen, polygonPoints[2], polygonPoints[3]); 2159 if(lineSegmentType == LineSegmentType.First) 2160 { 2161 // Left line 2162 DrawLine(thinBorderPen, polygonPoints[3], polygonPoints[0]); 2163 } 2164 else if(lineSegmentType == LineSegmentType.Last) 2165 { 2166 // Right Line 2167 DrawLine(thinBorderPen, polygonPoints[1], polygonPoints[2]); 2168 } 2169 else 2170 { 2171 // Left & Right lines 2172 DrawLine(thinBorderPen, polygonPoints[3], polygonPoints[0]); 2173 DrawLine(thinBorderPen, polygonPoints[1], polygonPoints[2]); 2174 } 2175 } 2176 2177 } 2178 else 2179 { 2180 // Draw thin polygon border of same color (solves anti-aliasing issues) 2181 if(polygonColor.A == 255) 2182 { 2183 DrawPolygon(new Pen(polygonColor, 1), polygonPoints); 2184 } 2185 2186 // Draw thin Front & Back lines 2187 DrawLine(thinBorderPen, polygonPoints[0], polygonPoints[1]); 2188 DrawLine(thinBorderPen, polygonPoints[2], polygonPoints[3]); 2189 } 2190 } 2191 2192 //********************************************************************** 2193 //** Draw thick border line on visible sides 2194 //********************************************************************** 2195 Pen thickBorderPen = null; 2196 if(borderWidth > 1 && !forceThickBorder) 2197 { 2198 // Create thick border line pen 2199 thickBorderPen = new Pen(surfaceBorderColor, borderWidth); 2200 thickBorderPen.StartCap = LineCap.Round; 2201 thickBorderPen.EndCap = LineCap.Round; 2202 2203 //**************************************************************** 2204 //** Switch first and second points. 2205 //**************************************************************** 2206 if(firstPoint.index > secondPoint.index) 2207 { 2208 DataPoint3D tempPoint = firstPoint; 2209 firstPoint = secondPoint; 2210 secondPoint = tempPoint; 2211 } 2212 2213 //********************************************************************** 2214 //** Check if there are visible (non-empty) lines to the left & right 2215 //** of the current line. 2216 //********************************************************************** 2217 2218 // Get visibility of bounding rectangle 2219 float minX = (float)Math.Min(points3D[0].X, points3D[1].X); 2220 float minY = (float)Math.Min(points3D[0].Y, points3D[1].Y); 2221 float maxX = (float)Math.Max(points3D[0].X, points3D[1].X); 2222 float maxY = (float)Math.Max(points3D[0].Y, points3D[1].Y); 2223 RectangleF position = new RectangleF(minX, minY, maxX - minX, maxY - minY); 2224 SurfaceNames visibleSurfaces = GetVisibleSurfaces(position,positionZ,depth,matrix); 2225 2226 // Check left line visibility 2227 bool thickBorderOnLeft = false; 2228 bool thickBorderOnRight = false; 2229 2230 if(lineSegmentType != LineSegmentType.Middle) 2231 { 2232 LineSegmentType tempLineSegmentType = LineSegmentType.Single; 2233 2234 // Check left line visibility 2235 thickBorderOnLeft = (ChartGraphics.ShouldDrawLineChartSurface( 2236 area, 2237 reversedSeriesOrder, 2238 SurfaceNames.Left, 2239 visibleSurfaces, 2240 polygonColor, 2241 points, 2242 firstPoint, 2243 secondPoint, 2244 multiSeries, 2245 ref tempLineSegmentType) == 2); 2246 2247 2248 // Check right line visibility 2249 thickBorderOnRight = (ChartGraphics.ShouldDrawLineChartSurface( 2250 area, 2251 reversedSeriesOrder, 2252 SurfaceNames.Right, 2253 visibleSurfaces, 2254 polygonColor, 2255 points, 2256 firstPoint, 2257 secondPoint, 2258 multiSeries, 2259 ref tempLineSegmentType) == 2); 2260 } 2261 2262 // Switch left & right border if series is reversed 2263 if(reversedSeriesOrder) 2264 { 2265 bool tempVal = thickBorderOnLeft; 2266 thickBorderOnLeft = thickBorderOnRight; 2267 thickBorderOnRight = tempVal; 2268 } 2269 2270 // Draw thick border for single segment lines only 2271 // or for the first & last segment 2272 if(lineSegmentType != LineSegmentType.First && lineSegmentType != LineSegmentType.Single) 2273 { 2274 thickBorderOnLeft = false; 2275 } 2276 if(lineSegmentType != LineSegmentType.Last && lineSegmentType != LineSegmentType.Single) 2277 { 2278 thickBorderOnRight = false; 2279 } 2280 2281 //********************************************************************** 2282 //** Draw border on the front side of line surface (only when visible) 2283 //********************************************************************** 2284 if( matrix.Perspective != 0 || 2285 (matrix.AngleX != 90 && matrix.AngleX != -90 && 2286 matrix.AngleY != 90 && matrix.AngleY != -90 && 2287 matrix.AngleY != 180 && matrix.AngleY != -180)) 2288 { 2289 // Draw thick line on the front side of the line surface 2290 if(drawElements) 2291 { 2292 DrawLine( 2293 thickBorderPen, 2294 (float)Math.Round(polygonPoints[0].X), 2295 (float)Math.Round(polygonPoints[0].Y), 2296 (float)Math.Round(polygonPoints[1].X), 2297 (float)Math.Round(polygonPoints[1].Y) ); 2298 } 2299 2300 // Calculate path for selection 2301 if(resultPath != null) 2302 { 2303 // Add front line to the path 2304 resultPath.AddLine( 2305 (float)Math.Round(polygonPoints[0].X), 2306 (float)Math.Round(polygonPoints[0].Y), 2307 (float)Math.Round(polygonPoints[1].X), 2308 (float)Math.Round(polygonPoints[1].Y)); 2309 } 2310 } 2311 2312 2313 //********************************************************************** 2314 //** Draw border on the left side of line surface (only when visible) 2315 //********************************************************************** 2316 2317 // Use flat end for Right & Left border 2318 thickBorderPen.EndCap = LineCap.Flat; 2319 2320 // Draw border on the left side 2321 if (matrix.Perspective != 0 || (matrix.AngleX != 90 && matrix.AngleX != -90)) 2322 { 2323 if(thickBorderOnLeft) 2324 { 2325 if(drawElements) 2326 { 2327 DrawLine( 2328 thickBorderPen, 2329 (float)Math.Round(polygonPoints[3].X), 2330 (float)Math.Round(polygonPoints[3].Y), 2331 (float)Math.Round(polygonPoints[0].X), 2332 (float)Math.Round(polygonPoints[0].Y) ); 2333 } 2334 2335 // Calculate path for selection 2336 if(resultPath != null) 2337 { 2338 // Add left line to the path 2339 resultPath.AddLine( 2340 (float)Math.Round(polygonPoints[3].X), 2341 (float)Math.Round(polygonPoints[3].Y), 2342 (float)Math.Round(polygonPoints[0].X), 2343 (float)Math.Round(polygonPoints[0].Y)); 2344 } 2345 } 2346 } 2347 2348 //********************************************************************** 2349 //** Draw border on the right side of the line surface 2350 //********************************************************************** 2351 if (matrix.Perspective != 0 || (matrix.AngleX != 90 && matrix.AngleX != -90)) 2352 { 2353 if(thickBorderOnRight) 2354 { 2355 if(drawElements) 2356 { 2357 DrawLine( 2358 thickBorderPen, 2359 (float)Math.Round(polygonPoints[1].X), 2360 (float)Math.Round(polygonPoints[1].Y), 2361 (float)Math.Round(polygonPoints[2].X), 2362 (float)Math.Round(polygonPoints[2].Y) ); 2363 } 2364 2365 // Calculate path for selection 2366 if(resultPath != null) 2367 { 2368 // Add right line to the path 2369 resultPath.AddLine( 2370 (float)Math.Round(polygonPoints[1].X), 2371 (float)Math.Round(polygonPoints[1].Y), 2372 (float)Math.Round(polygonPoints[2].X), 2373 (float)Math.Round(polygonPoints[2].Y)); 2374 } 2375 } 2376 } 2377 } 2378 2379 //********************************************************************** 2380 // Redraw front line of the previuos line segment. 2381 // Solves 3D visibility problem between wide border line and line surface. 2382 //********************************************************************** 2383 if( area.Area3DStyle.Perspective == 0 ) 2384 { 2385 if(frontLinePoint1 != PointF.Empty && frontLinePen != null) 2386 { 2387 // Draw line 2388 DrawLine( 2389 frontLinePen, 2390 (float)Math.Round(frontLinePoint1.X), 2391 (float)Math.Round(frontLinePoint1.Y), 2392 (float)Math.Round(frontLinePoint2.X), 2393 (float)Math.Round(frontLinePoint2.Y) ); 2394 2395 // Reset line properties 2396 frontLinePen = null; 2397 frontLinePoint1 = PointF.Empty; 2398 frontLinePoint2 = PointF.Empty; 2399 } 2400 2401 //********************************************************************** 2402 //** Check if front line should be redrawn whith the next segment. 2403 //********************************************************************** 2404 if(drawElements) 2405 { 2406 frontLinePen = (borderWidth > 1) ? thickBorderPen : thinBorderPen; 2407 frontLinePoint1 = polygonPoints[0]; 2408 frontLinePoint2 = polygonPoints[1]; 2409 } 2410 } 2411 2412 //********************************************************************** 2413 //** Calculate path for selection 2414 //********************************************************************** 2415 if(resultPath != null) 2416 { 2417 // Widen all the lines currently in the path 2418 if(thickBorderPen != null) 2419 { 2420 try 2421 { 2422 resultPath.Widen(thickBorderPen); 2423 } 2424 catch (OutOfMemoryException) 2425 { 2426 // GraphicsPath.Widen incorrectly throws OutOfMemoryException 2427 // catching here and reacting by not widening 2428 } 2429 catch (ArgumentException) 2430 { 2431 } 2432 } 2433 2434 // Add polygon to the path 2435 resultPath.AddPolygon(polygonPoints); 2436 } 2437 2438 return resultPath; 2439 } 2440 2441 2442 2443 /// <summary> 2444 /// Helper method, which indicates if area chart surface should be drawn or not. 2445 /// </summary> 2446 /// <param name="area">Chart area object.</param> 2447 /// <param name="reversedSeriesOrder">Series are drawn in reversed order.</param> 2448 /// <param name="surfaceName">Surface name.</param> 2449 /// <param name="boundaryRectVisibleSurfaces">Visible surfaces of the bounding rectangle.</param> 2450 /// <param name="color">Point back color.</param> 2451 /// <param name="points">Array of all points.</param> 2452 /// <param name="firstPoint">First point.</param> 2453 /// <param name="secondPoint">Second point.</param> 2454 /// <param name="multiSeries">Indicates that multiple series are painted at the same time (stacked or side-by-side).</param> 2455 /// <param name="lineSegmentType">Returns line segment type.</param> 2456 /// <returns>Function retrns 0, 1 or 2. 0 - Do not draw surface, 1 - draw on the back, 2 - draw in front.</returns> ShouldDrawLineChartSurface( ChartArea area, bool reversedSeriesOrder, SurfaceNames surfaceName, SurfaceNames boundaryRectVisibleSurfaces, Color color, ArrayList points, DataPoint3D firstPoint, DataPoint3D secondPoint, bool multiSeries, ref LineSegmentType lineSegmentType)2457 static internal int ShouldDrawLineChartSurface( 2458 ChartArea area, 2459 bool reversedSeriesOrder, 2460 SurfaceNames surfaceName, 2461 SurfaceNames boundaryRectVisibleSurfaces, 2462 Color color, 2463 ArrayList points, 2464 DataPoint3D firstPoint, 2465 DataPoint3D secondPoint, 2466 bool multiSeries, 2467 ref LineSegmentType lineSegmentType) 2468 { 2469 int result = 0; 2470 Series series = firstPoint.dataPoint.series; 2471 2472 // Set active horizontal/vertical axis 2473 Axis hAxis = (series.XAxisType == AxisType.Primary) ? area.AxisX : area.AxisX2; 2474 double hAxisMin = hAxis.ViewMinimum; 2475 double hAxisMax = hAxis.ViewMaximum; 2476 2477 //**************************************************************** 2478 //** Check if data point and it's neigbours have non-transparent 2479 //** colors. 2480 //**************************************************************** 2481 2482 // Check if point main color has transparency 2483 bool transparent = color.A != 255; 2484 2485 // Check if points on the left and right side exsit and are transparent 2486 bool leftPointVisible = false; 2487 bool rightPointVisible = false; 2488 if( surfaceName == SurfaceNames.Left ) 2489 { 2490 // Find Left point 2491 DataPoint3D leftPoint = null, leftPointAttr = null; 2492 int pointArrayIndex = int.MinValue; 2493 if(!reversedSeriesOrder) 2494 { 2495 leftPoint = ChartGraphics.FindPointByIndex(points, Math.Min(firstPoint.index, secondPoint.index) - 1, (multiSeries) ? secondPoint : null, ref pointArrayIndex); 2496 leftPointAttr = ChartGraphics.FindPointByIndex(points, Math.Min(firstPoint.index, secondPoint.index), (multiSeries) ? secondPoint : null, ref pointArrayIndex); 2497 } 2498 else 2499 { 2500 leftPoint = ChartGraphics.FindPointByIndex(points, Math.Max(firstPoint.index, secondPoint.index) + 1, (multiSeries) ? secondPoint : null, ref pointArrayIndex); 2501 leftPointAttr = leftPoint; 2502 } 2503 if(leftPoint != null) 2504 { 2505 if(leftPointAttr.dataPoint.IsEmpty) 2506 { 2507 if(leftPointAttr.dataPoint.series.EmptyPointStyle.Color == color || 2508 leftPointAttr.dataPoint.series.EmptyPointStyle.Color.A == 255) 2509 { 2510 leftPointVisible = true; 2511 } 2512 } 2513 else 2514 { 2515 if(leftPointAttr.dataPoint.Color == color || 2516 leftPointAttr.dataPoint.Color.A == 255) 2517 { 2518 leftPointVisible = true; 2519 } 2520 } 2521 2522 // Check if found point is outside the scaleView 2523 double xValue = (leftPoint.indexedSeries) ? leftPoint.index : leftPoint.dataPoint.XValue; 2524 if(xValue > hAxisMax || xValue < hAxisMin) 2525 { 2526 DataPoint3D currentPoint = null; 2527 if(reversedSeriesOrder) 2528 { 2529 currentPoint = (firstPoint.index > secondPoint.index) ? firstPoint : secondPoint; 2530 } 2531 else 2532 { 2533 currentPoint = (firstPoint.index < secondPoint.index) ? firstPoint : secondPoint; 2534 } 2535 double currentXValue = (currentPoint.indexedSeries) ? currentPoint.index : currentPoint.dataPoint.XValue; 2536 if(currentXValue > hAxisMax || currentXValue < hAxisMin) 2537 { 2538 leftPointVisible = false; 2539 } 2540 } 2541 } 2542 } 2543 2544 // Find Right point 2545 if( surfaceName == SurfaceNames.Right ) 2546 { 2547 DataPoint3D rightPoint = null, rightPointAttr = null; 2548 int pointArrayIndex = int.MinValue; 2549 if(!reversedSeriesOrder) 2550 { 2551 rightPoint = ChartGraphics.FindPointByIndex(points, Math.Max(firstPoint.index, secondPoint.index) + 1, (multiSeries) ? secondPoint : null, ref pointArrayIndex); 2552 rightPointAttr = rightPoint; 2553 } 2554 else 2555 { 2556 rightPoint = ChartGraphics.FindPointByIndex(points, Math.Min(firstPoint.index, secondPoint.index) - 1, (multiSeries) ? secondPoint : null, ref pointArrayIndex); 2557 rightPointAttr = ChartGraphics.FindPointByIndex(points, Math.Min(firstPoint.index, secondPoint.index), (multiSeries) ? secondPoint : null, ref pointArrayIndex); 2558 } 2559 if(rightPoint != null) 2560 { 2561 if(rightPointAttr.dataPoint.IsEmpty) 2562 { 2563 if(rightPointAttr.dataPoint.series.EmptyPointStyle.Color == color || 2564 rightPointAttr.dataPoint.series.EmptyPointStyle.Color.A == 255) 2565 { 2566 rightPointVisible = true; 2567 } 2568 } 2569 else 2570 { 2571 if(rightPointAttr.dataPoint.Color == color || 2572 rightPointAttr.dataPoint.Color.A == 255) 2573 { 2574 rightPointVisible = true; 2575 } 2576 } 2577 2578 // Check if found point is outside the scaleView 2579 double xValue = (rightPoint.indexedSeries) ? rightPoint.index : rightPoint.dataPoint.XValue; 2580 if(xValue > hAxisMax || xValue < hAxisMin) 2581 { 2582 DataPoint3D currentPoint = null; 2583 if(reversedSeriesOrder) 2584 { 2585 currentPoint = (firstPoint.index > secondPoint.index) ? firstPoint : secondPoint; 2586 } 2587 else 2588 { 2589 currentPoint = (firstPoint.index < secondPoint.index) ? firstPoint : secondPoint; 2590 } 2591 double currentXValue = (currentPoint.indexedSeries) ? currentPoint.index : currentPoint.dataPoint.XValue; 2592 if(currentXValue > hAxisMax || currentXValue < hAxisMin) 2593 { 2594 rightPointVisible = false; 2595 } 2596 } 2597 } 2598 } 2599 2600 //**************************************************************** 2601 //** Get line segment 2602 //**************************************************************** 2603 if( surfaceName == SurfaceNames.Left && !leftPointVisible) 2604 { 2605 if(lineSegmentType == LineSegmentType.Middle) 2606 { 2607 lineSegmentType = LineSegmentType.First; 2608 } 2609 else if(lineSegmentType == LineSegmentType.Last) 2610 { 2611 lineSegmentType = LineSegmentType.Single; 2612 } 2613 } 2614 if( surfaceName == SurfaceNames.Right && !rightPointVisible) 2615 { 2616 if(lineSegmentType == LineSegmentType.Middle) 2617 { 2618 lineSegmentType = LineSegmentType.Last; 2619 } 2620 else if(lineSegmentType == LineSegmentType.First) 2621 { 2622 lineSegmentType = LineSegmentType.Single; 2623 } 2624 } 2625 2626 2627 //**************************************************************** 2628 //** Check surfaces visibility 2629 //**************************************************************** 2630 if( surfaceName == SurfaceNames.Top ) 2631 { 2632 result = ((boundaryRectVisibleSurfaces & SurfaceNames.Top) == SurfaceNames.Top) ? 2 : 1; 2633 } 2634 if( surfaceName == SurfaceNames.Bottom ) 2635 { 2636 result = ((boundaryRectVisibleSurfaces & SurfaceNames.Bottom) == SurfaceNames.Bottom) ? 2 : 1; 2637 // Draw invisible bottom surface only if chart is transparent 2638 if(result == 1 && !transparent) 2639 { 2640 result = 0; 2641 } 2642 } 2643 if( surfaceName == SurfaceNames.Front ) 2644 { 2645 result = ((boundaryRectVisibleSurfaces & SurfaceNames.Front) == SurfaceNames.Front) ? 2 : 1; 2646 // Draw invisible front surface only if chart is transparent 2647 if(result == 1 && !transparent) 2648 { 2649 result = 0; 2650 } 2651 } 2652 if( surfaceName == SurfaceNames.Back ) 2653 { 2654 result = ((boundaryRectVisibleSurfaces & SurfaceNames.Back) == SurfaceNames.Back) ? 2 : 1; 2655 // Draw invisible back surface only if chart is transparent 2656 if(result == 1 && !transparent) 2657 { 2658 result = 0; 2659 } 2660 } 2661 if( surfaceName == SurfaceNames.Left ) 2662 { 2663 result = ((boundaryRectVisibleSurfaces & SurfaceNames.Left) == SurfaceNames.Left) ? 2 : 1; 2664 // Draw invisible left surface only if point to the left is transparent 2665 if(leftPointVisible) 2666 { 2667 result = 0; 2668 } 2669 } 2670 if( surfaceName == SurfaceNames.Right ) 2671 { 2672 result = ((boundaryRectVisibleSurfaces & SurfaceNames.Right) == SurfaceNames.Right) ? 2 : 1; 2673 // Draw invisible right surface only if point to the right is transparent 2674 if(rightPointVisible) 2675 { 2676 result = 0; 2677 } 2678 } 2679 2680 return result; 2681 } 2682 2683 2684 /// <summary> 2685 /// Helper method which finds point in the list by it's real index. 2686 /// </summary> 2687 /// <param name="points">List of points.</param> 2688 /// <param name="index">Required index.</param> 2689 /// <param name="neighborDataPoint">Neighbor point of the same series.</param> 2690 /// <param name="neighborPointIndex">Neighbor point index in the array list.</param> 2691 /// <returns>Data point found.</returns> FindPointByIndex(ArrayList points, int index, DataPoint3D neighborDataPoint, ref int neighborPointIndex)2692 internal static DataPoint3D FindPointByIndex(ArrayList points, int index, DataPoint3D neighborDataPoint, ref int neighborPointIndex) 2693 { 2694 // Try to look around the neighbor point index 2695 if(neighborPointIndex != int.MinValue) 2696 { 2697 // Try getting the next point 2698 if(neighborPointIndex < (points.Count - 2)) 2699 { 2700 DataPoint3D point = (DataPoint3D)points[neighborPointIndex + 1]; 2701 2702 // Check required point index for the first point 2703 if( point.index == index && 2704 (neighborDataPoint == null || String.Compare(neighborDataPoint.dataPoint.series.Name, point.dataPoint.series.Name, StringComparison.Ordinal) == 0)) 2705 { 2706 ++neighborPointIndex; 2707 return point; 2708 } 2709 } 2710 2711 // Try getting the prev point 2712 if(neighborPointIndex > 0) 2713 { 2714 DataPoint3D point = (DataPoint3D)points[neighborPointIndex - 1]; 2715 2716 // Check required point index for the first point 2717 if( point.index == index && 2718 (neighborDataPoint == null || String.Compare(neighborDataPoint.dataPoint.series.Name, point.dataPoint.series.Name, StringComparison.Ordinal) == 0)) 2719 { 2720 --neighborPointIndex; 2721 return point; 2722 } 2723 } 2724 2725 } 2726 2727 // Loop through all points 2728 neighborPointIndex = 0; 2729 foreach(DataPoint3D point3D in points) 2730 { 2731 // Check required point index for the first point 2732 if(point3D.index == index) 2733 { 2734 // Check if point belongs to the same series 2735 if(neighborDataPoint != null) 2736 { 2737 if (String.Compare(neighborDataPoint.dataPoint.series.Name, point3D.dataPoint.series.Name, StringComparison.Ordinal) != 0) 2738 { 2739 ++neighborPointIndex; 2740 continue; 2741 } 2742 } 2743 2744 // Point found 2745 return (DataPoint3D)point3D; 2746 } 2747 2748 ++neighborPointIndex; 2749 } 2750 2751 // Data point was not found 2752 return null; 2753 } 2754 2755 2756 #endregion 2757 2758 #region 3D Rectangle drawing methods 2759 2760 /// <summary> 2761 /// Function is used to calculate the coordinates of the 2D rectangle in 3D space 2762 /// and either draw it or/and calculate the bounding path for selection. 2763 /// </summary> 2764 /// <param name="position">Position of 2D rectangle.</param> 2765 /// <param name="positionZ">Z position of the back side of the 3D rectangle.</param> 2766 /// <param name="depth">Depth of the 3D rectangle.</param> 2767 /// <param name="matrix">Coordinate transformation matrix.</param> 2768 /// <param name="lightStyle">LightStyle style (None, Simplistic, Realistic).</param> 2769 /// <param name="backColor">Color of rectangle</param> 2770 /// <param name="borderColor">Border Color</param> 2771 /// <param name="borderWidth">Border Width</param> 2772 /// <param name="borderDashStyle">Border Style</param> 2773 /// <param name="operationType">AxisName of operation Drawing, Calculating Path or Both</param> 2774 /// <returns>Returns elemnt shape path if operationType parameter is set to CalcElementPath, otherwise Null.</returns> Fill3DRectangle( RectangleF position, float positionZ, float depth, Matrix3D matrix, LightStyle lightStyle, Color backColor, Color borderColor, int borderWidth, ChartDashStyle borderDashStyle, DrawingOperationTypes operationType)2775 internal GraphicsPath Fill3DRectangle( 2776 RectangleF position, 2777 float positionZ, 2778 float depth, 2779 Matrix3D matrix, 2780 LightStyle lightStyle, 2781 Color backColor, 2782 Color borderColor, 2783 int borderWidth, 2784 ChartDashStyle borderDashStyle, 2785 DrawingOperationTypes operationType) 2786 { 2787 return Fill3DRectangle( 2788 position, 2789 positionZ, 2790 depth, 2791 matrix, 2792 lightStyle, 2793 backColor, 2794 0f, 2795 0f, 2796 borderColor, 2797 borderWidth, 2798 borderDashStyle, 2799 BarDrawingStyle.Default, 2800 false, 2801 operationType); 2802 } 2803 2804 /// <summary> 2805 /// Function is used to calculate the coordinates of the 2D rectangle in 3D space 2806 /// and either draw it or/and calculate the bounding path for selection. 2807 /// </summary> 2808 /// <param name="position">Position of 2D rectangle.</param> 2809 /// <param name="positionZ">Z position of the back side of the 3D rectangle.</param> 2810 /// <param name="depth">Depth of the 3D rectangle.</param> 2811 /// <param name="matrix">Coordinate transformation matrix.</param> 2812 /// <param name="lightStyle">LightStyle style (None, Simplistic, Realistic).</param> 2813 /// <param name="backColor">Color of rectangle</param> 2814 /// <param name="topRightDarkening">Top (or right in bar chart) darkening effect.</param> 2815 /// <param name="bottomLeftDarkening">Bottom (or left in bar chart) darkening effect.</param> 2816 /// <param name="borderColor">Border Color</param> 2817 /// <param name="borderWidth">Border Width</param> 2818 /// <param name="borderDashStyle">Border Style</param> 2819 /// <param name="barDrawingStyle">Bar drawing style.</param> 2820 /// <param name="veticalOrientation">Defines if bar is vertical or horizontal.</param> 2821 /// <param name="operationType">AxisName of operation Drawing, Calculating Path or Both</param> 2822 /// <returns>Returns elemnt shape path if operationType parameter is set to CalcElementPath, otherwise Null.</returns> Fill3DRectangle( RectangleF position, float positionZ, float depth, Matrix3D matrix, LightStyle lightStyle, Color backColor, float topRightDarkening, float bottomLeftDarkening, Color borderColor, int borderWidth, ChartDashStyle borderDashStyle, BarDrawingStyle barDrawingStyle, bool veticalOrientation, DrawingOperationTypes operationType)2823 internal GraphicsPath Fill3DRectangle( 2824 RectangleF position, 2825 float positionZ, 2826 float depth, 2827 Matrix3D matrix, 2828 LightStyle lightStyle, 2829 Color backColor, 2830 float topRightDarkening, 2831 float bottomLeftDarkening, 2832 Color borderColor, 2833 int borderWidth, 2834 ChartDashStyle borderDashStyle, 2835 BarDrawingStyle barDrawingStyle, 2836 bool veticalOrientation, 2837 DrawingOperationTypes operationType) 2838 { 2839 2840 // Check if special drawing is required 2841 if(barDrawingStyle == BarDrawingStyle.Cylinder) 2842 { 2843 // Draw as 3D cylinder 2844 return Fill3DRectangleAsCylinder( 2845 position, 2846 positionZ, 2847 depth, 2848 matrix, 2849 lightStyle, 2850 backColor, 2851 topRightDarkening, 2852 bottomLeftDarkening, 2853 borderColor, 2854 borderWidth, 2855 borderDashStyle, 2856 veticalOrientation, 2857 operationType); 2858 } 2859 2860 // Declare variables 2861 Point3D[] cubePoints = new Point3D[8]; 2862 GraphicsPath resultPath = ((operationType & DrawingOperationTypes.CalcElementPath) == DrawingOperationTypes.CalcElementPath) 2863 ? new GraphicsPath() : null; 2864 2865 // Front Side 2866 cubePoints[0] = new Point3D( position.X, position.Y, positionZ + depth ); 2867 cubePoints[1] = new Point3D( position.X, position.Bottom, positionZ + depth ); 2868 cubePoints[2] = new Point3D( position.Right, position.Bottom, positionZ + depth ); 2869 cubePoints[3] = new Point3D( position.Right, position.Y, positionZ + depth ); 2870 2871 // Back Side 2872 cubePoints[4] = new Point3D( position.X, position.Y, positionZ ); 2873 cubePoints[5] = new Point3D( position.X, position.Bottom, positionZ ); 2874 cubePoints[6] = new Point3D( position.Right, position.Bottom, positionZ ); 2875 cubePoints[7] = new Point3D( position.Right, position.Y, positionZ ); 2876 2877 // Tranform cube coordinates 2878 matrix.TransformPoints( cubePoints ); 2879 2880 // For lightStyle style Non, Border color always exist. 2881 if( lightStyle == LightStyle.None && 2882 (borderWidth == 0 || borderDashStyle == ChartDashStyle.NotSet || borderColor == Color.Empty) ) 2883 { 2884 borderColor = ChartGraphics.GetGradientColor( backColor, Color.Black, 0.5 ); 2885 } 2886 2887 // Get surface colors 2888 Color frontLightColor, leftLightColor, topLightColor, backLightColor, rightLightColor, bottomLightColor; 2889 matrix.GetLight( backColor, out frontLightColor, out backLightColor, out leftLightColor, out rightLightColor, out topLightColor, out bottomLightColor ); 2890 2891 // Darken colors by specified values 2892 if(topRightDarkening != 0f) 2893 { 2894 if(veticalOrientation) 2895 { 2896 topLightColor = ChartGraphics.GetGradientColor(topLightColor, Color.Black, topRightDarkening); 2897 } 2898 else 2899 { 2900 rightLightColor = ChartGraphics.GetGradientColor(rightLightColor, Color.Black, topRightDarkening); 2901 } 2902 } 2903 if(bottomLeftDarkening != 0f) 2904 { 2905 if(veticalOrientation) 2906 { 2907 bottomLightColor = ChartGraphics.GetGradientColor(bottomLightColor, Color.Black, bottomLeftDarkening); 2908 } 2909 else 2910 { 2911 leftLightColor = ChartGraphics.GetGradientColor(leftLightColor, Color.Black, bottomLeftDarkening); 2912 } 2913 } 2914 2915 2916 // Check visible surfaces 2917 SurfaceNames visibleSurfaces = GetVisibleSurfacesWithPerspective(position,positionZ,depth,matrix); 2918 2919 // Draw all invisible surfaces first (if semi-transparent color is used) 2920 for(int drawVisible = 0; drawVisible <= 1; drawVisible++) 2921 { 2922 // Do not draw invisible surfaces for solid colors 2923 if(drawVisible == 0 && backColor.A == 255) 2924 { 2925 continue; 2926 } 2927 2928 // Check visibility of all surfaces and draw them 2929 for(int surfaceIndex = (int)SurfaceNames.Front; surfaceIndex <= (int)SurfaceNames.Bottom; surfaceIndex *= 2) 2930 { 2931 SurfaceNames currentSurface = (SurfaceNames)surfaceIndex; 2932 2933 // If width, height or depth of the cube (3DRectangle) is zero graphical path 2934 // should contain only one surface with 4 points. 2935 if(depth == 0.0 && currentSurface != SurfaceNames.Front) 2936 { 2937 continue; 2938 } 2939 if(position.Width == 0.0 && currentSurface != SurfaceNames.Left && currentSurface != SurfaceNames.Right) 2940 { 2941 continue; 2942 } 2943 if(position.Height == 0.0 && currentSurface != SurfaceNames.Top && currentSurface != SurfaceNames.Bottom) 2944 { 2945 continue; 2946 } 2947 2948 // Check if surface is visible or semi-transparent color is used 2949 bool isVisible = (visibleSurfaces & currentSurface) != 0; 2950 if(isVisible && drawVisible == 1 || 2951 !isVisible && drawVisible == 0) 2952 { 2953 // Fill surface coordinates and color 2954 PointF [] pointsSurface = new PointF[4]; 2955 Color surfaceColor = backColor; 2956 2957 switch(currentSurface) 2958 { 2959 case(SurfaceNames.Front): 2960 surfaceColor = frontLightColor; 2961 pointsSurface[0] = new PointF(cubePoints[0].X, cubePoints[0].Y); 2962 pointsSurface[1] = new PointF(cubePoints[1].X, cubePoints[1].Y); 2963 pointsSurface[2] = new PointF(cubePoints[2].X, cubePoints[2].Y); 2964 pointsSurface[3] = new PointF(cubePoints[3].X, cubePoints[3].Y); 2965 break; 2966 case(SurfaceNames.Back): 2967 surfaceColor = backLightColor; 2968 pointsSurface[0] = new PointF(cubePoints[4].X, cubePoints[4].Y); 2969 pointsSurface[1] = new PointF(cubePoints[5].X, cubePoints[5].Y); 2970 pointsSurface[2] = new PointF(cubePoints[6].X, cubePoints[6].Y); 2971 pointsSurface[3] = new PointF(cubePoints[7].X, cubePoints[7].Y); 2972 break; 2973 case(SurfaceNames.Left): 2974 surfaceColor = leftLightColor; 2975 pointsSurface[0] = new PointF(cubePoints[0].X, cubePoints[0].Y); 2976 pointsSurface[1] = new PointF(cubePoints[1].X, cubePoints[1].Y); 2977 pointsSurface[2] = new PointF(cubePoints[5].X, cubePoints[5].Y); 2978 pointsSurface[3] = new PointF(cubePoints[4].X, cubePoints[4].Y); 2979 break; 2980 case(SurfaceNames.Right): 2981 surfaceColor = rightLightColor; 2982 pointsSurface[0] = new PointF(cubePoints[3].X, cubePoints[3].Y); 2983 pointsSurface[1] = new PointF(cubePoints[2].X, cubePoints[2].Y); 2984 pointsSurface[2] = new PointF(cubePoints[6].X, cubePoints[6].Y); 2985 pointsSurface[3] = new PointF(cubePoints[7].X, cubePoints[7].Y); 2986 break; 2987 case(SurfaceNames.Top): 2988 surfaceColor = topLightColor; 2989 pointsSurface[0] = new PointF(cubePoints[0].X, cubePoints[0].Y); 2990 pointsSurface[1] = new PointF(cubePoints[3].X, cubePoints[3].Y); 2991 pointsSurface[2] = new PointF(cubePoints[7].X, cubePoints[7].Y); 2992 pointsSurface[3] = new PointF(cubePoints[4].X, cubePoints[4].Y); 2993 break; 2994 case(SurfaceNames.Bottom): 2995 surfaceColor = bottomLightColor; 2996 pointsSurface[0] = new PointF(cubePoints[1].X, cubePoints[1].Y); 2997 pointsSurface[1] = new PointF(cubePoints[2].X, cubePoints[2].Y); 2998 pointsSurface[2] = new PointF(cubePoints[6].X, cubePoints[6].Y); 2999 pointsSurface[3] = new PointF(cubePoints[5].X, cubePoints[5].Y); 3000 break; 3001 } 3002 3003 // Covert coordinates to absolute 3004 for(int pointIndex = 0; pointIndex < pointsSurface.Length; pointIndex++) 3005 { 3006 pointsSurface[pointIndex] = GetAbsolutePoint(pointsSurface[pointIndex]); 3007 } 3008 3009 // Draw surface 3010 if( (operationType & DrawingOperationTypes.DrawElement) == DrawingOperationTypes.DrawElement) 3011 { 3012 // Draw only completly visible surfaces 3013 if((visibleSurfaces & currentSurface) != 0) 3014 { 3015 using (Brush brush = new SolidBrush(surfaceColor)) 3016 { 3017 FillPolygon(brush, pointsSurface); 3018 } 3019 3020 // Check if any additional drawing should be done 3021 if(currentSurface == SurfaceNames.Front && 3022 barDrawingStyle != BarDrawingStyle.Default && 3023 barDrawingStyle != BarDrawingStyle.Cylinder) 3024 { 3025 this.DrawBarStyleGradients(matrix, barDrawingStyle, position, positionZ, depth, veticalOrientation); 3026 } 3027 } 3028 3029 // Draw surface border 3030 using (Pen pen = new Pen(borderColor, borderWidth)) 3031 { 3032 pen.DashStyle = GetPenStyle(borderDashStyle); 3033 if (lightStyle != LightStyle.None && 3034 (borderWidth == 0 || borderDashStyle == ChartDashStyle.NotSet || borderColor == Color.Empty)) 3035 { 3036 // Draw line of the same color inside the bar 3037 pen.Color = surfaceColor; 3038 pen.Width = 1; 3039 pen.Alignment = PenAlignment.Inset; 3040 } 3041 3042 pen.StartCap = LineCap.Round; 3043 pen.EndCap = LineCap.Round; 3044 DrawLine(pen, pointsSurface[0], pointsSurface[1]); 3045 DrawLine(pen, pointsSurface[1], pointsSurface[2]); 3046 DrawLine(pen, pointsSurface[2], pointsSurface[3]); 3047 DrawLine(pen, pointsSurface[3], pointsSurface[0]); 3048 } 3049 } 3050 3051 // Add surface coordinate to the path 3052 if( (operationType & DrawingOperationTypes.CalcElementPath) == DrawingOperationTypes.CalcElementPath) 3053 { 3054 // Only if surface is completly visible 3055 if((visibleSurfaces & currentSurface) != 0) 3056 { 3057 resultPath.SetMarkers(); 3058 resultPath.AddPolygon(pointsSurface); 3059 } 3060 } 3061 3062 } 3063 } 3064 } 3065 3066 return resultPath; 3067 } 3068 3069 /// <summary> 3070 /// Draws special bar style effect on the front surface of the bar. 3071 /// </summary> 3072 /// <param name="matrix">Drawing matrix.</param> 3073 /// <param name="barDrawingStyle">Bar drawing style.</param> 3074 /// <param name="position">Position in relative coordinates</param> 3075 /// <param name="positionZ">Z position.</param> 3076 /// <param name="depth">Depth.</param> 3077 /// <param name="isVertical">Defines if bar is vertical or horizontal.</param> DrawBarStyleGradients( Matrix3D matrix, BarDrawingStyle barDrawingStyle, RectangleF position, float positionZ, float depth, bool isVertical)3078 private void DrawBarStyleGradients( 3079 Matrix3D matrix, 3080 BarDrawingStyle barDrawingStyle, 3081 RectangleF position, 3082 float positionZ, 3083 float depth, 3084 bool isVertical) 3085 { 3086 if(barDrawingStyle == BarDrawingStyle.Wedge) 3087 { 3088 // Calculate wedge size to fit the rectangle 3089 RectangleF positionAbs = GetAbsoluteRectangle(position); 3090 float size = (isVertical) ? positionAbs.Width / 2f : positionAbs.Height / 2f; 3091 if(isVertical && 2f * size > positionAbs.Height) 3092 { 3093 size = positionAbs.Height/2f; 3094 } 3095 if(!isVertical && 2f * size > positionAbs.Width) 3096 { 3097 size = positionAbs.Width/2f; 3098 } 3099 SizeF sizeRel = GetRelativeSize(new SizeF(size, size)); 3100 3101 // Make 3D convertion of the key points 3102 Point3D[] gradientPoints = new Point3D[6]; 3103 gradientPoints[0] = new Point3D( position.Left, position.Top, positionZ + depth ); 3104 gradientPoints[1] = new Point3D( position.Left, position.Bottom, positionZ + depth ); 3105 gradientPoints[2] = new Point3D( position.Right, position.Bottom, positionZ + depth ); 3106 gradientPoints[3] = new Point3D( position.Right, position.Top, positionZ + depth ); 3107 if(isVertical) 3108 { 3109 gradientPoints[4] = new Point3D( position.X + position.Width / 2f, position.Top + sizeRel.Height, positionZ + depth ); 3110 gradientPoints[5] = new Point3D( position.X + position.Width / 2f, position.Bottom - sizeRel.Height, positionZ + depth ); 3111 } 3112 else 3113 { 3114 gradientPoints[4] = new Point3D( position.X + sizeRel.Width, position.Top + position.Height / 2f, positionZ + depth ); 3115 gradientPoints[5] = new Point3D( position.Right - sizeRel.Width, position.Top + position.Height / 2f, positionZ + depth ); 3116 } 3117 3118 // Tranform cube coordinates 3119 matrix.TransformPoints( gradientPoints ); 3120 3121 // Convert points to absolute 3122 PointF [] gradientPointsAbs = new PointF[6]; 3123 for(int index = 0; index < gradientPoints.Length; index++) 3124 { 3125 gradientPointsAbs[index] = GetAbsolutePoint(gradientPoints[index].PointF); 3126 } 3127 3128 3129 // Draw left/bottom shadow 3130 using(GraphicsPath path = new GraphicsPath()) 3131 { 3132 if(isVertical) 3133 { 3134 path.AddLine(gradientPointsAbs[4], gradientPointsAbs[5]); 3135 path.AddLine(gradientPointsAbs[5], gradientPointsAbs[2]); 3136 path.AddLine(gradientPointsAbs[2], gradientPointsAbs[3]); 3137 } 3138 else 3139 { 3140 path.AddLine(gradientPointsAbs[4], gradientPointsAbs[5]); 3141 path.AddLine(gradientPointsAbs[5], gradientPointsAbs[2]); 3142 path.AddLine(gradientPointsAbs[2], gradientPointsAbs[1]); 3143 } 3144 path.CloseAllFigures(); 3145 3146 // Create brush and fill path 3147 using(SolidBrush brush = new SolidBrush(Color.FromArgb(90, Color.Black))) 3148 { 3149 this.FillPath(brush, path); 3150 } 3151 } 3152 3153 // Draw top/right triangle 3154 using(GraphicsPath path = new GraphicsPath()) 3155 { 3156 if(isVertical) 3157 { 3158 path.AddLine(gradientPointsAbs[0], gradientPointsAbs[4]); 3159 path.AddLine(gradientPointsAbs[4], gradientPointsAbs[3]); 3160 } 3161 else 3162 { 3163 path.AddLine(gradientPointsAbs[3], gradientPointsAbs[5]); 3164 path.AddLine(gradientPointsAbs[5], gradientPointsAbs[2]); 3165 } 3166 3167 // Create brush and fill path 3168 using(SolidBrush brush = new SolidBrush(Color.FromArgb(50, Color.Black))) 3169 { 3170 // Fill shadow path on the left-bottom side of the bar 3171 this.FillPath(brush, path); 3172 3173 // Draw Lines 3174 using(Pen penDark = new Pen(Color.FromArgb(20, Color.Black), 1)) 3175 { 3176 this.DrawPath(penDark, path); 3177 this.DrawLine( 3178 penDark, 3179 gradientPointsAbs[4], 3180 gradientPointsAbs[5]); 3181 } 3182 3183 // Draw Lines 3184 using(Pen pen = new Pen(Color.FromArgb(40, Color.White), 1)) 3185 { 3186 this.DrawPath(pen, path); 3187 this.DrawLine( 3188 pen, 3189 gradientPointsAbs[4], 3190 gradientPointsAbs[5]); 3191 } 3192 } 3193 } 3194 3195 // Draw bottom/left triangle 3196 using(GraphicsPath path = new GraphicsPath()) 3197 { 3198 if(isVertical) 3199 { 3200 path.AddLine(gradientPointsAbs[1], gradientPointsAbs[5]); 3201 path.AddLine(gradientPointsAbs[5], gradientPointsAbs[2]); 3202 } 3203 else 3204 { 3205 path.AddLine(gradientPointsAbs[0], gradientPointsAbs[4]); 3206 path.AddLine(gradientPointsAbs[4], gradientPointsAbs[1]); 3207 } 3208 3209 // Create brush 3210 using(SolidBrush brush = new SolidBrush(Color.FromArgb(50, Color.Black))) 3211 { 3212 // Fill shadow path on the left-bottom side of the bar 3213 this.FillPath(brush, path); 3214 3215 // Draw edges 3216 using(Pen penDark = new Pen(Color.FromArgb(20, Color.Black), 1)) 3217 { 3218 this.DrawPath(penDark, path); 3219 } 3220 using(Pen pen = new Pen(Color.FromArgb(40, Color.White), 1)) 3221 { 3222 this.DrawPath(pen, path); 3223 } 3224 } 3225 } 3226 3227 3228 } 3229 else if(barDrawingStyle == BarDrawingStyle.LightToDark) 3230 { 3231 // Calculate width of shadows used to create the effect 3232 RectangleF positionAbs = GetAbsoluteRectangle(position); 3233 float shadowSizeAbs = 5f; 3234 if(positionAbs.Width < 6f || positionAbs.Height < 6f) 3235 { 3236 shadowSizeAbs = 2f; 3237 } 3238 else if(positionAbs.Width < 15f || positionAbs.Height < 15f) 3239 { 3240 shadowSizeAbs = 3f; 3241 } 3242 SizeF shadowSizeRel = GetRelativeSize(new SizeF(shadowSizeAbs, shadowSizeAbs)); 3243 3244 // Calculate gradient position 3245 RectangleF gradientRect = position; 3246 gradientRect.Inflate(-shadowSizeRel.Width, -shadowSizeRel.Height); 3247 if(isVertical) 3248 { 3249 gradientRect.Height = (float)Math.Floor(gradientRect.Height / 3f); 3250 } 3251 else 3252 { 3253 gradientRect.X = gradientRect.Right - (float)Math.Floor(gradientRect.Width / 3f); 3254 gradientRect.Width = (float)Math.Floor(gradientRect.Width / 3f); 3255 } 3256 3257 3258 // Top gradient 3259 Point3D[] gradientPoints = new Point3D[4]; 3260 gradientPoints[0] = new Point3D( gradientRect.Left, gradientRect.Top, positionZ + depth ); 3261 gradientPoints[1] = new Point3D( gradientRect.Left, gradientRect.Bottom, positionZ + depth ); 3262 gradientPoints[2] = new Point3D( gradientRect.Right, gradientRect.Bottom, positionZ + depth ); 3263 gradientPoints[3] = new Point3D( gradientRect.Right, gradientRect.Top, positionZ + depth ); 3264 3265 // Tranform cube coordinates 3266 matrix.TransformPoints( gradientPoints ); 3267 3268 // Convert points to absolute 3269 PointF [] gradientPointsAbs = new PointF[4]; 3270 for(int index = 0; index < gradientPoints.Length; index++) 3271 { 3272 gradientPointsAbs[index] = GetAbsolutePoint(gradientPoints[index].PointF); 3273 } 3274 3275 // Create and draw top path 3276 using(GraphicsPath path = new GraphicsPath()) 3277 { 3278 path.AddPolygon(gradientPointsAbs); 3279 RectangleF bounds = path.GetBounds(); 3280 bounds.Width += 1f; 3281 bounds.Height += 1f; 3282 3283 // Create brush 3284 if(bounds.Width > 0f && bounds.Height > 0f) 3285 { 3286 using(LinearGradientBrush topBrush = new LinearGradientBrush( 3287 bounds, 3288 (!isVertical) ? Color.Transparent : Color.FromArgb(120, Color.White), 3289 (!isVertical) ? Color.FromArgb(120, Color.White) : Color.Transparent, 3290 (isVertical) ? LinearGradientMode.Vertical : LinearGradientMode.Horizontal)) 3291 { 3292 // Fill shadow path on the top side of the bar 3293 this.FillPath(topBrush, path); 3294 } 3295 } 3296 } 3297 3298 3299 3300 // Calculate gradient position for the bottom gradient 3301 gradientRect = position; 3302 gradientRect.Inflate(-shadowSizeRel.Width, -shadowSizeRel.Height); 3303 if(isVertical) 3304 { 3305 gradientRect.Y = gradientRect.Bottom - (float)Math.Floor(gradientRect.Height / 3f); 3306 gradientRect.Height = (float)Math.Floor(gradientRect.Height / 3f); 3307 } 3308 else 3309 { 3310 gradientRect.Width = (float)Math.Floor(gradientRect.Width / 3f); 3311 } 3312 3313 3314 // Top gradient 3315 gradientPoints = new Point3D[4]; 3316 gradientPoints[0] = new Point3D( gradientRect.Left, gradientRect.Top, positionZ + depth ); 3317 gradientPoints[1] = new Point3D( gradientRect.Left, gradientRect.Bottom, positionZ + depth ); 3318 gradientPoints[2] = new Point3D( gradientRect.Right, gradientRect.Bottom, positionZ + depth ); 3319 gradientPoints[3] = new Point3D( gradientRect.Right, gradientRect.Top, positionZ + depth ); 3320 3321 // Tranform cube coordinates 3322 matrix.TransformPoints( gradientPoints ); 3323 3324 // Convert points to absolute 3325 gradientPointsAbs = new PointF[4]; 3326 for(int index = 0; index < gradientPoints.Length; index++) 3327 { 3328 gradientPointsAbs[index] = GetAbsolutePoint(gradientPoints[index].PointF); 3329 } 3330 3331 // Create and draw top path 3332 using(GraphicsPath path = new GraphicsPath()) 3333 { 3334 path.AddPolygon(gradientPointsAbs); 3335 RectangleF bounds = path.GetBounds(); 3336 bounds.Width += 1f; 3337 bounds.Height += 1f; 3338 3339 // Create brush 3340 if(bounds.Width > 0f && bounds.Height > 0f) 3341 { 3342 using(LinearGradientBrush topBrush = new LinearGradientBrush( 3343 bounds, 3344 (isVertical) ? Color.Transparent : Color.FromArgb(80, Color.Black), 3345 (isVertical) ? Color.FromArgb(80, Color.Black) : Color.Transparent, 3346 (isVertical) ? LinearGradientMode.Vertical : LinearGradientMode.Horizontal)) 3347 { 3348 // Fill shadow path on the top side of the bar 3349 this.FillPath(topBrush, path); 3350 } 3351 } 3352 } 3353 3354 } 3355 else if(barDrawingStyle == BarDrawingStyle.Emboss) 3356 { 3357 // Calculate width of shadows used to create the effect 3358 RectangleF positionAbs = GetAbsoluteRectangle(position); 3359 float shadowSizeAbs = 4f; 3360 if(positionAbs.Width < 6f || positionAbs.Height < 6f) 3361 { 3362 shadowSizeAbs = 2f; 3363 } 3364 else if(positionAbs.Width < 15f || positionAbs.Height < 15f) 3365 { 3366 shadowSizeAbs = 3f; 3367 } 3368 SizeF shadowSizeRel = GetRelativeSize(new SizeF(shadowSizeAbs, shadowSizeAbs)); 3369 3370 // Left/top Side 3371 Point3D[] gradientPoints = new Point3D[6]; 3372 gradientPoints[0] = new Point3D( position.Left, position.Bottom, positionZ + depth ); 3373 gradientPoints[1] = new Point3D( position.Left, position.Top, positionZ + depth ); 3374 gradientPoints[2] = new Point3D( position.Right, position.Top, positionZ + depth ); 3375 gradientPoints[3] = new Point3D( position.Right - shadowSizeRel.Width, position.Top + shadowSizeRel.Height, positionZ + depth ); 3376 gradientPoints[4] = new Point3D( position.Left + shadowSizeRel.Width, position.Top + shadowSizeRel.Height, positionZ + depth ); 3377 gradientPoints[5] = new Point3D( position.Left + shadowSizeRel.Width, position.Bottom - shadowSizeRel.Height, positionZ + depth ); 3378 3379 // Tranform cube coordinates 3380 matrix.TransformPoints( gradientPoints ); 3381 3382 // Convert points to absolute 3383 PointF [] gradientPointsAbs = new PointF[6]; 3384 for(int index = 0; index < gradientPoints.Length; index++) 3385 { 3386 gradientPointsAbs[index] = GetAbsolutePoint(gradientPoints[index].PointF); 3387 } 3388 3389 // Create and draw left/top path 3390 using(GraphicsPath path = new GraphicsPath()) 3391 { 3392 path.AddPolygon(gradientPointsAbs); 3393 3394 // Create brush 3395 using(SolidBrush leftTopBrush = new SolidBrush(Color.FromArgb(100, Color.White))) 3396 { 3397 // Fill shadow path on the left-bottom side of the bar 3398 this.FillPath(leftTopBrush, path); 3399 } 3400 } 3401 3402 // Right/bottom Side 3403 gradientPoints[0] = new Point3D( position.Right, position.Top, positionZ + depth ); 3404 gradientPoints[1] = new Point3D( position.Right, position.Bottom, positionZ + depth ); 3405 gradientPoints[2] = new Point3D( position.Left, position.Bottom, positionZ + depth ); 3406 gradientPoints[3] = new Point3D( position.Left + shadowSizeRel.Width, position.Bottom - shadowSizeRel.Height, positionZ + depth ); 3407 gradientPoints[4] = new Point3D( position.Right - shadowSizeRel.Width, position.Bottom - shadowSizeRel.Height, positionZ + depth ); 3408 gradientPoints[5] = new Point3D( position.Right - shadowSizeRel.Width, position.Top + shadowSizeRel.Height, positionZ + depth ); 3409 3410 // Tranform cube coordinates 3411 matrix.TransformPoints( gradientPoints ); 3412 3413 // Convert points to absolute 3414 for(int index = 0; index < gradientPoints.Length; index++) 3415 { 3416 gradientPointsAbs[index] = GetAbsolutePoint(gradientPoints[index].PointF); 3417 } 3418 3419 // Create and draw left/top path 3420 using(GraphicsPath path = new GraphicsPath()) 3421 { 3422 path.AddPolygon(gradientPointsAbs); 3423 3424 // Create brush 3425 using(SolidBrush bottomRightBrush = new SolidBrush(Color.FromArgb(80, Color.Black))) 3426 { 3427 // Fill shadow path on the left-bottom side of the bar 3428 this.FillPath(bottomRightBrush, path); 3429 } 3430 } 3431 } 3432 } 3433 3434 #endregion 3435 3436 #region 3D markers drawing methods 3437 3438 /// <summary> 3439 /// Draw marker using absolute coordinates of the center. 3440 /// </summary> 3441 /// <param name="matrix">Coordinates transformation matrix.</param> 3442 /// <param name="lightStyle">LightStyle style (None, Simplistic, Realistic).</param> 3443 /// <param name="positionZ">Z position of the 3D marker center.</param> 3444 /// <param name="point">Coordinates of the center.</param> 3445 /// <param name="markerStyle">Marker style.</param> 3446 /// <param name="markerSize">Marker size.</param> 3447 /// <param name="markerColor">Marker color.</param> 3448 /// <param name="markerBorderColor">Marker border color.</param> 3449 /// <param name="markerBorderSize">Marker border size.</param> 3450 /// <param name="markerImage">Marker image name.</param> 3451 /// <param name="markerImageTransparentColor">Marker image transparent color.</param> 3452 /// <param name="shadowSize">Marker shadow size.</param> 3453 /// <param name="shadowColor">Marker shadow color.</param> 3454 /// <param name="imageScaleRect">Rectangle to which marker image should be scaled.</param> 3455 /// <param name="operationType">AxisName of operation Drawing, Calculating Path or Both</param> 3456 /// <returns>Returns elemnt shape path if operationType parameter is set to ElementPath, otherwise Null.</returns> DrawMarker3D( Matrix3D matrix, LightStyle lightStyle, float positionZ, PointF point, MarkerStyle markerStyle, int markerSize, Color markerColor, Color markerBorderColor, int markerBorderSize, string markerImage, Color markerImageTransparentColor, int shadowSize, Color shadowColor, RectangleF imageScaleRect, DrawingOperationTypes operationType )3457 internal GraphicsPath DrawMarker3D( 3458 Matrix3D matrix, 3459 LightStyle lightStyle, 3460 float positionZ, 3461 PointF point, 3462 MarkerStyle markerStyle, 3463 int markerSize, 3464 Color markerColor, 3465 Color markerBorderColor, 3466 int markerBorderSize, 3467 string markerImage, 3468 Color markerImageTransparentColor, 3469 int shadowSize, 3470 Color shadowColor, 3471 RectangleF imageScaleRect, 3472 DrawingOperationTypes operationType ) 3473 { 3474 ChartGraphics graph = (ChartGraphics)this; 3475 GraphicsPath resultPath = ((operationType & DrawingOperationTypes.CalcElementPath) == DrawingOperationTypes.CalcElementPath) 3476 ? new GraphicsPath() : null; 3477 3478 //************************************************************ 3479 //** Transform marker position in 3D space 3480 //************************************************************ 3481 // Get projection coordinates 3482 Point3D[] marker3DPosition = new Point3D[1]; 3483 marker3DPosition[0] = new Point3D(point.X, point.Y, positionZ); 3484 3485 // Transform coordinates of the marker center 3486 matrix.TransformPoints(marker3DPosition); 3487 PointF markerRotatedPosition = marker3DPosition[0].PointF; 3488 3489 // Translate to absolute coordinates 3490 markerRotatedPosition = graph.GetAbsolutePoint(markerRotatedPosition); 3491 3492 //************************************************************ 3493 //** For those markers that do not have a 3D version - draw the same as in 2D 3494 //************************************************************ 3495 if(markerImage.Length > 0 || 3496 !(markerStyle == MarkerStyle.Circle || 3497 markerStyle == MarkerStyle.Square) ) 3498 { 3499 // Call 2D version of the method 3500 if( (operationType & DrawingOperationTypes.DrawElement) == DrawingOperationTypes.DrawElement) 3501 { 3502 graph.DrawMarkerAbs(markerRotatedPosition, markerStyle, markerSize, markerColor, markerBorderColor, markerBorderSize, markerImage, markerImageTransparentColor, shadowSize, shadowColor, imageScaleRect, false); 3503 } 3504 3505 // Prepare marker path 3506 if( (operationType & DrawingOperationTypes.CalcElementPath) == DrawingOperationTypes.CalcElementPath) 3507 { 3508 RectangleF rect = RectangleF.Empty; 3509 rect.X = markerRotatedPosition.X - ((float)markerSize)/2F; 3510 rect.Y = markerRotatedPosition.Y - ((float)markerSize)/2F; 3511 rect.Width = markerSize; 3512 rect.Height = markerSize; 3513 resultPath.AddRectangle(rect); 3514 } 3515 3516 return resultPath; 3517 } 3518 3519 //************************************************************ 3520 //** Draw marker 3521 //************************************************************ 3522 // Check if marker properties are set 3523 if (markerStyle != MarkerStyle.None && markerSize > 0 && markerColor != Color.Empty) 3524 { 3525 // Create solid color brush 3526 using (SolidBrush brush = new SolidBrush(markerColor)) 3527 { 3528 3529 // Calculate marker rectangle 3530 RectangleF rect = RectangleF.Empty; 3531 rect.X = markerRotatedPosition.X - ((float)markerSize) / 2F; 3532 rect.Y = markerRotatedPosition.Y - ((float)markerSize) / 2F; 3533 rect.Width = markerSize; 3534 rect.Height = markerSize; 3535 3536 // Calculate relative marker size 3537 SizeF markerRelativeSize = graph.GetRelativeSize(new SizeF(markerSize, markerSize)); 3538 3539 // Draw marker depending on style 3540 switch (markerStyle) 3541 { 3542 case (MarkerStyle.Circle): 3543 { 3544 if ((operationType & DrawingOperationTypes.DrawElement) == DrawingOperationTypes.DrawElement) 3545 { 3546 // Draw marker shadow 3547 if (shadowSize != 0 && shadowColor != Color.Empty) 3548 { 3549 if (!graph.softShadows) 3550 { 3551 using (Brush shadowBrush = new SolidBrush((shadowColor.A != 255) ? shadowColor : Color.FromArgb(markerColor.A / 2, shadowColor))) 3552 { 3553 RectangleF shadowRect = rect; 3554 shadowRect.X += shadowSize; 3555 shadowRect.Y += shadowSize; 3556 graph.FillEllipse(shadowBrush, shadowRect); 3557 } 3558 } 3559 else 3560 { 3561 // Add circle to the graphics path 3562 using (GraphicsPath path = new GraphicsPath()) 3563 { 3564 path.AddEllipse(rect.X + shadowSize - 1, rect.Y + shadowSize - 1, rect.Width + 2, rect.Height + 2); 3565 3566 // Create path brush 3567 using (PathGradientBrush shadowBrush = new PathGradientBrush(path)) 3568 { 3569 shadowBrush.CenterColor = shadowColor; 3570 3571 // Set the color along the entire boundary of the path 3572 Color[] colors = { Color.Transparent }; 3573 shadowBrush.SurroundColors = colors; 3574 shadowBrush.CenterPoint = new PointF(markerRotatedPosition.X, markerRotatedPosition.Y); 3575 3576 // Define brush focus scale 3577 PointF focusScale = new PointF(1 - 2f * shadowSize / rect.Width, 1 - 2f * shadowSize / rect.Height); 3578 if (focusScale.X < 0) 3579 { 3580 focusScale.X = 0; 3581 } 3582 if (focusScale.Y < 0) 3583 { 3584 focusScale.Y = 0; 3585 } 3586 shadowBrush.FocusScales = focusScale; 3587 3588 // Draw shadow 3589 graph.FillPath(shadowBrush, path); 3590 } 3591 } 3592 } 3593 } 3594 3595 // Create path gradient brush 3596 using (GraphicsPath brushPath = new GraphicsPath()) 3597 { 3598 RectangleF rectLightCenter = new RectangleF(rect.Location, rect.Size); 3599 rectLightCenter.Inflate(rectLightCenter.Width / 4f, rectLightCenter.Height / 4f); 3600 brushPath.AddEllipse(rectLightCenter); 3601 using (PathGradientBrush circleBrush = new PathGradientBrush(brushPath)) 3602 { 3603 circleBrush.CenterColor = ChartGraphics.GetGradientColor(markerColor, Color.White, 0.85); 3604 circleBrush.SurroundColors = new Color[] { markerColor }; 3605 3606 // Calculate the center point of the gradient 3607 Point3D[] centerPoint = new Point3D[] { new Point3D(point.X, point.Y, positionZ + markerRelativeSize.Width) }; 3608 matrix.TransformPoints(centerPoint); 3609 centerPoint[0].PointF = graph.GetAbsolutePoint(centerPoint[0].PointF); 3610 circleBrush.CenterPoint = centerPoint[0].PointF; 3611 3612 // Draw circle (sphere) 3613 graph.FillEllipse(circleBrush, rect); 3614 graph.DrawEllipse(new Pen(markerBorderColor, markerBorderSize), rect); 3615 } 3616 } 3617 } 3618 3619 // Prepare marker path 3620 if ((operationType & DrawingOperationTypes.CalcElementPath) == DrawingOperationTypes.CalcElementPath) 3621 { 3622 resultPath.AddEllipse(rect); 3623 } 3624 3625 break; 3626 } 3627 case (MarkerStyle.Square): 3628 { 3629 3630 // Calculate marker non-rotated rectangle 3631 RectangleF rectNonRotated = RectangleF.Empty; 3632 rectNonRotated.X = point.X - ((float)markerRelativeSize.Width) / 2F; 3633 rectNonRotated.Y = point.Y - ((float)markerRelativeSize.Height) / 2F; 3634 rectNonRotated.Width = markerRelativeSize.Width; 3635 rectNonRotated.Height = markerRelativeSize.Height; 3636 3637 // Draw 3D bar 3638 resultPath = this.Fill3DRectangle( 3639 rectNonRotated, 3640 positionZ - markerRelativeSize.Width / 2f, 3641 markerRelativeSize.Width, 3642 matrix, 3643 lightStyle, 3644 markerColor, 3645 markerBorderColor, 3646 markerBorderSize, 3647 ChartDashStyle.Solid, 3648 operationType); 3649 3650 break; 3651 } 3652 default: 3653 { 3654 throw (new InvalidOperationException(SR.ExceptionGraphics3DMarkerStyleUnknown)); 3655 } 3656 } 3657 } 3658 } 3659 3660 return resultPath; 3661 } 3662 3663 #endregion 3664 3665 #region 3D cube surface visibility methods 3666 3667 /// <summary> 3668 /// Returns visible surfaces of the 3D cube. 3669 /// </summary> 3670 /// <param name="position">2D rectangle coordinates.</param> 3671 /// <param name="positionZ">Z coordinate of the back side of the cube.</param> 3672 /// <param name="depth">Cube depth.</param> 3673 /// <param name="matrix">Coordinate transformation matrix.</param> 3674 /// <returns>Visible surfaces.</returns> GetVisibleSurfaces( RectangleF position, float positionZ, float depth, Matrix3D matrix )3675 internal SurfaceNames GetVisibleSurfaces( 3676 RectangleF position, 3677 float positionZ, 3678 float depth, 3679 Matrix3D matrix 3680 ) 3681 { 3682 // Check if perspective is used 3683 if(matrix.Perspective != 0) 3684 { 3685 // More sofisticated algorithm must be used for visibility detection. 3686 return GetVisibleSurfacesWithPerspective(position, positionZ, depth, matrix); 3687 } 3688 3689 // Front surface is always visible 3690 SurfaceNames result = SurfaceNames.Front; 3691 3692 // Left and Right surfaces depend on the Y axis angle 3693 if (matrix.AngleY > 0) 3694 { 3695 result |= SurfaceNames.Right; 3696 } 3697 else if (matrix.AngleY < 0) 3698 { 3699 result |= SurfaceNames.Left; 3700 } 3701 3702 // Top and Bottom surfaces depend on the X axis angle 3703 if (matrix.AngleX > 0) 3704 { 3705 result |= SurfaceNames.Top; 3706 } 3707 else if (matrix.AngleX < 0) 3708 { 3709 result |= SurfaceNames.Bottom; 3710 } 3711 3712 return result; 3713 } 3714 3715 /// <summary> 3716 /// Returns visible surfaces of the 3D cube. 3717 /// This method takes in consideration the perspective. 3718 /// </summary> 3719 /// <param name="position">2D rectangle coordinates.</param> 3720 /// <param name="positionZ">Z coordinate of the back side of the cube.</param> 3721 /// <param name="depth">Cube depth.</param> 3722 /// <param name="matrix">Coordinate transformation matrix.</param> 3723 /// <returns>Visible surfaces.</returns> GetVisibleSurfacesWithPerspective( RectangleF position, float positionZ, float depth, Matrix3D matrix)3724 internal SurfaceNames GetVisibleSurfacesWithPerspective( 3725 RectangleF position, 3726 float positionZ, 3727 float depth, 3728 Matrix3D matrix) 3729 { 3730 // Create cube coordinates in 3D space 3731 Point3D[] cubePoints = new Point3D[8]; 3732 3733 // Front Side 3734 cubePoints[0] = new Point3D( position.X, position.Y, positionZ + depth ); 3735 cubePoints[1] = new Point3D( position.X, position.Bottom, positionZ + depth ); 3736 cubePoints[2] = new Point3D( position.Right, position.Bottom, positionZ + depth ); 3737 cubePoints[3] = new Point3D( position.Right, position.Y, positionZ + depth ); 3738 3739 // Back Side 3740 cubePoints[4] = new Point3D( position.X, position.Y, positionZ ); 3741 cubePoints[5] = new Point3D( position.X, position.Bottom, positionZ ); 3742 cubePoints[6] = new Point3D( position.Right, position.Bottom, positionZ ); 3743 cubePoints[7] = new Point3D( position.Right, position.Y, positionZ ); 3744 3745 // Tranform coordinates 3746 matrix.TransformPoints( cubePoints ); 3747 3748 // Detect surfaces visibility 3749 return GetVisibleSurfacesWithPerspective(cubePoints); 3750 } 3751 3752 /// <summary> 3753 /// Returns visible surfaces of the 3D cube. 3754 /// This method takes in consideration the perspective. 3755 /// </summary> 3756 /// <param name="cubePoints">Array of 8 points which define the cube.</param> 3757 /// <returns>Visible surfaces.</returns> GetVisibleSurfacesWithPerspective(Point3D[] cubePoints)3758 internal SurfaceNames GetVisibleSurfacesWithPerspective(Point3D[] cubePoints) 3759 { 3760 // Check imput array size 3761 if(cubePoints.Length != 8) 3762 { 3763 throw (new ArgumentException(SR.ExceptionGraphics3DCoordinatesInvalid, "cubePoints")); 3764 } 3765 3766 // Detect surfaces visibility 3767 SurfaceNames result = 0; 3768 3769 // Check the front side 3770 if(IsSurfaceVisible(cubePoints[0],cubePoints[3],cubePoints[2])) 3771 { 3772 result |= SurfaceNames.Front; 3773 } 3774 // Check the back side 3775 if(IsSurfaceVisible(cubePoints[4],cubePoints[5],cubePoints[6])) 3776 { 3777 result |= SurfaceNames.Back; 3778 } 3779 3780 // Check the left side 3781 if(IsSurfaceVisible(cubePoints[0],cubePoints[1],cubePoints[5])) 3782 { 3783 result |= SurfaceNames.Left; 3784 } 3785 3786 // Check the right side 3787 if(IsSurfaceVisible(cubePoints[3],cubePoints[7],cubePoints[6])) 3788 { 3789 result |= SurfaceNames.Right; 3790 } 3791 3792 // Check the top side 3793 if(IsSurfaceVisible(cubePoints[4],cubePoints[7],cubePoints[3])) 3794 { 3795 result |= SurfaceNames.Top; 3796 } 3797 3798 // Check the bottom side 3799 if(IsSurfaceVisible(cubePoints[1],cubePoints[2],cubePoints[6])) 3800 { 3801 result |= SurfaceNames.Bottom; 3802 } 3803 3804 return result; 3805 } 3806 3807 /// <summary> 3808 /// Checks surface visibility using 3 points and clockwise points index rotation. 3809 /// </summary> 3810 /// <param name="first">First point.</param> 3811 /// <param name="second">Second point.</param> 3812 /// <param name="tree">Third point.</param> 3813 /// <returns>True if surface is visible</returns> IsSurfaceVisible( Point3D first, Point3D second, Point3D tree )3814 internal static bool IsSurfaceVisible( Point3D first, Point3D second, Point3D tree ) 3815 { 3816 // Check if points are oriented clocwise in 2D projection. 3817 // If points are clockwise the surface is visible. 3818 float a = ( first.Y - second.Y ) / ( first.X - second.X ); 3819 float b = first.Y - a * first.X; 3820 if( first.X == second.X ) 3821 { 3822 if( first.Y > second.Y ) 3823 { 3824 if( tree.X > first.X ) 3825 { 3826 return true; 3827 } 3828 else 3829 { 3830 return false; 3831 } 3832 } 3833 else 3834 { 3835 if( tree.X > first.X ) 3836 { 3837 return false; 3838 } 3839 else 3840 { 3841 return true; 3842 } 3843 } 3844 } 3845 else if ( first.X < second.X ) 3846 { 3847 if( tree.Y < a * tree.X + b ) 3848 { 3849 return false; 3850 } 3851 else 3852 { 3853 return true; 3854 } 3855 } 3856 else 3857 { 3858 if( tree.Y <= a * tree.X + b ) 3859 { 3860 return true; 3861 } 3862 else 3863 { 3864 return false; 3865 } 3866 } 3867 } 3868 3869 #endregion 3870 3871 #region Line intersection helper method 3872 3873 /// <summary> 3874 /// Gets intersection point of two lines 3875 /// </summary> 3876 /// <param name="x1">First X value of first line.</param> 3877 /// <param name="y1">First Y value of first line.</param> 3878 /// <param name="x2">Second X value of first line.</param> 3879 /// <param name="y2">Second Y value of first line.</param> 3880 /// <param name="x3">First X value of second line.</param> 3881 /// <param name="y3">First Y value of second line.</param> 3882 /// <param name="x4">Second X value of second line.</param> 3883 /// <param name="y4">Second Y value of second line.</param> 3884 /// <returns>Intersection coordinates.</returns> GetLinesIntersection(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4)3885 internal static PointF GetLinesIntersection(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) 3886 { 3887 PointF result = PointF.Empty; 3888 3889 // Special case for horizontal & vertical lines 3890 if(x1 == x2 && y3 == y4) 3891 { 3892 result.X = x1; 3893 result.Y = y3; 3894 return result; 3895 } 3896 else if(y1 == y2 && x3 == x4) 3897 { 3898 result.X = x3; 3899 result.Y = y1; 3900 return result; 3901 } 3902 else if(x1 == x2) 3903 { 3904 result.X = x1; 3905 result.Y = (result.X - x3) * (y4 - y3); 3906 result.Y /= x4 - x3; 3907 result.Y += y3; 3908 return result; 3909 } 3910 else if(x3 == x4) 3911 { 3912 result.X = x3; 3913 result.Y = (result.X - x1) * (y2 - y1); 3914 result.Y /= x2 - x1; 3915 result.Y += y1; 3916 return result; 3917 } 3918 3919 // Calculate line eqaution 3920 float a1 = ( y1 - y2 ) / ( x1 - x2 ); 3921 float b1 = y1 - a1 * x1; 3922 float a2 = ( y3 - y4 ) / ( x3 - x4 ); 3923 float b2 = y3 - a2 * x3; 3924 3925 // Calculate intersection point 3926 result.X = (b2 - b1)/(a1 - a2); 3927 result.Y = a1*result.X + b1; 3928 3929 return result; 3930 } 3931 3932 #endregion 3933 3934 #region 3D Cylinder drawing methods 3935 3936 3937 /// <summary> 3938 /// Function is used to calculate the coordinates of the 2D rectangle in 3D space 3939 /// and either draw it or/and calculate the bounding path for selection. 3940 /// </summary> 3941 /// <param name="position">Position of 2D rectangle.</param> 3942 /// <param name="positionZ">Z position of the back side of the 3D rectangle.</param> 3943 /// <param name="depth">Depth of the 3D rectangle.</param> 3944 /// <param name="matrix">Coordinate transformation matrix.</param> 3945 /// <param name="lightStyle">LightStyle style (None, Simplistic, Realistic).</param> 3946 /// <param name="backColor">Color of rectangle</param> 3947 /// <param name="topRightDarkening">Top (or right in bar chart) darkening effect.</param> 3948 /// <param name="bottomLeftDarkening">Bottom (or left in bar chart) darkening effect.</param> 3949 /// <param name="borderColor">Border Color</param> 3950 /// <param name="borderWidth">Border Width</param> 3951 /// <param name="borderDashStyle">Border Style</param> 3952 /// <param name="veticalOrientation">Defines if bar is vertical or horizontal.</param> 3953 /// <param name="operationType">AxisName of operation Drawing, Calculating Path or Both</param> 3954 /// <returns>Returns elemnt shape path if operationType parameter is set to CalcElementPath, otherwise Null.</returns> Fill3DRectangleAsCylinder( RectangleF position, float positionZ, float depth, Matrix3D matrix, LightStyle lightStyle, Color backColor, float topRightDarkening, float bottomLeftDarkening, Color borderColor, int borderWidth, ChartDashStyle borderDashStyle, bool veticalOrientation, DrawingOperationTypes operationType)3955 internal GraphicsPath Fill3DRectangleAsCylinder( 3956 RectangleF position, 3957 float positionZ, 3958 float depth, 3959 Matrix3D matrix, 3960 LightStyle lightStyle, 3961 Color backColor, 3962 float topRightDarkening, 3963 float bottomLeftDarkening, 3964 Color borderColor, 3965 int borderWidth, 3966 ChartDashStyle borderDashStyle, 3967 bool veticalOrientation, 3968 DrawingOperationTypes operationType) 3969 { 3970 Point3D[] cubePoints = new Point3D[8]; 3971 GraphicsPath resultPath = ((operationType & DrawingOperationTypes.CalcElementPath) == DrawingOperationTypes.CalcElementPath) 3972 ? new GraphicsPath() : null; 3973 3974 //******************************************************* 3975 //** Define coordinates to draw the cylinder 3976 //******************************************************* 3977 if(veticalOrientation) 3978 { 3979 cubePoints[0] = new Point3D( position.X, position.Y, positionZ + depth / 2f ); 3980 cubePoints[1] = new Point3D( position.X, position.Bottom, positionZ + depth / 2f ); 3981 cubePoints[2] = new Point3D( position.Right, position.Bottom, positionZ + depth / 2f ); 3982 cubePoints[3] = new Point3D( position.Right, position.Y, positionZ + depth / 2f ); 3983 3984 float middleXValue = position.X + position.Width / 2f; 3985 cubePoints[4] = new Point3D( middleXValue, position.Y, positionZ + depth ); 3986 cubePoints[5] = new Point3D( middleXValue, position.Bottom, positionZ + depth ); 3987 cubePoints[6] = new Point3D( middleXValue, position.Bottom, positionZ ); 3988 cubePoints[7] = new Point3D( middleXValue, position.Y, positionZ ); 3989 } 3990 else 3991 { 3992 cubePoints[0] = new Point3D( position.Right, position.Y, positionZ + depth / 2f ); 3993 cubePoints[1] = new Point3D( position.X, position.Y, positionZ + depth / 2f ); 3994 cubePoints[2] = new Point3D( position.X, position.Bottom, positionZ + depth / 2f ); 3995 cubePoints[3] = new Point3D( position.Right, position.Bottom, positionZ + depth / 2f ); 3996 3997 float middleYValue = position.Y + position.Height / 2f; 3998 cubePoints[4] = new Point3D( position.Right, middleYValue, positionZ + depth ); 3999 cubePoints[5] = new Point3D( position.X, middleYValue, positionZ + depth ); 4000 cubePoints[6] = new Point3D( position.X, middleYValue, positionZ ); 4001 cubePoints[7] = new Point3D( position.Right, middleYValue, positionZ ); 4002 } 4003 4004 // Tranform cylinder coordinates 4005 matrix.TransformPoints( cubePoints ); 4006 4007 // Covert coordinates to absolute 4008 for(int pointIndex = 0; pointIndex < cubePoints.Length; pointIndex++) 4009 { 4010 cubePoints[pointIndex].PointF = GetAbsolutePoint(cubePoints[pointIndex].PointF); 4011 } 4012 4013 //******************************************************* 4014 //** Get cylinder colors. 4015 //******************************************************* 4016 if( lightStyle == LightStyle.None && 4017 (borderWidth == 0 || borderDashStyle == ChartDashStyle.NotSet || borderColor == Color.Empty) ) 4018 { 4019 borderColor = ChartGraphics.GetGradientColor( backColor, Color.Black, 0.5 ); 4020 } 4021 4022 // Get surface colors 4023 Color frontLightColor, leftLightColor, topLightColor, backLightColor, rightLightColor, bottomLightColor; 4024 matrix.GetLight( backColor, out frontLightColor, out backLightColor, out leftLightColor, out rightLightColor, out topLightColor, out bottomLightColor ); 4025 4026 // Darken colors by specified values 4027 if(topRightDarkening != 0f) 4028 { 4029 if(veticalOrientation) 4030 { 4031 topLightColor = ChartGraphics.GetGradientColor(topLightColor, Color.Black, topRightDarkening); 4032 } 4033 else 4034 { 4035 rightLightColor = ChartGraphics.GetGradientColor(rightLightColor, Color.Black, topRightDarkening); 4036 } 4037 } 4038 if(bottomLeftDarkening != 0f) 4039 { 4040 if(veticalOrientation) 4041 { 4042 bottomLightColor = ChartGraphics.GetGradientColor(bottomLightColor, Color.Black, bottomLeftDarkening); 4043 } 4044 else 4045 { 4046 leftLightColor = ChartGraphics.GetGradientColor(leftLightColor, Color.Black, bottomLeftDarkening); 4047 } 4048 } 4049 4050 //******************************************************* 4051 //** Check visible surfaces 4052 //******************************************************* 4053 SurfaceNames visibleSurfaces = GetVisibleSurfacesWithPerspective(position,positionZ,depth,matrix); 4054 4055 // Front surface is always visible in cylinder 4056 if( (visibleSurfaces & SurfaceNames.Front) != SurfaceNames.Front) 4057 { 4058 visibleSurfaces |= SurfaceNames.Front; 4059 } 4060 4061 //******************************************************* 4062 //** Create flattened paths for the sides of the 4063 //** cylinder (top,bottom/left,rigth) 4064 //******************************************************* 4065 PointF[] sidePoints = new PointF[4]; 4066 sidePoints[0] = cubePoints[6].PointF; 4067 sidePoints[1] = cubePoints[1].PointF; 4068 sidePoints[2] = cubePoints[5].PointF; 4069 sidePoints[3] = cubePoints[2].PointF; 4070 GraphicsPath bottomLeftSide = new GraphicsPath(); 4071 bottomLeftSide.AddClosedCurve(sidePoints, 0.8f); 4072 bottomLeftSide.Flatten(); 4073 sidePoints[0] = cubePoints[7].PointF; 4074 sidePoints[1] = cubePoints[0].PointF; 4075 sidePoints[2] = cubePoints[4].PointF; 4076 sidePoints[3] = cubePoints[3].PointF; 4077 GraphicsPath topRigthSide = new GraphicsPath(); 4078 topRigthSide.AddClosedCurve(sidePoints, 0.8f); 4079 topRigthSide.Flatten(); 4080 4081 //******************************************************* 4082 //** Find cylinder angle 4083 //******************************************************* 4084 float cylinderAngle = 90f; 4085 if(cubePoints[5].PointF.Y != cubePoints[4].PointF.Y) 4086 { 4087 cylinderAngle = (float)Math.Atan( 4088 (cubePoints[4].PointF.X - cubePoints[5].PointF.X) / 4089 (cubePoints[5].PointF.Y - cubePoints[4].PointF.Y) ); 4090 cylinderAngle = (float)Math.Round(cylinderAngle * 180f / (float)Math.PI); 4091 } 4092 4093 //******************************************************* 4094 //** Draw all invisible surfaces first (if semi-transparent color is used) 4095 //******************************************************* 4096 for(int drawVisible = 0; drawVisible <= 1; drawVisible++) 4097 { 4098 // Do not draw invisible surfaces for solid colors 4099 if(drawVisible == 0 && backColor.A == 255) 4100 { 4101 continue; 4102 } 4103 4104 // Check visibility of all surfaces and draw them 4105 for(int surfaceIndex = (int)SurfaceNames.Front; surfaceIndex <= (int)SurfaceNames.Bottom; surfaceIndex *= 2) 4106 { 4107 SurfaceNames currentSurface = (SurfaceNames)surfaceIndex; 4108 4109 // Check if surface is visible or semi-transparent color is used 4110 bool isVisible = (visibleSurfaces & currentSurface) != 0; 4111 if(isVisible && drawVisible == 1 || 4112 !isVisible && drawVisible == 0) 4113 { 4114 // Fill surface coordinates and color 4115 GraphicsPath pathToDraw = null; 4116 Color surfaceColor = backColor; 4117 4118 // Declare a special brush for the front surface 4119 Brush frontSurfaceBrush = null; 4120 4121 switch(currentSurface) 4122 { 4123 case(SurfaceNames.Front): 4124 { 4125 // Set front surface color 4126 surfaceColor = backColor; 4127 4128 // Add ellipse segment of the cylinder on top/rigth (reversed) 4129 pathToDraw = new GraphicsPath(); 4130 PointF leftSideLinePoint = PointF.Empty; 4131 PointF rightSideLinePoint = PointF.Empty; 4132 AddEllipseSegment( 4133 pathToDraw, 4134 topRigthSide, 4135 bottomLeftSide, 4136 (matrix.Perspective == 0) ? veticalOrientation : false, 4137 cylinderAngle, 4138 out leftSideLinePoint, 4139 out rightSideLinePoint); 4140 pathToDraw.Reverse(); 4141 4142 // Add ellipse segment of the cylinder on bottom/left 4143 PointF leftOppSideLinePoint = PointF.Empty; 4144 PointF rightOppSideLinePoint = PointF.Empty; 4145 AddEllipseSegment( 4146 pathToDraw, 4147 bottomLeftSide, 4148 topRigthSide, 4149 (matrix.Perspective == 0) ? veticalOrientation : false, 4150 cylinderAngle, 4151 out leftOppSideLinePoint, 4152 out rightOppSideLinePoint); 4153 pathToDraw.CloseAllFigures(); 4154 4155 // Reset indexes of opposite side points 4156 this._oppLeftBottomPoint = -1; 4157 this._oppRigthTopPoint = -1; 4158 4159 // Create gradient brush for the front surface 4160 if(lightStyle != LightStyle.None) 4161 { 4162 RectangleF boundsRect = pathToDraw.GetBounds(); 4163 if(boundsRect.Height > 0 && boundsRect.Width > 0) 4164 { 4165 Color lightColor = ChartGraphics.GetGradientColor( backColor, Color.White, 0.3 ); 4166 Color darkColor = ChartGraphics.GetGradientColor( backColor, Color.Black, 0.3 ); 4167 4168 // Create gradient 4169 if(!leftSideLinePoint.IsEmpty && 4170 !rightSideLinePoint.IsEmpty && 4171 !leftOppSideLinePoint.IsEmpty && 4172 !rightOppSideLinePoint.IsEmpty) 4173 { 4174 PointF boundsRectMiddlePoint = PointF.Empty; 4175 boundsRectMiddlePoint.X = boundsRect.X + boundsRect.Width/2f; 4176 boundsRectMiddlePoint.Y = boundsRect.Y + boundsRect.Height/2f; 4177 4178 PointF centralLinePoint = PointF.Empty; 4179 double centralLineAngle = ((cylinderAngle) * Math.PI / 180f); 4180 if(cylinderAngle == 0 || cylinderAngle == 180 || cylinderAngle == -180) 4181 { 4182 centralLinePoint.X = boundsRectMiddlePoint.X + 100f; 4183 centralLinePoint.Y = boundsRectMiddlePoint.Y; 4184 } 4185 else if(cylinderAngle == 90 || cylinderAngle == -90) 4186 { 4187 centralLinePoint.X = boundsRectMiddlePoint.X; 4188 centralLinePoint.Y = boundsRectMiddlePoint.Y + 100f; 4189 } 4190 else if(cylinderAngle > -45 && cylinderAngle < 45) 4191 { 4192 centralLinePoint.X = boundsRectMiddlePoint.X + 100f; 4193 centralLinePoint.Y = (float)(Math.Tan(centralLineAngle) * centralLinePoint.X); 4194 centralLinePoint.Y += (float)(boundsRectMiddlePoint.Y - Math.Tan(centralLineAngle) * boundsRectMiddlePoint.X); 4195 } 4196 else 4197 { 4198 centralLinePoint.Y = boundsRectMiddlePoint.Y + 100f; 4199 centralLinePoint.X = (float)(centralLinePoint.Y - (boundsRectMiddlePoint.Y - Math.Tan(centralLineAngle) * boundsRectMiddlePoint.X)); 4200 centralLinePoint.X /= (float)(Math.Tan(centralLineAngle)); 4201 } 4202 4203 4204 PointF middlePoint1 = ChartGraphics.GetLinesIntersection( 4205 boundsRectMiddlePoint.X, boundsRectMiddlePoint.Y, 4206 centralLinePoint.X, centralLinePoint.Y, 4207 leftSideLinePoint.X, leftSideLinePoint.Y, 4208 leftOppSideLinePoint.X, leftOppSideLinePoint.Y); 4209 4210 PointF middlePoint2 = ChartGraphics.GetLinesIntersection( 4211 boundsRectMiddlePoint.X, boundsRectMiddlePoint.Y, 4212 centralLinePoint.X, centralLinePoint.Y, 4213 rightSideLinePoint.X, rightSideLinePoint.Y, 4214 rightOppSideLinePoint.X, rightOppSideLinePoint.Y); 4215 4216 // Gradient points can not have same coordinates 4217 if(middlePoint1.X != middlePoint2.X || middlePoint1.Y != middlePoint2.Y) 4218 { 4219 frontSurfaceBrush = new LinearGradientBrush( 4220 middlePoint1, 4221 middlePoint2, 4222 lightColor, 4223 darkColor); 4224 4225 4226 ColorBlend colorBlend = new ColorBlend(5); 4227 colorBlend.Colors[0] = darkColor; 4228 colorBlend.Colors[1] = darkColor; 4229 colorBlend.Colors[2] = lightColor; 4230 colorBlend.Colors[3] = darkColor; 4231 colorBlend.Colors[4] = darkColor; 4232 4233 colorBlend.Positions[0] = 0.0f; 4234 colorBlend.Positions[1] = 0.0f; 4235 colorBlend.Positions[2] = 0.5f; 4236 colorBlend.Positions[3] = 1.0f; 4237 colorBlend.Positions[4] = 1.0f; 4238 4239 ((LinearGradientBrush)frontSurfaceBrush).InterpolationColors = colorBlend; 4240 } 4241 } 4242 4243 } 4244 } 4245 4246 break; 4247 } 4248 case(SurfaceNames.Top): 4249 if(veticalOrientation) 4250 { 4251 surfaceColor = topLightColor; 4252 pathToDraw = topRigthSide; 4253 } 4254 break; 4255 case(SurfaceNames.Bottom): 4256 if(veticalOrientation) 4257 { 4258 surfaceColor = bottomLightColor; 4259 pathToDraw = bottomLeftSide; 4260 } 4261 break; 4262 case(SurfaceNames.Right): 4263 if(!veticalOrientation) 4264 { 4265 surfaceColor = rightLightColor; 4266 pathToDraw = topRigthSide; 4267 } 4268 break; 4269 case(SurfaceNames.Left): 4270 if(!veticalOrientation) 4271 { 4272 surfaceColor = leftLightColor; 4273 pathToDraw = bottomLeftSide; 4274 } 4275 break; 4276 } 4277 4278 4279 //******************************************************* 4280 //** Draw surface 4281 //******************************************************* 4282 if(pathToDraw != null) 4283 { 4284 if( (operationType & DrawingOperationTypes.DrawElement) == DrawingOperationTypes.DrawElement) 4285 { 4286 // Draw only completly visible surfaces 4287 if((visibleSurfaces & currentSurface) != 0) 4288 { 4289 using (Brush brush = new SolidBrush(surfaceColor)) 4290 { 4291 FillPath( (frontSurfaceBrush == null) ? brush : frontSurfaceBrush, pathToDraw ); 4292 } 4293 } 4294 4295 // Draw surface border 4296 using (Pen pen = new Pen(borderColor, borderWidth)) 4297 { 4298 pen.DashStyle = GetPenStyle(borderDashStyle); 4299 if (lightStyle != LightStyle.None && 4300 (borderWidth == 0 || borderDashStyle == ChartDashStyle.NotSet || borderColor == Color.Empty)) 4301 { 4302 // Draw line of the darker color inside the cylinder 4303 pen.Color = frontSurfaceBrush == null ? surfaceColor : ChartGraphics.GetGradientColor(backColor, Color.Black, 0.3); 4304 pen.Width = 1; 4305 pen.Alignment = PenAlignment.Inset; 4306 } 4307 4308 pen.StartCap = LineCap.Round; 4309 pen.EndCap = LineCap.Round; 4310 pen.LineJoin = LineJoin.Bevel; 4311 DrawPath(pen, pathToDraw); 4312 } 4313 } 4314 4315 // Add surface coordinate to the path 4316 if( (operationType & DrawingOperationTypes.CalcElementPath) == DrawingOperationTypes.CalcElementPath) 4317 { 4318 // Only if surface is completly visible 4319 if((visibleSurfaces & currentSurface) != 0) 4320 { 4321 if(pathToDraw != null && pathToDraw.PointCount > 0) 4322 { 4323 resultPath.AddPath(pathToDraw, true); 4324 resultPath.SetMarkers(); 4325 } 4326 } 4327 } 4328 } 4329 4330 } 4331 } 4332 } 4333 4334 return resultPath; 4335 } 4336 4337 /// <summary> 4338 /// Adds segment of the ellipse to form the front surface of the cylinder 4339 /// </summary> AddEllipseSegment( GraphicsPath resultPath, GraphicsPath ellipseFlattenPath, GraphicsPath oppositeEllipseFlattenPath, bool veticalOrientation, float cylinderAngle, out PointF leftSideLinePoint, out PointF rightSideLinePoint)4340 internal void AddEllipseSegment( 4341 GraphicsPath resultPath, 4342 GraphicsPath ellipseFlattenPath, 4343 GraphicsPath oppositeEllipseFlattenPath, 4344 bool veticalOrientation, 4345 float cylinderAngle, 4346 out PointF leftSideLinePoint, 4347 out PointF rightSideLinePoint) 4348 { 4349 // Initialize return values 4350 leftSideLinePoint = PointF.Empty; 4351 rightSideLinePoint = PointF.Empty; 4352 4353 // Check if input path is empty 4354 if(ellipseFlattenPath.PointCount == 0) 4355 { 4356 return; 4357 } 4358 4359 // Find the index the left/bottom most and right/top most point in flatten array of ellipse points 4360 int leftBottomPoint = 0; 4361 int rigthTopPoint = 0; 4362 PointF[] ellipsePoints = ellipseFlattenPath.PathPoints; 4363 4364 4365 if(veticalOrientation) 4366 { 4367 for(int pointIndex = 1; pointIndex < ellipsePoints.Length; pointIndex++) 4368 { 4369 if(ellipsePoints[leftBottomPoint].X > ellipsePoints[pointIndex].X) 4370 { 4371 leftBottomPoint = pointIndex; 4372 } 4373 if(ellipsePoints[rigthTopPoint].X < ellipsePoints[pointIndex].X) 4374 { 4375 rigthTopPoint = pointIndex; 4376 } 4377 } 4378 } 4379 else 4380 { 4381 bool doneFlag = false; 4382 leftBottomPoint = -1; 4383 rigthTopPoint = -1; 4384 4385 if(this._oppLeftBottomPoint != -1 && this._oppRigthTopPoint != -1) 4386 { 4387 // Get index from previously calculated values 4388 leftBottomPoint = this._oppLeftBottomPoint; 4389 rigthTopPoint = this._oppRigthTopPoint; 4390 } 4391 else 4392 { 4393 // Loop through first ellipse points 4394 PointF[] oppositeEllipsePoints = oppositeEllipseFlattenPath.PathPoints; 4395 for(int pointIndex = 0; !doneFlag && pointIndex < ellipsePoints.Length; pointIndex++) 4396 { 4397 // Loop through opposite ellipse points 4398 for(int pointOppositeIndex = 0; !doneFlag && pointOppositeIndex < oppositeEllipsePoints.Length; pointOppositeIndex++) 4399 { 4400 bool closeToVertical = false; 4401 bool pointsOnLeft = false; 4402 bool pointsOnRight = false; 4403 4404 //if(cylinderAngle == 0 || cylinderAngle == 180 || cylinderAngle == -180) 4405 if(cylinderAngle > -30 && cylinderAngle < 30) 4406 { 4407 closeToVertical = true; 4408 } 4409 4410 if(closeToVertical) 4411 { 4412 if(oppositeEllipsePoints[pointOppositeIndex].Y == ellipsePoints[pointIndex].Y) 4413 { 4414 continue; 4415 } 4416 4417 float linePointX = oppositeEllipsePoints[pointOppositeIndex].X - ellipsePoints[pointIndex].X; 4418 linePointX /= oppositeEllipsePoints[pointOppositeIndex].Y - ellipsePoints[pointIndex].Y; 4419 4420 // Check if this line has any points to the right/left 4421 for(int innerPointIndex = 0; innerPointIndex < ellipsePoints.Length; innerPointIndex++) 4422 { 4423 // Skip points used to define line function 4424 if(innerPointIndex == pointIndex) 4425 { 4426 continue; 4427 } 4428 4429 float x = linePointX; 4430 x *= ellipsePoints[innerPointIndex].Y - ellipsePoints[pointIndex].Y; 4431 x += ellipsePoints[pointIndex].X; 4432 4433 if(x > ellipsePoints[innerPointIndex].X) 4434 { 4435 pointsOnLeft = true; 4436 } 4437 if(x < ellipsePoints[innerPointIndex].X) 4438 { 4439 pointsOnRight = true; 4440 } 4441 if(pointsOnLeft && pointsOnRight) 4442 { 4443 break; 4444 } 4445 } 4446 4447 if(pointsOnLeft == false || pointsOnRight == false) 4448 { 4449 for(int innerPointIndex = 0; innerPointIndex < oppositeEllipsePoints.Length; innerPointIndex++) 4450 { 4451 // Skip points used to define line function 4452 if(innerPointIndex == pointOppositeIndex) 4453 { 4454 continue; 4455 } 4456 4457 float x = linePointX; 4458 x *= oppositeEllipsePoints[innerPointIndex].Y - ellipsePoints[pointIndex].Y; 4459 x += ellipsePoints[pointIndex].X; 4460 4461 if(x > oppositeEllipsePoints[innerPointIndex].X) 4462 { 4463 pointsOnLeft = true; 4464 } 4465 if(x < oppositeEllipsePoints[innerPointIndex].X) 4466 { 4467 pointsOnRight = true; 4468 } 4469 if(pointsOnLeft && pointsOnRight) 4470 { 4471 break; 4472 } 4473 } 4474 } 4475 } 4476 else 4477 { 4478 if(oppositeEllipsePoints[pointOppositeIndex].X == ellipsePoints[pointIndex].X) 4479 { 4480 continue; 4481 } 4482 4483 float linePointY = oppositeEllipsePoints[pointOppositeIndex].Y - ellipsePoints[pointIndex].Y; 4484 linePointY /= oppositeEllipsePoints[pointOppositeIndex].X - ellipsePoints[pointIndex].X; 4485 4486 // Check if this line has any points to the right/left 4487 for(int innerPointIndex = 0; innerPointIndex < ellipsePoints.Length; innerPointIndex++) 4488 { 4489 // Skip points used to define line function 4490 if(innerPointIndex == pointIndex) 4491 { 4492 continue; 4493 } 4494 4495 float y = linePointY; 4496 y *= ellipsePoints[innerPointIndex].X - ellipsePoints[pointIndex].X; 4497 y += ellipsePoints[pointIndex].Y; 4498 4499 if(y > ellipsePoints[innerPointIndex].Y) 4500 { 4501 pointsOnLeft = true; 4502 } 4503 if(y < ellipsePoints[innerPointIndex].Y) 4504 { 4505 pointsOnRight = true; 4506 } 4507 if(pointsOnLeft && pointsOnRight) 4508 { 4509 break; 4510 } 4511 } 4512 4513 if(pointsOnLeft == false || pointsOnRight == false) 4514 { 4515 for(int innerPointIndex = 0; innerPointIndex < oppositeEllipsePoints.Length; innerPointIndex++) 4516 { 4517 // Skip points used to define line function 4518 if(innerPointIndex == pointOppositeIndex) 4519 { 4520 continue; 4521 } 4522 4523 float y = linePointY; 4524 y *= oppositeEllipsePoints[innerPointIndex].X - ellipsePoints[pointIndex].X; 4525 y += ellipsePoints[pointIndex].Y; 4526 4527 if(y > oppositeEllipsePoints[innerPointIndex].Y) 4528 { 4529 pointsOnLeft = true; 4530 } 4531 if(y < oppositeEllipsePoints[innerPointIndex].Y) 4532 { 4533 pointsOnRight = true; 4534 } 4535 if(pointsOnLeft && pointsOnRight) 4536 { 4537 break; 4538 } 4539 } 4540 } 4541 } 4542 4543 if(!pointsOnLeft && leftBottomPoint == -1) 4544 { 4545 leftBottomPoint = pointIndex; 4546 this._oppLeftBottomPoint = pointOppositeIndex; 4547 } 4548 if(!pointsOnRight && rigthTopPoint == -1) 4549 { 4550 rigthTopPoint = pointIndex; 4551 this._oppRigthTopPoint = pointOppositeIndex; 4552 } 4553 4554 if(leftBottomPoint >= 0 && rigthTopPoint >= 0) 4555 { 4556 doneFlag = true; 4557 4558 if(closeToVertical) 4559 { 4560 if(ellipsePoints[leftBottomPoint].Y > oppositeEllipsePoints[this._oppLeftBottomPoint].Y) 4561 { 4562 int temp = leftBottomPoint; 4563 leftBottomPoint = rigthTopPoint; 4564 rigthTopPoint = temp; 4565 4566 temp = this._oppLeftBottomPoint; 4567 this._oppLeftBottomPoint = this._oppRigthTopPoint; 4568 this._oppRigthTopPoint = temp; 4569 } 4570 } 4571 4572 } 4573 } 4574 } 4575 } 4576 } 4577 4578 // Point indexes were not found 4579 if(leftBottomPoint == rigthTopPoint || 4580 rigthTopPoint == -1 || 4581 leftBottomPoint == -1) 4582 { 4583 return; 4584 } 4585 4586 // Set left\right line coordinates 4587 leftSideLinePoint = ellipsePoints[leftBottomPoint]; 4588 rightSideLinePoint = ellipsePoints[rigthTopPoint]; 4589 4590 // Add required ellipse segment to the result path 4591 for(int pointIndex = leftBottomPoint + 1; pointIndex != rigthTopPoint + 1; pointIndex++) 4592 { 4593 if(pointIndex > ellipsePoints.Length - 1) 4594 { 4595 resultPath.AddLine(ellipsePoints[ellipsePoints.Length - 1], ellipsePoints[0]); 4596 pointIndex = 0; 4597 continue; 4598 } 4599 resultPath.AddLine(ellipsePoints[pointIndex - 1], ellipsePoints[pointIndex]); 4600 } 4601 } 4602 4603 #endregion 4604 } 4605 4606 /// <summary> 4607 /// The Point3D class represents point coordinates in 3D space. 4608 /// </summary> 4609 #if ASPPERM_35 4610 [AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)] 4611 [AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)] 4612 #endif 4613 public class Point3D 4614 { 4615 #region Fields 4616 4617 // Point X and Y coordinates 4618 private PointF _coordXY = new PointF(0f, 0f); 4619 4620 // Point Z coordinate (depth) 4621 private float _coordZ = 0; 4622 4623 #endregion 4624 4625 #region Properties 4626 4627 /// <summary> 4628 /// Gets or sets the X coordinate of the point. 4629 /// </summary> 4630 [ 4631 Bindable(true), 4632 DefaultValue(0), 4633 SRDescription("DescriptionAttributePoint3D_X") 4634 ] 4635 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "X")] 4636 public float X 4637 { 4638 get 4639 { 4640 return this._coordXY.X; 4641 } 4642 set 4643 { 4644 this._coordXY.X = value; 4645 } 4646 } 4647 4648 /// <summary> 4649 /// Gets or sets the Y coordinate of the point. 4650 /// </summary> 4651 [ 4652 Bindable(true), 4653 DefaultValue(0), 4654 SRDescription("DescriptionAttributePoint3D_Y") 4655 ] 4656 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Y")] 4657 public float Y 4658 { 4659 get 4660 { 4661 return this._coordXY.Y; 4662 } 4663 set 4664 { 4665 this._coordXY.Y = value; 4666 } 4667 } 4668 4669 /// <summary> 4670 /// Gets or sets the Z coordinate of the point. 4671 /// </summary> 4672 [ 4673 Bindable(true), 4674 DefaultValue(0), 4675 SRDescription("DescriptionAttributePoint3D_Z") 4676 ] 4677 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Z")] 4678 public float Z 4679 { 4680 get 4681 { 4682 return this._coordZ; 4683 } 4684 set 4685 { 4686 this._coordZ = value; 4687 } 4688 } 4689 4690 /// <summary> 4691 /// Gets or sets a PointF structure, which stores the X and Y coordinates of a 3D point. 4692 /// </summary> 4693 [ 4694 Bindable(true), 4695 DefaultValue(0), 4696 SRDescription("DescriptionAttributePoint3D_PointF") 4697 ] 4698 public PointF PointF 4699 { 4700 get 4701 { 4702 return this._coordXY; 4703 } 4704 set 4705 { 4706 this._coordXY = new PointF(value.X, value.Y); 4707 } 4708 } 4709 4710 #endregion 4711 4712 #region Constructors 4713 4714 /// <summary> 4715 /// Public constructor. 4716 /// </summary> 4717 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", 4718 Justification = "X, Y and Z are cartesian coordinates and well understood")] Point3D(float x, float y, float z)4719 public Point3D(float x, float y, float z) 4720 { 4721 this._coordXY = new PointF(x, y); 4722 this._coordZ = z; 4723 } 4724 4725 /// <summary> 4726 /// Public constructor. 4727 /// </summary> Point3D( )4728 public Point3D( ) 4729 { 4730 4731 } 4732 4733 #endregion // Constructor 4734 } 4735 } 4736