1 //------------------------------------------------------------- 2 // <copyright company=�Microsoft Corporation�> 3 // Copyright � Microsoft Corporation. All Rights Reserved. 4 // </copyright> 5 //------------------------------------------------------------- 6 // @owner=alexgor, deliant 7 //================================================================= 8 // File: ChartArea3D.cs 9 // 10 // Namespace: System.Web.UI.WebControls[Windows.Forms].Charting 11 // 12 // Classes: ChartArea3DStyle, ChartArea3D 13 // 14 // Purpose: ChartArea3D class represents 3D chart area. It contains 15 // methods for coordinates transformation, drawing the 3D 16 // scene and many 3D related helper methods. 17 // 18 // Reviewed: AG - Microsoft 16, 2007 19 // 20 //=================================================================== 21 22 #region Used namespaces 23 using System; 24 using System.Drawing; 25 using System.Drawing.Drawing2D; 26 using System.ComponentModel; 27 using System.ComponentModel.Design; 28 using System.Collections; 29 using System.Globalization; 30 using System.Collections.Generic; 31 32 #if WINFORMS_CONTROL 33 using System.Windows.Forms.DataVisualization.Charting; 34 using System.Windows.Forms.DataVisualization.Charting.Data; 35 using System.Windows.Forms.DataVisualization.Charting.ChartTypes; 36 using System.Windows.Forms.DataVisualization.Charting.Utilities; 37 using System.Windows.Forms.DataVisualization.Charting.Borders3D; 38 #else 39 using System.Web.UI.DataVisualization.Charting; 40 using System.Web.UI.DataVisualization.Charting.ChartTypes; 41 using System.Web.UI.DataVisualization.Charting.Utilities; 42 using System.Web.UI; 43 #endif 44 45 46 #endregion 47 48 #if WINFORMS_CONTROL 49 namespace System.Windows.Forms.DataVisualization.Charting 50 #else 51 namespace System.Web.UI.DataVisualization.Charting 52 53 #endif 54 { 55 #region 3D lightStyle style enumerations 56 57 /// <summary> 58 /// A lighting style for a 3D chart area. 59 /// </summary> 60 public enum LightStyle 61 { 62 /// <summary> 63 /// No lighting. 64 /// </summary> 65 None, 66 /// <summary> 67 /// Simplistic lighting. 68 /// </summary> 69 Simplistic, 70 /// <summary> 71 /// Realistic lighting. 72 /// </summary> 73 Realistic 74 } 75 76 #endregion 77 78 #region 3D Center of Projetion coordinates enumeration 79 80 /// <summary> 81 /// Coordinates of the Center Of Projection 82 /// </summary> 83 [Flags] 84 internal enum COPCoordinates 85 { 86 /// <summary> 87 /// Check X coordinate. 88 /// </summary> 89 X = 1, 90 /// <summary> 91 /// Check Y coordinate. 92 /// </summary> 93 Y = 2, 94 /// <summary> 95 /// Check Z coordinate. 96 /// </summary> 97 Z = 4 98 } 99 100 #endregion 101 102 /// <summary> 103 /// The ChartArea3DStyleClass class provides the functionality for 3D attributes of chart areas, 104 /// such as rotation angles and perspective. 105 /// </summary> 106 #if ASPPERM_35 107 [AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)] 108 [AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)] 109 #endif 110 public class ChartArea3DStyle 111 { 112 #region Constructor and Initialization 113 114 /// <summary> 115 /// ChartArea3DStyle constructor. 116 /// </summary> ChartArea3DStyle()117 public ChartArea3DStyle() 118 { 119 } 120 121 /// <summary> 122 /// ChartArea3DStyle constructor. 123 /// </summary> ChartArea3DStyle(ChartArea chartArea)124 public ChartArea3DStyle(ChartArea chartArea) 125 { 126 this._chartArea = chartArea; 127 } 128 129 /// <summary> 130 /// Initialize Chart area and axes 131 /// </summary> 132 /// <param name="chartArea">Chart area object.</param> Initialize(ChartArea chartArea)133 internal void Initialize(ChartArea chartArea) 134 { 135 this._chartArea = chartArea; 136 } 137 138 #endregion 139 140 #region Fields 141 142 // Reference to the chart area object 143 private ChartArea _chartArea = null; 144 145 // Enables/disables 3D chart types in the area. 146 private bool _enable3D = false; 147 148 // Indicates that axes are set at the right angle independent of the rotation. 149 private bool _isRightAngleAxes = true; 150 151 // Indicates that series should be drawn as isClustered. 152 private bool _isClustered = false; 153 154 // 3D area lightStyle style. 155 private LightStyle _lightStyle = LightStyle.Simplistic; 156 157 // 3D area perspective which controls the scaleView of the chart depth. 158 private int _perspective = 0; 159 160 // Chart area rotation angle around the X axis. 161 private int _inclination = 30; 162 163 // Chart area rotation angle around the Y axis. 164 private int _rotation = 30; 165 166 // Chart area walls width. 167 private int _wallWidth = 7; 168 169 // Series points depth in percentages 170 private int _pointDepth = 100; 171 172 // Series points gap depth in percentages 173 private int _pointGapDepth = 100; 174 175 #endregion 176 177 #region Properties 178 179 /// <summary> 180 /// Gets or sets a Boolean value that toggles 3D for a chart area on and off. 181 /// </summary> 182 [ 183 SRCategory("CategoryAttribute3D"), 184 Bindable(true), 185 DefaultValue(false), 186 SRDescription("DescriptionAttributeChartArea3DStyle_Enable3D"), 187 ParenthesizePropertyNameAttribute(true) 188 ] 189 public bool Enable3D 190 { 191 get 192 { 193 return this._enable3D; 194 } 195 set 196 { 197 if (this._enable3D != value) 198 { 199 this._enable3D = value; 200 201 if (this._chartArea != null) 202 { 203 #if SUBAXES 204 // If one of the axes has sub axis the scales has to be recalculated 205 foreach(Axis axis in this._chartArea.Axes) 206 { 207 if(axis.SubAxes.Count > 0) 208 { 209 this._chartArea.ResetAutoValues(); 210 break; 211 } 212 } 213 #endif // SUBAXES 214 215 this._chartArea.Invalidate(); 216 } 217 } 218 } 219 } 220 221 222 /// <summary> 223 /// Gets or sets a Boolean that determines if a chart area is displayed using an isometric projection. 224 /// </summary> 225 [ 226 SRCategory("CategoryAttribute3D"), 227 Bindable(true), 228 DefaultValue(true), 229 SRDescription("DescriptionAttributeChartArea3DStyle_RightAngleAxes"), 230 RefreshPropertiesAttribute(RefreshProperties.All) 231 ] 232 public bool IsRightAngleAxes 233 { 234 get 235 { 236 return _isRightAngleAxes; 237 } 238 set 239 { 240 _isRightAngleAxes = value; 241 242 // Adjust 3D properties values 243 if (_isRightAngleAxes) 244 { 245 // Disable perspective if right angle axis are used 246 this._perspective = 0; 247 } 248 249 if (this._chartArea != null) 250 { 251 this._chartArea.Invalidate(); 252 } 253 } 254 } 255 256 257 /// <summary> 258 /// Gets or sets a Boolean value that determines if bar chart or column 259 /// chart data series are clustered (displayed along distinct rows). 260 /// </summary> 261 [ 262 SRCategory("CategoryAttribute3D"), 263 Bindable(true), 264 DefaultValue(false), 265 SRDescription("DescriptionAttributeChartArea3DStyle_Clustered"), 266 ] 267 public bool IsClustered 268 { 269 get 270 { 271 return _isClustered; 272 } 273 set 274 { 275 _isClustered = value; 276 if (this._chartArea != null) 277 { 278 this._chartArea.Invalidate(); 279 } 280 } 281 } 282 283 /// <summary> 284 /// Gets or sets the style of lighting for a 3D chart area. 285 /// </summary> 286 [ 287 SRCategory("CategoryAttribute3D"), 288 Bindable(true), 289 DefaultValue(typeof(LightStyle), "Simplistic"), 290 SRDescription("DescriptionAttributeChartArea3DStyle_Light"), 291 ] 292 public LightStyle LightStyle 293 { 294 get 295 { 296 return _lightStyle; 297 } 298 set 299 { 300 _lightStyle = value; 301 if (this._chartArea != null) 302 { 303 this._chartArea.Invalidate(); 304 } 305 } 306 } 307 308 /// <summary> 309 /// Gets or sets the percent of perspective for a 3D chart area. 310 /// </summary> 311 [ 312 SRCategory("CategoryAttribute3D"), 313 Bindable(true), 314 DefaultValue(0), 315 SRDescription("DescriptionAttributeChartArea3DStyle_Perspective"), 316 RefreshPropertiesAttribute(RefreshProperties.All) 317 ] 318 public int Perspective 319 { 320 get 321 { 322 return _perspective; 323 } 324 set 325 { 326 if(value < 0 || value > 100) 327 { 328 throw (new ArgumentOutOfRangeException("value", SR.ExceptionChartArea3DPerspectiveInvalid)); 329 } 330 331 _perspective = value; 332 333 // Adjust 3D properties values 334 if (_perspective != 0) 335 { 336 // Disable right angle axes 337 this._isRightAngleAxes = false; 338 } 339 340 if (this._chartArea != null) 341 { 342 this._chartArea.Invalidate(); 343 } 344 } 345 } 346 347 /// <summary> 348 /// Gets or sets the inclination for a 3D chart area. 349 /// </summary> 350 [ 351 SRCategory("CategoryAttribute3D"), 352 Bindable(true), 353 DefaultValue(30), 354 SRDescription("DescriptionAttributeChartArea3DStyle_Inclination"), 355 RefreshPropertiesAttribute(RefreshProperties.All) 356 ] 357 public int Inclination 358 { 359 get 360 { 361 return _inclination; 362 } 363 set 364 { 365 if(value < -90 || value > 90) 366 { 367 throw (new ArgumentOutOfRangeException("value", SR.ExceptionChartArea3DInclinationInvalid)); 368 } 369 _inclination = value; 370 371 if (this._chartArea != null) 372 { 373 this._chartArea.Invalidate(); 374 } 375 } 376 } 377 378 /// <summary> 379 /// Gets or sets the rotation angle for a 3D chart area. 380 /// </summary> 381 [ 382 SRCategory("CategoryAttribute3D"), 383 Bindable(true), 384 DefaultValue(30), 385 SRDescription("DescriptionAttributeChartArea3DStyle_Rotation"), 386 RefreshPropertiesAttribute(RefreshProperties.All) 387 ] 388 public int Rotation 389 { 390 get 391 { 392 return _rotation; 393 } 394 set 395 { 396 if(value < -180 || value > 180) 397 { 398 throw (new ArgumentOutOfRangeException("value", SR.ExceptionChartArea3DRotationInvalid)); 399 } 400 _rotation = value; 401 402 if (this._chartArea != null) 403 { 404 this._chartArea.Invalidate(); 405 } 406 } 407 } 408 409 /// <summary> 410 /// Gets or sets the width of the walls displayed in 3D chart areas. 411 /// </summary> 412 [ 413 SRCategory("CategoryAttribute3D"), 414 Bindable(true), 415 DefaultValue(7), 416 SRDescription("DescriptionAttributeChartArea3DStyle_WallWidth"), 417 RefreshPropertiesAttribute(RefreshProperties.All) 418 ] 419 public int WallWidth 420 { 421 get 422 { 423 return _wallWidth; 424 } 425 set 426 { 427 if(value < 0 || value > 30) 428 { 429 throw (new ArgumentOutOfRangeException("value", SR.ExceptionChartArea3DWallWidthInvalid)); 430 } 431 432 _wallWidth = value; 433 if (this._chartArea != null) 434 { 435 this._chartArea.Invalidate(); 436 } 437 } 438 } 439 440 /// <summary> 441 /// Gets or sets the depth of data points displayed in 3D chart areas (0-1000%). 442 /// </summary> 443 [ 444 SRCategory("CategoryAttribute3D"), 445 Bindable(true), 446 DefaultValue(100), 447 SRDescription("DescriptionAttributeChartArea3DStyle_PointDepth"), 448 RefreshPropertiesAttribute(RefreshProperties.All) 449 ] 450 public int PointDepth 451 { 452 get 453 { 454 return _pointDepth; 455 } 456 set 457 { 458 if(value < 0 || value > 1000) 459 { 460 throw (new ArgumentOutOfRangeException("value", SR.ExceptionChartArea3DPointsDepthInvalid)); 461 } 462 463 _pointDepth = value; 464 if (this._chartArea != null) 465 { 466 this._chartArea.Invalidate(); 467 } 468 } 469 } 470 471 /// <summary> 472 /// Gets or sets the distance between series rows in 3D chart areas (0-1000%). 473 /// </summary> 474 [ 475 SRCategory("CategoryAttribute3D"), 476 Bindable(true), 477 DefaultValue(100), 478 SRDescription("DescriptionAttributeChartArea3DStyle_PointGapDepth"), 479 RefreshPropertiesAttribute(RefreshProperties.All) 480 ] 481 public int PointGapDepth 482 { 483 get 484 { 485 return _pointGapDepth; 486 } 487 set 488 { 489 if(value < 0 || value > 1000) 490 { 491 throw (new ArgumentOutOfRangeException("value", SR.ExceptionChartArea3DPointsGapInvalid)); 492 } 493 494 _pointGapDepth = value; 495 if (this._chartArea != null) 496 { 497 this._chartArea.Invalidate(); 498 } 499 } 500 } 501 502 #endregion 503 } 504 505 /// <summary> 506 /// ChartArea3D class represents 3D chart area. It contains all the 3D 507 /// scene settings and methods for drawing the 3D plotting area, and calculating 508 /// the depth of chart elements. 509 /// </summary> 510 public partial class ChartArea 511 { 512 #region Fields 513 514 // Chart area 3D style attribuytes 515 private ChartArea3DStyle _area3DStyle = new ChartArea3DStyle(); 516 517 // Coordinate convertion matrix 518 internal Matrix3D matrix3D = new Matrix3D(); 519 520 // Chart area scene wall width in relative coordinates 521 internal SizeF areaSceneWallWidth = SizeF.Empty; 522 523 // Chart area scene depth 524 internal float areaSceneDepth = 0; 525 526 // Visible surfaces in plotting area 527 private SurfaceNames _visibleSurfaces; 528 529 // Z axis depth of series points 530 private double _pointsDepth = 0; 531 532 // Z axis depth of the gap between isClustered series 533 private double _pointsGapDepth = 0; 534 535 /// <summary> 536 /// Indicates that series order should be reversed to simulate Y axis rotation. 537 /// </summary> 538 private bool _reverseSeriesOrder = false; 539 540 /// <summary> 541 /// Old X axis reversed flag 542 /// </summary> 543 internal bool oldReverseX = false; 544 545 /// <summary> 546 /// Old Y axis reversed flag 547 /// </summary> 548 internal bool oldReverseY = false; 549 550 /// <summary> 551 /// Old Y axis rotation angle 552 /// </summary> 553 internal int oldYAngle = 30; 554 555 /// <summary> 556 /// List of all stack group names 557 /// </summary> 558 private ArrayList _stackGroupNames = null; 559 560 /// <summary> 561 /// This list contains an array of series names for each 3D cluster 562 /// </summary> 563 internal List<List<string>> seriesClusters = null; 564 565 #endregion 566 567 #region 3D Style properties 568 569 /// <summary> 570 /// Gets or sets a ChartArea3DStyle object, used to draw all series in a chart area in 3D. 571 /// </summary> 572 [ 573 SRCategory("CategoryAttribute3D"), 574 Bindable(true), 575 DefaultValue(null), 576 SRDescription("DescriptionAttributeArea3DStyle"), 577 DesignerSerializationVisibility(DesignerSerializationVisibility.Content), 578 TypeConverter(typeof(NoNameExpandableObjectConverter)), 579 #if !WINFORMS_CONTROL 580 PersistenceMode(PersistenceMode.InnerProperty), 581 #endif 582 ] 583 public ChartArea3DStyle Area3DStyle 584 { 585 get 586 { 587 return _area3DStyle; 588 } 589 set 590 { 591 _area3DStyle = value; 592 593 // Initialize style object 594 _area3DStyle.Initialize((ChartArea)this); 595 } 596 } 597 598 /// <summary> 599 /// Indicates that series order should be reversed to simulate Y axis rotation. 600 /// </summary> 601 internal bool ReverseSeriesOrder 602 { 603 get { return _reverseSeriesOrder; } 604 } 605 606 /// <summary> 607 /// Gets the list of all stack group names 608 /// </summary> 609 internal ArrayList StackGroupNames 610 { 611 get { return _stackGroupNames; } 612 } 613 614 #endregion 615 616 #region 3D Coordinates transfotmation methods 617 618 /// <summary> 619 /// Call this method to apply 3D transformations on an array of 3D points (must be done before calling GDI+ drawing methods). 620 /// </summary> 621 /// <param name="points">3D Points array.</param> TransformPoints( Point3D[] points )622 public void TransformPoints( Point3D[] points ) 623 { 624 // Convert Z coordinates from 0-100% to axis values 625 foreach(Point3D pt in points) 626 { 627 pt.Z = (pt.Z / 100f) * this.areaSceneDepth; 628 } 629 630 // Transform points 631 this.matrix3D.TransformPoints( points ); 632 } 633 634 #endregion 635 636 #region 3D Scene drawing methods 637 638 /// <summary> 639 /// Draws chart area 3D scene, which consists of 3 or 2 walls. 640 /// </summary> 641 /// <param name="graph">Chart graphics object.</param> 642 /// <param name="position">Chart area 2D position.</param> DrawArea3DScene(ChartGraphics graph, RectangleF position)643 internal void DrawArea3DScene(ChartGraphics graph, RectangleF position) 644 { 645 // Reference to the chart area class 646 ChartArea chartArea = (ChartArea)this; 647 648 // Calculate relative size of the wall 649 areaSceneWallWidth = graph.GetRelativeSize( new SizeF(this.Area3DStyle.WallWidth, this.Area3DStyle.WallWidth)); 650 651 //*********************************************************** 652 //** Calculate the depth of the chart area scene 653 //*********************************************************** 654 areaSceneDepth = GetArea3DSceneDepth(); 655 656 //*********************************************************** 657 //** Initialize coordinate transformation matrix 658 //*********************************************************** 659 this.matrix3D.Initialize( 660 position, 661 areaSceneDepth, 662 this.Area3DStyle.Inclination, 663 this.Area3DStyle.Rotation, 664 this.Area3DStyle.Perspective, 665 this.Area3DStyle.IsRightAngleAxes); 666 667 //*********************************************************** 668 //** Initialize Lighting 669 //*********************************************************** 670 this.matrix3D.InitLight( 671 this.Area3DStyle.LightStyle 672 ); 673 674 //*********************************************************** 675 //** Find chart area visible surfaces 676 //*********************************************************** 677 _visibleSurfaces = graph.GetVisibleSurfaces( 678 position, 679 0, 680 areaSceneDepth, 681 this.matrix3D); 682 683 //*********************************************************** 684 //** Chech if area scene should be drawn 685 //*********************************************************** 686 Color sceneBackColor = chartArea.BackColor; 687 688 // Do not draw the transparent walls 689 if(sceneBackColor == Color.Transparent) 690 { 691 // Area wall is not visible 692 areaSceneWallWidth = SizeF.Empty; 693 return; 694 } 695 696 // If color is not set (default) - use LightGray 697 if(sceneBackColor == Color.Empty) 698 { 699 sceneBackColor = Color.LightGray; 700 } 701 702 //*********************************************************** 703 //** Adjust scene 2D rectangle so that wall are drawn 704 //** outside plotting area. 705 //*********************************************************** 706 // If bottom wall is visible 707 if(IsBottomSceneWallVisible()) 708 { 709 position.Height += areaSceneWallWidth.Height; 710 } 711 712 // Adjust for the left/right wall 713 position.Width += areaSceneWallWidth.Width; 714 if(this.Area3DStyle.Rotation > 0) 715 { 716 position.X -= areaSceneWallWidth.Width; 717 } 718 719 //*********************************************************** 720 //** Draw scene walls 721 //*********************************************************** 722 723 // Draw back wall 724 RectangleF wallRect2D = new RectangleF(position.Location, position.Size); 725 float wallDepth = areaSceneWallWidth.Width; 726 float wallZPosition = -wallDepth; 727 728 // For isometric projection Front wall should be visible sometimes 729 if( IsMainSceneWallOnFront()) 730 { 731 wallZPosition = areaSceneDepth; 732 } 733 734 graph.Fill3DRectangle( 735 wallRect2D, 736 wallZPosition, 737 wallDepth, 738 this.matrix3D, 739 chartArea.Area3DStyle.LightStyle, 740 sceneBackColor, 741 chartArea.BorderColor, 742 chartArea.BorderWidth, 743 chartArea.BorderDashStyle, 744 DrawingOperationTypes.DrawElement ); 745 746 // Draw side wall on the left or right side 747 wallRect2D = new RectangleF(position.Location, position.Size); 748 wallRect2D.Width = areaSceneWallWidth.Width; 749 if(!IsSideSceneWallOnLeft()) 750 { 751 // Wall is on the right side 752 wallRect2D.X = position.Right - areaSceneWallWidth.Width; 753 } 754 graph.Fill3DRectangle( 755 wallRect2D, 756 0f, 757 areaSceneDepth, 758 this.matrix3D, 759 chartArea.Area3DStyle.LightStyle, 760 sceneBackColor, 761 chartArea.BorderColor, 762 chartArea.BorderWidth, 763 chartArea.BorderDashStyle, 764 DrawingOperationTypes.DrawElement); 765 766 // Draw bottom wall 767 if(IsBottomSceneWallVisible()) 768 { 769 wallRect2D = new RectangleF(position.Location, position.Size); 770 wallRect2D.Height = areaSceneWallWidth.Height; 771 wallRect2D.Y = position.Bottom - areaSceneWallWidth.Height; 772 wallRect2D.Width -= areaSceneWallWidth.Width; 773 if(IsSideSceneWallOnLeft()) 774 { 775 wallRect2D.X += areaSceneWallWidth.Width; 776 } 777 778 wallZPosition = 0; 779 graph.Fill3DRectangle( 780 wallRect2D, 781 0f, 782 areaSceneDepth, 783 this.matrix3D, 784 chartArea.Area3DStyle.LightStyle, 785 sceneBackColor, 786 chartArea.BorderColor, 787 chartArea.BorderWidth, 788 chartArea.BorderDashStyle, 789 DrawingOperationTypes.DrawElement ); 790 } 791 792 } 793 794 /// <summary> 795 /// Helper method which return True if bottom wall of the 796 /// chart area scene is visible. 797 /// </summary> 798 /// <returns>True if bottom wall is visible.</returns> IsBottomSceneWallVisible()799 internal bool IsBottomSceneWallVisible() 800 { 801 return (this.Area3DStyle.Inclination >= 0); 802 } 803 804 /// <summary> 805 /// Helper method which return True if main wall of the 806 /// chart area scene is displayed on the front side. 807 /// </summary> 808 /// <returns>True if front wall is visible.</returns> IsMainSceneWallOnFront()809 internal bool IsMainSceneWallOnFront() 810 { 811 // Note: Not used in this version! 812 return false; 813 } 814 815 /// <summary> 816 /// Helper method which return True if side wall of the 817 /// chart area scene is displayed on the left side. 818 /// </summary> 819 /// <returns>True if bottom wall is visible.</returns> IsSideSceneWallOnLeft()820 internal bool IsSideSceneWallOnLeft() 821 { 822 return (this.Area3DStyle.Rotation > 0); 823 } 824 825 #endregion 826 827 #region 3D Scene depth claculation methods 828 829 /// <summary> 830 /// Call this method to get the Z position of a series (useful for custom drawing). 831 /// </summary> 832 /// <param name="series">The series to retrieve the Z position for.</param> 833 /// <returns>The Z position of the specified series. Measured as a percentage of the chart area's depth.</returns> GetSeriesZPosition(Series series)834 public float GetSeriesZPosition(Series series) 835 { 836 float positionZ, depth; 837 GetSeriesZPositionAndDepth(series, out depth, out positionZ); 838 return ((positionZ + depth/2f) / this.areaSceneDepth) * 100f; 839 } 840 841 /// <summary> 842 /// Call this method to get the depth of a series in a chart area. 843 /// </summary> 844 /// <param name="series">The series to retrieve the depth for.</param> 845 /// <returns>The depth of the specified series. Measured as a percentage of the chart area's depth.</returns> GetSeriesDepth(Series series)846 public float GetSeriesDepth(Series series) 847 { 848 float positionZ, depth; 849 GetSeriesZPositionAndDepth(series, out depth, out positionZ); 850 return (depth / this.areaSceneDepth) * 100f; 851 } 852 853 /// <summary> 854 /// Calculates area 3D scene depth depending on the number of isClustered 855 /// series and interval between points. 856 /// </summary> 857 /// <returns>Returns the depth of the chart area scene.</returns> GetArea3DSceneDepth()858 private float GetArea3DSceneDepth() 859 { 860 //*********************************************************** 861 //** Calculate the smallest interval between points 862 //*********************************************************** 863 864 // Check if any series attached to the area is indexed 865 bool indexedSeries = ChartHelper.IndexedSeries(this.Common, this._series.ToArray()); 866 867 // Smallest interval series 868 Series smallestIntervalSeries = null; 869 if(this._series.Count > 0) 870 { 871 smallestIntervalSeries = this.Common.DataManager.Series[(string)this._series[0]]; 872 } 873 874 // Get X axis 875 Axis xAxis = ((ChartArea)this).AxisX; 876 if(this._series.Count > 0) 877 { 878 Series firstSeries = this.Common.DataManager.Series[this._series[0]]; 879 if(firstSeries != null && firstSeries.XAxisType == AxisType.Secondary) 880 { 881 xAxis = ((ChartArea)this).AxisX2; 882 } 883 } 884 885 // Get smallest interval between points (use interval 1 for indexed series) 886 double clusteredInterval = 1; 887 if(!indexedSeries) 888 { 889 bool sameInterval; 890 clusteredInterval = this.GetPointsInterval(this._series, xAxis.IsLogarithmic, xAxis.logarithmBase, false, out sameInterval, out smallestIntervalSeries); 891 } 892 893 //*********************************************************** 894 //** Check if "DrawSideBySide" attribute is set. 895 //*********************************************************** 896 bool drawSideBySide = false; 897 if(smallestIntervalSeries != null) 898 { 899 drawSideBySide = Common.ChartTypeRegistry.GetChartType(smallestIntervalSeries.ChartTypeName).SideBySideSeries; 900 foreach(string seriesName in this._series) 901 { 902 if(this.Common.DataManager.Series[seriesName].IsCustomPropertySet(CustomPropertyName.DrawSideBySide)) 903 { 904 string attribValue = this.Common.DataManager.Series[seriesName][CustomPropertyName.DrawSideBySide]; 905 if(String.Compare(attribValue, "False", StringComparison.OrdinalIgnoreCase) == 0) 906 { 907 drawSideBySide = false; 908 } 909 else if(String.Compare(attribValue, "True", StringComparison.OrdinalIgnoreCase) == 0) 910 { 911 drawSideBySide = true; 912 } 913 else if (String.Compare(attribValue, "Auto", StringComparison.OrdinalIgnoreCase) == 0) 914 { 915 // Do nothing 916 } 917 else 918 { 919 throw (new InvalidOperationException(SR.ExceptionAttributeDrawSideBySideInvalid)); 920 } 921 } 922 } 923 } 924 925 // Get smallest interval cate----cal axis 926 Axis categoricalAxis = ((ChartArea)this).AxisX; 927 if(smallestIntervalSeries != null && smallestIntervalSeries.XAxisType == AxisType.Secondary) 928 { 929 categoricalAxis = ((ChartArea)this).AxisX2; 930 } 931 932 //*********************************************************** 933 //** If series with the smallest interval is displayed 934 //** side-by-side - devide the interval by number of series 935 //** of the same chart type. 936 //*********************************************************** 937 double pointWidthSize = 0.8; 938 int seriesNumber = 1; 939 if(smallestIntervalSeries != null) 940 { 941 // Check if series is side-by-side 942 if(this.Area3DStyle.IsClustered && drawSideBySide) 943 { 944 // Count number of side-by-side series 945 seriesNumber = 0; 946 foreach(string seriesName in this._series) 947 { 948 // Get series object from name 949 Series curSeries = this.Common.DataManager.Series[seriesName]; 950 if(String.Compare(curSeries.ChartTypeName, smallestIntervalSeries.ChartTypeName, StringComparison.OrdinalIgnoreCase) == 0 ) 951 { 952 ++seriesNumber; 953 } 954 } 955 } 956 } 957 958 959 960 //*********************************************************** 961 //** Stacked column and bar charts can be drawn side-by-side 962 //** using the StackGroupName custom properties. The code 963 //** checks if multiple groups are used how many of these 964 //** groups exsist. 965 //** 966 //** If isClustered mode enabled each stack group is drawn 967 //** using it's own cluster. 968 //*********************************************************** 969 if(smallestIntervalSeries != null && this.Area3DStyle.IsClustered) 970 { 971 // Check series support stack groups 972 if(Common.ChartTypeRegistry.GetChartType(smallestIntervalSeries.ChartTypeName).SupportStackedGroups) 973 { 974 // Calculate how many stack groups exsist 975 seriesNumber = 0; 976 ArrayList stackGroupNames = new ArrayList(); 977 foreach(string seriesName in this._series) 978 { 979 // Get series object from name 980 Series curSeries = this.Common.DataManager.Series[seriesName]; 981 if(String.Compare(curSeries.ChartTypeName, smallestIntervalSeries.ChartTypeName, StringComparison.OrdinalIgnoreCase) == 0 ) 982 { 983 string seriesStackGroupName = string.Empty; 984 if(curSeries.IsCustomPropertySet(CustomPropertyName.StackedGroupName)) 985 { 986 seriesStackGroupName = curSeries[CustomPropertyName.StackedGroupName]; 987 } 988 989 // Add group name if it do not already exsist 990 if(!stackGroupNames.Contains(seriesStackGroupName)) 991 { 992 stackGroupNames.Add(seriesStackGroupName); 993 } 994 } 995 } 996 seriesNumber = stackGroupNames.Count; 997 } 998 } 999 1000 1001 1002 //*********************************************************** 1003 //** Check if series provide custom value for point\gap depth 1004 //*********************************************************** 1005 _pointsDepth = clusteredInterval * pointWidthSize * this.Area3DStyle.PointDepth / 100.0; 1006 _pointsDepth = categoricalAxis.GetPixelInterval(_pointsDepth); 1007 if(smallestIntervalSeries != null) 1008 { 1009 _pointsDepth = smallestIntervalSeries.GetPointWidth( 1010 this.Common.graph, 1011 categoricalAxis, 1012 clusteredInterval, 1013 0.8) / seriesNumber; 1014 _pointsDepth *= this.Area3DStyle.PointDepth / 100.0; 1015 } 1016 _pointsGapDepth = (_pointsDepth * 0.8) * this.Area3DStyle.PointGapDepth / 100.0; 1017 1018 // Get point depth and gap from series 1019 if(smallestIntervalSeries != null) 1020 { 1021 smallestIntervalSeries.GetPointDepthAndGap( 1022 this.Common.graph, 1023 categoricalAxis, 1024 ref _pointsDepth, 1025 ref _pointsGapDepth); 1026 } 1027 1028 1029 //*********************************************************** 1030 //** Calculate scene depth 1031 //*********************************************************** 1032 return (float)((_pointsGapDepth + _pointsDepth) * GetNumberOfClusters()); 1033 } 1034 1035 /// <summary> 1036 /// Calculates the depth and Z position for specified series. 1037 /// </summary> 1038 /// <param name="series">Series object.</param> 1039 /// <param name="depth">Returns series depth.</param> 1040 /// <param name="positionZ">Returns series Z position.</param> GetSeriesZPositionAndDepth(Series series, out float depth, out float positionZ)1041 internal void GetSeriesZPositionAndDepth(Series series, out float depth, out float positionZ) 1042 { 1043 // Check arguments 1044 if (series == null) 1045 throw new ArgumentNullException("series"); 1046 1047 // Get series cluster index 1048 int seriesIndex = GetSeriesClusterIndex(series); 1049 1050 // Initialize the output parameters 1051 depth = (float)_pointsDepth; 1052 positionZ = (float)(_pointsGapDepth / 2.0 + (_pointsDepth + _pointsGapDepth) * seriesIndex); 1053 } 1054 1055 1056 1057 /// <summary> 1058 /// Returns number of clusters on the Z axis. 1059 /// </summary> 1060 /// <returns>Number of clusters on the Z axis.</returns> GetNumberOfClusters()1061 internal int GetNumberOfClusters() 1062 { 1063 if(this.seriesClusters == null) 1064 { 1065 // Lists that hold processed chart types and stacked groups 1066 ArrayList processedChartTypes = new ArrayList(); 1067 ArrayList processedStackedGroups = new ArrayList(); 1068 1069 // Reset series cluster list 1070 this.seriesClusters = new List<List<string>>(); 1071 1072 // Iterate through all series that belong to this chart area 1073 int clusterIndex = -1; 1074 foreach(string seriesName in this._series) 1075 { 1076 // Get series object by name 1077 Series curSeries = this.Common.DataManager.Series[seriesName]; 1078 1079 // Check if stacked chart type is using multiple groups that 1080 // can be displayed in individual clusters 1081 if(!this.Area3DStyle.IsClustered && 1082 Common.ChartTypeRegistry.GetChartType(curSeries.ChartTypeName).SupportStackedGroups) 1083 { 1084 // Get group name 1085 string stackGroupName = StackedColumnChart.GetSeriesStackGroupName(curSeries); 1086 1087 // Check if group was already counted 1088 if(processedStackedGroups.Contains(stackGroupName)) 1089 { 1090 // Find in which cluster this stacked group is located 1091 bool found = false; 1092 for(int index = 0; !found && index < this.seriesClusters.Count; index++) 1093 { 1094 foreach(string name in this.seriesClusters[index]) 1095 { 1096 // Get series object by name 1097 Series ser = this.Common.DataManager.Series[name]; 1098 if(stackGroupName == StackedColumnChart.GetSeriesStackGroupName(ser)) 1099 { 1100 clusterIndex = index; 1101 found = true; 1102 } 1103 } 1104 } 1105 } 1106 else 1107 { 1108 // Increase cluster index 1109 clusterIndex = this.seriesClusters.Count; 1110 1111 // Add processed group name 1112 processedStackedGroups.Add(stackGroupName); 1113 } 1114 } 1115 1116 1117 // Chech if series is displayed in the same cluster than other series 1118 else if( Common.ChartTypeRegistry.GetChartType(curSeries.ChartTypeName).Stacked || 1119 (this.Area3DStyle.IsClustered && Common.ChartTypeRegistry.GetChartType(curSeries.ChartTypeName).SideBySideSeries) ) 1120 { 1121 // Check if this chart type is already in the list 1122 if(processedChartTypes.Contains(curSeries.ChartTypeName.ToUpper(System.Globalization.CultureInfo.InvariantCulture))) 1123 { 1124 // Find in which cluster this chart type is located 1125 bool found = false; 1126 for(int index = 0; !found && index < this.seriesClusters.Count; index++) 1127 { 1128 foreach(string name in this.seriesClusters[index]) 1129 { 1130 // Get series object by name 1131 Series ser = this.Common.DataManager.Series[name]; 1132 if(ser.ChartTypeName.ToUpper(System.Globalization.CultureInfo.InvariantCulture) == 1133 curSeries.ChartTypeName.ToUpper(System.Globalization.CultureInfo.InvariantCulture)) 1134 { 1135 clusterIndex = index; 1136 found = true; 1137 } 1138 } 1139 } 1140 } 1141 else 1142 { 1143 // Increase cluster index 1144 clusterIndex = this.seriesClusters.Count; 1145 1146 // Add new chart type into the collection 1147 processedChartTypes.Add(curSeries.ChartTypeName.ToUpper(System.Globalization.CultureInfo.InvariantCulture)); 1148 } 1149 } 1150 else 1151 { 1152 // Create New cluster 1153 clusterIndex = this.seriesClusters.Count; 1154 } 1155 1156 // Create an item in the cluster list that will hold all series names 1157 if(this.seriesClusters.Count <= clusterIndex) 1158 { 1159 this.seriesClusters.Add(new List<string>()); 1160 } 1161 1162 // Add series name into the current cluster 1163 this.seriesClusters[clusterIndex].Add(seriesName); 1164 } 1165 } 1166 1167 return this.seriesClusters.Count; 1168 } 1169 1170 /// <summary> 1171 /// Get series cluster index. 1172 /// </summary> 1173 /// <param name="series">Series object.</param> 1174 /// <returns>Series cluster index.</returns> GetSeriesClusterIndex(Series series)1175 internal int GetSeriesClusterIndex(Series series) 1176 { 1177 // Fill list of clusters 1178 if(this.seriesClusters == null) 1179 { 1180 this.GetNumberOfClusters(); 1181 } 1182 1183 // Iterate through all clusters 1184 for(int clusterIndex = 0; clusterIndex < this.seriesClusters.Count; clusterIndex++) 1185 { 1186 List<string> seriesNames = this.seriesClusters[clusterIndex]; 1187 1188 // Iterate through all series names 1189 foreach(string seriesName in seriesNames) 1190 { 1191 if(seriesName == series.Name) 1192 { 1193 // Check if series are drawn in reversed order 1194 if(this._reverseSeriesOrder) 1195 { 1196 clusterIndex = (this.seriesClusters.Count - 1) - clusterIndex; 1197 } 1198 return clusterIndex; 1199 } 1200 } 1201 } 1202 return 0; 1203 } 1204 1205 1206 1207 #endregion 1208 1209 #region 3D Scene helper methods 1210 1211 /// <summary> 1212 /// This method is used to calculate estimated scene 1213 /// depth. Regular scene depth method can not be used 1214 /// because Plot area position is zero. Instead, Chart 1215 /// area position is used to find depth of the scene. 1216 /// Algorithm which draws axis labels will decide what 1217 /// should be size and position of plotting area. 1218 /// </summary> 1219 /// <returns>Returns estimated scene depth</returns> GetEstimatedSceneDepth()1220 private float GetEstimatedSceneDepth() 1221 { 1222 float sceneDepth; 1223 1224 ChartArea area = (ChartArea) this; 1225 1226 1227 // Reset current list of clusters 1228 this.seriesClusters = null; 1229 1230 1231 ElementPosition plottingAreaRect = area.InnerPlotPosition; 1232 1233 area.AxisX.PlotAreaPosition = area.Position; 1234 area.AxisY.PlotAreaPosition = area.Position; 1235 area.AxisX2.PlotAreaPosition = area.Position; 1236 area.AxisY2.PlotAreaPosition = area.Position; 1237 1238 sceneDepth = GetArea3DSceneDepth(); 1239 1240 area.AxisX.PlotAreaPosition = plottingAreaRect; 1241 area.AxisY.PlotAreaPosition = plottingAreaRect; 1242 area.AxisX2.PlotAreaPosition = plottingAreaRect; 1243 area.AxisY2.PlotAreaPosition = plottingAreaRect; 1244 1245 return sceneDepth; 1246 } 1247 1248 /// <summary> 1249 /// Estimate Interval for 3D Charts. When scene is rotated the 1250 /// number of labels should be changed. 1251 /// </summary> 1252 /// <param name="graph">Chart graphics object.</param> Estimate3DInterval(ChartGraphics graph )1253 internal void Estimate3DInterval(ChartGraphics graph ) 1254 { 1255 // Reference to the chart area class 1256 ChartArea area = (ChartArea)this; 1257 1258 // Calculate relative size of the wall 1259 areaSceneWallWidth = graph.GetRelativeSize( new SizeF(this.Area3DStyle.WallWidth, this.Area3DStyle.WallWidth)); 1260 1261 //*********************************************************** 1262 //** Calculate the depth of the chart area scene 1263 //*********************************************************** 1264 areaSceneDepth = GetEstimatedSceneDepth(); 1265 1266 RectangleF plottingRect = area.Position.ToRectangleF(); 1267 1268 // Check if plot area position was recalculated. 1269 // If not and non-auto InnerPlotPosition & Position were 1270 // specified - do all needed calculations 1271 if(PlotAreaPosition.Width == 0 && 1272 PlotAreaPosition.Height == 0 && 1273 !area.InnerPlotPosition.Auto 1274 && !area.Position.Auto) 1275 { 1276 // Initialize plotting area position 1277 1278 if(!area.InnerPlotPosition.Auto) 1279 { 1280 plottingRect.X += (area.Position.Width / 100F) * area.InnerPlotPosition.X; 1281 plottingRect.Y += (area.Position.Height / 100F) * area.InnerPlotPosition.Y; 1282 plottingRect.Width = (area.Position.Width / 100F) * area.InnerPlotPosition.Width; 1283 plottingRect.Height = (area.Position.Height / 100F) * area.InnerPlotPosition.Height; 1284 } 1285 1286 } 1287 1288 int yAngle = GetRealYAngle( ); 1289 1290 //*********************************************************** 1291 //** Initialize coordinate transformation matrix 1292 //*********************************************************** 1293 Matrix3D intervalMatrix3D = new Matrix3D(); 1294 intervalMatrix3D.Initialize( 1295 plottingRect, 1296 areaSceneDepth, 1297 this.Area3DStyle.Inclination, 1298 yAngle, 1299 this.Area3DStyle.Perspective, 1300 this.Area3DStyle.IsRightAngleAxes); 1301 bool notUsed; 1302 float zPosition; 1303 double size; 1304 1305 Point3D [] points = new Point3D[8]; 1306 1307 if( area.switchValueAxes ) 1308 { 1309 1310 // X Axis 1311 zPosition = axisX.GetMarksZPosition( out notUsed ); 1312 1313 points[0] = new Point3D( plottingRect.X, plottingRect.Y, zPosition ); 1314 points[1] = new Point3D( plottingRect.X, plottingRect.Bottom, zPosition ); 1315 1316 // Y Axis 1317 zPosition = axisY.GetMarksZPosition( out notUsed ); 1318 1319 points[2] = new Point3D( plottingRect.X, plottingRect.Bottom, zPosition ); 1320 points[3] = new Point3D( plottingRect.Right, plottingRect.Bottom, zPosition ); 1321 1322 // X2 Axis 1323 zPosition = axisX2.GetMarksZPosition( out notUsed ); 1324 1325 points[4] = new Point3D( plottingRect.X, plottingRect.Y, zPosition ); 1326 points[5] = new Point3D( plottingRect.X, plottingRect.Bottom, zPosition ); 1327 1328 // Y2 Axis 1329 zPosition = axisY2.GetMarksZPosition( out notUsed ); 1330 1331 points[6] = new Point3D( plottingRect.X, plottingRect.Y, zPosition ); 1332 points[7] = new Point3D( plottingRect.Right, plottingRect.Y, zPosition ); 1333 } 1334 else 1335 { 1336 // X Axis 1337 zPosition = axisX.GetMarksZPosition( out notUsed ); 1338 1339 points[0] = new Point3D( plottingRect.X, plottingRect.Bottom, zPosition ); 1340 points[1] = new Point3D( plottingRect.Right, plottingRect.Bottom, zPosition ); 1341 1342 // Y Axis 1343 zPosition = axisY.GetMarksZPosition( out notUsed ); 1344 1345 points[2] = new Point3D( plottingRect.X, plottingRect.Y, zPosition ); 1346 points[3] = new Point3D( plottingRect.X, plottingRect.Bottom, zPosition ); 1347 1348 // X2 Axis 1349 zPosition = axisX2.GetMarksZPosition( out notUsed ); 1350 1351 points[4] = new Point3D( plottingRect.X, plottingRect.Y, zPosition ); 1352 points[5] = new Point3D( plottingRect.Right, plottingRect.Y, zPosition ); 1353 1354 // Y2 Axis 1355 zPosition = axisY2.GetMarksZPosition( out notUsed ); 1356 1357 points[6] = new Point3D( plottingRect.X, plottingRect.Y, zPosition ); 1358 points[7] = new Point3D( plottingRect.X, plottingRect.Bottom, zPosition ); 1359 } 1360 1361 // Crossing has to be reset because interval and 1362 // sometimes minimum and maximum are changed. 1363 foreach( Axis axis in area.Axes ) 1364 { 1365 axis.crossing = axis.tempCrossing; 1366 } 1367 1368 // Transform all points 1369 intervalMatrix3D.TransformPoints( points ); 1370 1371 int axisIndx = 0; 1372 foreach( Axis axis in area.Axes ) 1373 { 1374 // Find size of projected axis 1375 size = Math.Sqrt( 1376 ( points[axisIndx].X - points[axisIndx+1].X ) * ( points[axisIndx].X - points[axisIndx+1].X ) + 1377 ( points[axisIndx].Y - points[axisIndx+1].Y ) * ( points[axisIndx].Y - points[axisIndx+1].Y ) ); 1378 1379 // At the beginning plotting area chart is not calculated because 1380 // algorithm for labels calculates plotting area position. To 1381 // calculate labels position we need interval and interval 1382 // need this correction. Because of that Chart area is used 1383 // instead of plotting area position. If secondary label is 1384 // enabled error for using chart area position instead of 1385 // plotting area position is much bigger. This value 1386 // corrects this error. 1387 float plottingChartAreaCorrection = 1; 1388 if( !area.switchValueAxes ) 1389 { 1390 plottingChartAreaCorrection = 0.5F; 1391 } 1392 1393 // Set correction for axis size 1394 if( axis.AxisName == AxisName.X || axis.AxisName == AxisName.X2 ) 1395 { 1396 if( area.switchValueAxes ) 1397 axis.interval3DCorrection = size / plottingRect.Height; 1398 else 1399 axis.interval3DCorrection = size / plottingRect.Width; 1400 } 1401 else 1402 { 1403 if( area.switchValueAxes ) 1404 axis.interval3DCorrection = size / plottingRect.Width; 1405 else 1406 axis.interval3DCorrection = size / plottingRect.Height * plottingChartAreaCorrection; 1407 } 1408 1409 // There is a limit for correction 1410 if( axis.interval3DCorrection < 0.15 ) 1411 axis.interval3DCorrection = 0.15; 1412 1413 // There is a limit for correction 1414 if( axis.interval3DCorrection > 0.8 ) 1415 axis.interval3DCorrection = 1.0; 1416 1417 axisIndx += 2; 1418 1419 } 1420 } 1421 1422 1423 /// <summary> 1424 /// Calculates real Y angle from Y angle and reverseSeriesOrder field 1425 /// </summary> 1426 /// <returns>Real Y angle</returns> GetRealYAngle( )1427 internal int GetRealYAngle( ) 1428 { 1429 int yAngle; 1430 1431 // Case from -90 to 90 1432 yAngle = this.Area3DStyle.Rotation; 1433 1434 // Case from 90 to 180 1435 if( this._reverseSeriesOrder && this.Area3DStyle.Rotation >= 0 ) 1436 yAngle = this.Area3DStyle.Rotation - 180; 1437 1438 // Case from -90 to -180 1439 if( this._reverseSeriesOrder && this.Area3DStyle.Rotation <= 0 ) 1440 yAngle = this.Area3DStyle.Rotation + 180; 1441 1442 return yAngle; 1443 } 1444 1445 /// <summary> 1446 /// Check if surface element should be drawn on the Back or Front layer. 1447 /// </summary> 1448 /// <param name="surfaceName">Surface name.</param> 1449 /// <param name="backLayer">Back/front layer.</param> 1450 /// <param name="onEdge">Indicates that element is on the edge (drawn on the back layer).</param> 1451 /// <returns>True if element should be drawn.</returns> ShouldDrawOnSurface(SurfaceNames surfaceName, bool backLayer, bool onEdge)1452 internal bool ShouldDrawOnSurface(SurfaceNames surfaceName, bool backLayer, bool onEdge) 1453 { 1454 // Check if surface element should be drawn on the Back or Front layer. 1455 bool isVisible = ((this._visibleSurfaces & surfaceName) == surfaceName); 1456 1457 // Elements on the edge are drawn on the back layer 1458 if(onEdge) 1459 { 1460 return backLayer; 1461 } 1462 1463 return (backLayer == (!isVisible) ); 1464 } 1465 1466 /// <summary> 1467 /// Indicates that data points in all series of this 1468 /// chart area should be drawn in reversed order. 1469 /// </summary> 1470 /// <returns>True if series points should be drawn in reversed order.</returns> DrawPointsInReverseOrder()1471 internal bool DrawPointsInReverseOrder() 1472 { 1473 return (this.Area3DStyle.Rotation <= 0); 1474 } 1475 1476 /// <summary> 1477 /// Checks if points should be drawn from sides to center. 1478 /// </summary> 1479 /// <param name="coord">Which coordinate of COP (X, Y or Z) to test for surface overlapping</param> 1480 /// <returns>True if points should be drawn from sides to center.</returns> DrawPointsToCenter(ref COPCoordinates coord)1481 internal bool DrawPointsToCenter(ref COPCoordinates coord) 1482 { 1483 bool result = false; 1484 COPCoordinates resultCoordinates = 0; 1485 1486 // Check only if perspective is set 1487 if(this.Area3DStyle.Perspective != 0) 1488 { 1489 if( (coord & COPCoordinates.X) == COPCoordinates.X ) 1490 { 1491 // Only when Left & Right sides of plotting area are invisible 1492 if ((this._visibleSurfaces & SurfaceNames.Left) == 0 && 1493 (this._visibleSurfaces & SurfaceNames.Right) == 0) 1494 { 1495 result = true; 1496 } 1497 resultCoordinates = resultCoordinates | COPCoordinates.X; 1498 } 1499 if( (coord & COPCoordinates.Y) == COPCoordinates.Y ) 1500 { 1501 // Only when Top & Bottom sides of plotting area are invisible 1502 if ((this._visibleSurfaces & SurfaceNames.Top) == 0 && 1503 (this._visibleSurfaces & SurfaceNames.Bottom) == 0) 1504 { 1505 result = true; 1506 } 1507 resultCoordinates = resultCoordinates | COPCoordinates.Y; 1508 } 1509 if( (coord & COPCoordinates.Z) == COPCoordinates.Z ) 1510 { 1511 // Only when Front & Back sides of plotting area are invisible 1512 if ((this._visibleSurfaces & SurfaceNames.Front) == 0 && 1513 (this._visibleSurfaces & SurfaceNames.Back) == 0) 1514 { 1515 result = true; 1516 } 1517 resultCoordinates = resultCoordinates | COPCoordinates.Z; 1518 } 1519 } 1520 1521 return result; 1522 } 1523 1524 /// <summary> 1525 /// Checks if series should be drawn from sides to center. 1526 /// </summary> 1527 /// <returns>True if series should be drawn from sides to center.</returns> DrawSeriesToCenter()1528 internal bool DrawSeriesToCenter() 1529 { 1530 // Check only if perspective is set 1531 if(this.Area3DStyle.Perspective != 0) 1532 { 1533 // Only when Left & Right sides of plotting area are invisible 1534 if ((this._visibleSurfaces & SurfaceNames.Front) == 0 && 1535 (this._visibleSurfaces & SurfaceNames.Back) == 0) 1536 { 1537 return true; 1538 } 1539 } 1540 1541 return false; 1542 } 1543 1544 #endregion 1545 1546 #region 3D Series drawing and selection methods 1547 1548 /// <summary> 1549 /// Draws 3D series in the chart area. 1550 /// </summary> 1551 /// <param name="graph">Chart graphics object.</param> PaintChartSeries3D( ChartGraphics graph )1552 internal void PaintChartSeries3D( ChartGraphics graph ) 1553 { 1554 // Reference to the chart area object 1555 ChartArea area = (ChartArea)this; 1556 1557 // Get order of series drawing 1558 List<Series> seriesDrawingOrder = GetSeriesDrawingOrder(_reverseSeriesOrder); 1559 1560 // Loop through all series in the order of drawing 1561 IChartType type; 1562 foreach( object seriesObj in seriesDrawingOrder) 1563 { 1564 Series series = (Series)seriesObj; 1565 type = Common.ChartTypeRegistry.GetChartType(series.ChartTypeName); 1566 type.Paint( graph, Common, area, series ); 1567 } 1568 } 1569 1570 #endregion 1571 1572 #region 3D Series & Points drawing order methods 1573 1574 /// <summary> 1575 /// Gets a list of series names that belong to the same 3D cluster. 1576 /// </summary> 1577 /// <param name="seriesName">One of the series names that belongs to the cluster.</param> 1578 /// <returns>List of all series names that belong to the same cluster as specified series.</returns> GetClusterSeriesNames(string seriesName)1579 internal List<string> GetClusterSeriesNames(string seriesName) 1580 { 1581 // Iterate through all clusters 1582 foreach(List<string> seriesNames in this.seriesClusters) 1583 { 1584 if(seriesNames.Contains(seriesName)) 1585 { 1586 return seriesNames; 1587 } 1588 } 1589 return new List<string>(); 1590 } 1591 1592 /// <summary> 1593 /// Gets the series list in drawing order. 1594 /// </summary> 1595 /// <param name="reverseSeriesOrder">Series order should be reversed because of the Y axis angle.</param> 1596 /// <returns>Gets the series list in drawing order.</returns> GetSeriesDrawingOrder(bool reverseSeriesOrder)1597 private List<Series> GetSeriesDrawingOrder(bool reverseSeriesOrder) 1598 { 1599 // Create list of series 1600 List<Series> seriesList = new List<Series>(); 1601 1602 // Iterate through all clusters 1603 foreach(List<string> seriesNames in this.seriesClusters) 1604 { 1605 // Make sure there is at least one series 1606 if(seriesNames.Count > 0) 1607 { 1608 // Get first series object in the current cluster 1609 Series series = Common.DataManager.Series[seriesNames[0]]; 1610 1611 // Add series into the drawing list 1612 seriesList.Add(series); 1613 } 1614 } 1615 1616 // Reversed series list 1617 if(reverseSeriesOrder) 1618 { 1619 seriesList.Reverse(); 1620 } 1621 1622 // Check if series should be drawn from sides into the center 1623 if(DrawSeriesToCenter() && 1624 this.matrix3D.IsInitialized()) 1625 { 1626 // Get Z coordinate of Center Of Projection 1627 Point3D areaProjectionCenter = new Point3D(float.NaN, float.NaN, float.NaN); 1628 areaProjectionCenter = this.GetCenterOfProjection(COPCoordinates.Z); 1629 if(!float.IsNaN(areaProjectionCenter.Z)) 1630 { 1631 // Loop through all series 1632 for(int seriesIndex = 0; seriesIndex < seriesList.Count; seriesIndex++) 1633 { 1634 // Check if series is not empty 1635 if(((Series)seriesList[seriesIndex]).Points.Count == 0) 1636 { 1637 continue; 1638 } 1639 1640 // Get series Z position 1641 float seriesDepth, seriesZPosition; 1642 this.GetSeriesZPositionAndDepth((Series)seriesList[seriesIndex], out seriesDepth, out seriesZPosition); 1643 1644 // Check if series passes the Z coordinate of Center of Projection 1645 if(seriesZPosition >= areaProjectionCenter.Z) 1646 { 1647 // Reversed all series order staring from previous series 1648 --seriesIndex; 1649 if(seriesIndex < 0) 1650 seriesIndex = 0; 1651 seriesList.Reverse(seriesIndex, seriesList.Count - seriesIndex); 1652 break; 1653 } 1654 } 1655 } 1656 } 1657 1658 return seriesList; 1659 } 1660 1661 1662 /// <summary> 1663 /// Gets number of stack groups in specified array of series names. 1664 /// </summary> 1665 /// <param name="seriesNamesList">Array of series names.</param> 1666 /// <returns>Number of stack groups. One by default.</returns> GetNumberOfStackGroups(IList<string> seriesNamesList)1667 private int GetNumberOfStackGroups(IList<string> seriesNamesList) 1668 { 1669 this._stackGroupNames = new ArrayList(); 1670 foreach( object seriesName in seriesNamesList ) 1671 { 1672 // Get series object 1673 Series ser = this.Common.DataManager.Series[(string)seriesName]; 1674 1675 // Get stack group name from the series 1676 string stackGroupName = string.Empty; 1677 if(ser.IsCustomPropertySet(CustomPropertyName.StackedGroupName)) 1678 { 1679 stackGroupName = ser[CustomPropertyName.StackedGroupName]; 1680 } 1681 1682 // Add group name if it do not already exsist 1683 if(!this._stackGroupNames.Contains(stackGroupName)) 1684 { 1685 this._stackGroupNames.Add(stackGroupName); 1686 } 1687 } 1688 1689 return this._stackGroupNames.Count; 1690 } 1691 1692 /// <summary> 1693 /// Gets index of the series stack group. 1694 /// </summary> 1695 /// <param name="series">Series to get the index for.</param> 1696 /// <param name="stackGroupName">Group name this series belongs to.</param> 1697 /// <returns>Index of series stack group.</returns> GetSeriesStackGroupIndex(Series series, ref string stackGroupName)1698 internal int GetSeriesStackGroupIndex(Series series, ref string stackGroupName) 1699 { 1700 stackGroupName = string.Empty; 1701 if(this._stackGroupNames != null) 1702 { 1703 // Get stack group name from the series 1704 if(series.IsCustomPropertySet(CustomPropertyName.StackedGroupName)) 1705 { 1706 stackGroupName = series[CustomPropertyName.StackedGroupName]; 1707 } 1708 return this._stackGroupNames.IndexOf(stackGroupName); 1709 } 1710 return 0; 1711 } 1712 1713 1714 1715 /// <summary> 1716 /// Determine the order of points drawing from one or several series of the same type. 1717 /// </summary> 1718 /// <param name="seriesNamesList">List of series names.</param> 1719 /// <param name="chartType">Chart type.</param> 1720 /// <param name="selection">If True selection mode is active (points order should be reversed).</param> 1721 /// <param name="coord">Which coordinate of COP (X, Y or Z) to test for surface overlapping</param> 1722 /// <param name="comparer">Points comparer class. Can be Null.</param> 1723 /// <param name="mainYValueIndex">Index of the main Y value.</param> 1724 /// <param name="sideBySide">Series should be drawn side by side.</param> 1725 /// <returns>Array list of points in drawing order.</returns> GetDataPointDrawingOrder( List<string> seriesNamesList, IChartType chartType, bool selection, COPCoordinates coord, IComparer comparer, int mainYValueIndex, bool sideBySide)1726 internal ArrayList GetDataPointDrawingOrder( 1727 List<string> seriesNamesList, 1728 IChartType chartType, 1729 bool selection, 1730 COPCoordinates coord, 1731 IComparer comparer, 1732 int mainYValueIndex, 1733 bool sideBySide) 1734 { 1735 ChartArea area = (ChartArea)this; 1736 1737 // Array of points in all series 1738 ArrayList pointsList = new ArrayList(); 1739 1740 //************************************************************ 1741 //** Analyse input series 1742 //************************************************************ 1743 1744 // Find the number of data series for side-by-side drawing 1745 double numOfSeries = 1; 1746 if(area.Area3DStyle.IsClustered && !chartType.Stacked && sideBySide) 1747 { 1748 numOfSeries = seriesNamesList.Count; 1749 } 1750 1751 1752 // Check stacked series group names 1753 if(chartType.SupportStackedGroups) 1754 { 1755 // Fill the list of group names and get the number of unique groups 1756 int numberOfGroups = this.GetNumberOfStackGroups(seriesNamesList); 1757 1758 // If series are not isClustered set series number to the stacked group number 1759 if(this.Area3DStyle.IsClustered && 1760 seriesNamesList.Count > 0) 1761 { 1762 numOfSeries = numberOfGroups; 1763 } 1764 } 1765 1766 1767 // Check if chart series are indexed 1768 bool indexedSeries = ChartHelper.IndexedSeries(this.Common, seriesNamesList.ToArray()); 1769 1770 //************************************************************ 1771 //** Loop through all series and fill array of points 1772 //************************************************************ 1773 int seriesIndx = 0; 1774 foreach( object seriesName in seriesNamesList ) 1775 { 1776 // Get series object 1777 Series ser = this.Common.DataManager.Series[(string)seriesName]; 1778 1779 1780 // Check if stacked groups present 1781 if(chartType.SupportStackedGroups && 1782 this._stackGroupNames != null) 1783 { 1784 // Get index of the series using stacked group 1785 string groupName = string.Empty; 1786 seriesIndx = this.GetSeriesStackGroupIndex(ser, ref groupName); 1787 1788 // Set current group name 1789 StackedColumnChart stackedColumnChart = chartType as StackedColumnChart; 1790 if (stackedColumnChart != null) 1791 { 1792 stackedColumnChart.currentStackGroup = groupName; 1793 } 1794 else 1795 { 1796 StackedBarChart stackedBarChart = chartType as StackedBarChart; 1797 if (stackedBarChart != null) 1798 { 1799 stackedBarChart.currentStackGroup = groupName; 1800 } 1801 } 1802 } 1803 1804 1805 // Set active vertical/horizontal axis and their max/min values 1806 Axis vAxis = (ser.YAxisType == AxisType.Primary) ? area.AxisY : area.AxisY2; 1807 Axis hAxis = (ser.XAxisType == AxisType.Primary) ? area.AxisX : area.AxisX2; 1808 1809 // Get points interval: 1810 // - set interval to 1 for indexed series 1811 // - if points are not equaly spaced, the minimum interval between points is selected. 1812 // - if points have same interval bars do not overlap each other. 1813 bool sameInterval = true; 1814 double interval = 1; 1815 if(!indexedSeries) 1816 { 1817 interval = area.GetPointsInterval( seriesNamesList, hAxis.IsLogarithmic, hAxis.logarithmBase, true, out sameInterval ); 1818 } 1819 1820 // Get column width 1821 double width = ser.GetPointWidth(area.Common.graph, hAxis, interval, 0.8) / numOfSeries; 1822 1823 // Get series depth and Z position 1824 float seriesDepth, seriesZPosition; 1825 this.GetSeriesZPositionAndDepth(ser, out seriesDepth, out seriesZPosition); 1826 1827 //************************************************************ 1828 //** Loop through all points in series 1829 //************************************************************ 1830 int index = 0; 1831 foreach( DataPoint point in ser.Points ) 1832 { 1833 // Increase point index 1834 index++; 1835 1836 // Set x position 1837 double xCenterVal; 1838 double xPosition; 1839 if( indexedSeries ) 1840 { 1841 // The formula for position is based on a distance 1842 //from the grid line or nPoints position. 1843 xPosition = hAxis.GetPosition( (double)index ) - width * ((double) numOfSeries) / 2.0 + width/2 + seriesIndx * width; 1844 xCenterVal = hAxis.GetPosition( (double)index ); 1845 1846 } 1847 else if( sameInterval ) 1848 { 1849 xPosition = hAxis.GetPosition( point.XValue ) - width * ((double) numOfSeries) / 2.0 + width/2 + seriesIndx * width; 1850 xCenterVal = hAxis.GetPosition( point.XValue ); 1851 } 1852 else 1853 { 1854 xPosition = hAxis.GetPosition( point.XValue ); 1855 xCenterVal = hAxis.GetPosition( point.XValue ); 1856 } 1857 1858 1859 //************************************************************ 1860 //** Create and add new DataPoint3D object 1861 //************************************************************ 1862 DataPoint3D pointEx = new DataPoint3D(); 1863 pointEx.indexedSeries = indexedSeries; 1864 pointEx.dataPoint = point; 1865 pointEx.index = index; 1866 pointEx.xPosition = xPosition; 1867 pointEx.xCenterVal = xCenterVal; 1868 pointEx.width = ser.GetPointWidth(area.Common.graph, hAxis, interval, 0.8) / numOfSeries; 1869 pointEx.depth = seriesDepth; 1870 pointEx.zPosition = seriesZPosition; 1871 1872 // Set Y value and height 1873 double yValue = chartType.GetYValue(Common, area, ser, point, index - 1, mainYValueIndex); 1874 if (point.IsEmpty && Double.IsNaN(yValue)) 1875 { 1876 yValue = 0.0; 1877 } 1878 pointEx.yPosition = vAxis.GetPosition(yValue); 1879 pointEx.height = vAxis.GetPosition(yValue - chartType.GetYValue(Common, area, ser, point, index - 1, -1)); 1880 1881 1882 pointsList.Add(pointEx); 1883 } 1884 1885 // Data series index 1886 if(numOfSeries > 1 && sideBySide) 1887 { 1888 seriesIndx++; 1889 } 1890 } 1891 1892 //************************************************************ 1893 //** Sort points in drawing order 1894 //************************************************************ 1895 if(comparer == null) 1896 { 1897 comparer = new PointsDrawingOrderComparer((ChartArea)this, selection, coord); 1898 } 1899 pointsList.Sort(comparer); 1900 1901 return pointsList; 1902 } 1903 1904 #endregion 1905 1906 #region Points drawing order comparer class 1907 1908 /// <summary> 1909 /// Used to compare points in array and sort them by drawing order. 1910 /// </summary> 1911 internal class PointsDrawingOrderComparer : IComparer 1912 { 1913 /// <summary> 1914 /// Chart area object reference. 1915 /// </summary> 1916 private ChartArea _area = null; 1917 1918 /// <summary> 1919 /// Area X position where visible sides are switched. 1920 /// </summary> 1921 private Point3D _areaProjectionCenter = new Point3D(float.NaN, float.NaN, float.NaN); 1922 1923 /// <summary> 1924 /// Selection mode. Points order should be reversed. 1925 /// </summary> 1926 private bool _selection = false; 1927 1928 /// <summary> 1929 /// Public constructor. 1930 /// </summary> 1931 /// <param name="area">Chart area.</param> 1932 /// <param name="selection">Selection indicator.</param> 1933 /// <param name="coord">Which coordinate of COP (X, Y or Z) to test for surface overlapping</param> PointsDrawingOrderComparer(ChartArea area, bool selection, COPCoordinates coord)1934 public PointsDrawingOrderComparer(ChartArea area, bool selection, COPCoordinates coord) 1935 { 1936 this._area = area; 1937 this._selection = selection; 1938 1939 // Get center of projection 1940 if(area.DrawPointsToCenter(ref coord)) 1941 { 1942 _areaProjectionCenter = area.GetCenterOfProjection(coord); 1943 } 1944 } 1945 1946 /// <summary> 1947 /// Comparer method. 1948 /// </summary> 1949 /// <param name="o1">First object.</param> 1950 /// <param name="o2">Second object.</param> 1951 /// <returns>Comparison result.</returns> Compare(object o1, object o2)1952 public int Compare(object o1, object o2) 1953 { 1954 DataPoint3D point1 = (DataPoint3D) o1; 1955 DataPoint3D point2 = (DataPoint3D) o2; 1956 1957 int result = 0; 1958 if(point1.xPosition < point2.xPosition) 1959 { 1960 result = -1; 1961 } 1962 else if(point1.xPosition > point2.xPosition) 1963 { 1964 result = 1; 1965 } 1966 else 1967 { 1968 1969 // If X coordinate is the same - filter by Y coordinate 1970 if(point1.yPosition < point2.yPosition) 1971 { 1972 result = 1; 1973 } 1974 else if(point1.yPosition > point2.yPosition) 1975 { 1976 result = -1; 1977 } 1978 1979 // Order points from sides to center 1980 if(!float.IsNaN(_areaProjectionCenter.Y)) 1981 { 1982 double yMin1 = Math.Min(point1.yPosition, point1.height); 1983 double yMax1 = Math.Max(point1.yPosition, point1.height); 1984 double yMin2 = Math.Min(point2.yPosition, point2.height); 1985 double yMax2 = Math.Max(point2.yPosition, point2.height); 1986 1987 if(_area.IsBottomSceneWallVisible()) 1988 { 1989 if( yMin1 <= _areaProjectionCenter.Y && yMin2 <= _areaProjectionCenter.Y ) 1990 { 1991 result *= -1; 1992 } 1993 else if( yMin1 <= _areaProjectionCenter.Y) 1994 { 1995 result = 1; 1996 } 1997 1998 } 1999 else 2000 { 2001 2002 if( yMax1 >= _areaProjectionCenter.Y && yMax2 >= _areaProjectionCenter.Y ) 2003 { 2004 result *= 1; 2005 } 2006 else if( yMax1 >= _areaProjectionCenter.Y) 2007 { 2008 result = 1; 2009 } 2010 else 2011 { 2012 result *= -1; 2013 } 2014 } 2015 } 2016 2017 // Reversed order if looking from the bottom 2018 else if(!_area.IsBottomSceneWallVisible()) 2019 { 2020 result *= -1; 2021 } 2022 2023 } 2024 2025 if(point1.xPosition != point2.xPosition) 2026 { 2027 // Order points from sides to center 2028 if (!float.IsNaN(_areaProjectionCenter.X)) 2029 { 2030 if ((point1.xPosition + point1.width / 2f) >= _areaProjectionCenter.X && 2031 (point2.xPosition + point2.width / 2f) >= _areaProjectionCenter.X) 2032 { 2033 result *= -1; 2034 } 2035 } 2036 2037 // Reversed order of points by X value 2038 else if (_area.DrawPointsInReverseOrder()) 2039 { 2040 result *= -1; 2041 } 2042 } 2043 2044 return (_selection) ? -result : result; 2045 } 2046 } 2047 2048 #endregion 2049 2050 #region Center of Projection calculation methods 2051 2052 /// <summary> 2053 /// Returns one or many (X, Y, Z) coordinates of the center of projection. 2054 /// </summary> 2055 /// <param name="coord">Defines coordinates to return.</param> 2056 /// <returns>Center of projection. Value can be set to float.NaN if not defined or outside plotting area.</returns> GetCenterOfProjection(COPCoordinates coord)2057 internal Point3D GetCenterOfProjection(COPCoordinates coord) 2058 { 2059 // Define 2 points in the opposite corners of the plotting area 2060 Point3D[] points = new Point3D[2]; 2061 points[0] = new Point3D(this.PlotAreaPosition.X, this.PlotAreaPosition.Bottom, 0f); 2062 points[1] = new Point3D(this.PlotAreaPosition.Right, this.PlotAreaPosition.Y, this.areaSceneDepth); 2063 2064 // Check if surfaces (points 1 & 2) has same orientation 2065 bool xSameOrientation, ySameOrientation, zSameOrientation; 2066 CheckSurfaceOrientation( 2067 coord, 2068 points[0], 2069 points[1], 2070 out xSameOrientation, 2071 out ySameOrientation, 2072 out zSameOrientation); 2073 2074 // If orientation of all surfaces is the same - no futher processing is required (COP is outside of plotting area) 2075 Point3D resultPoint = new Point3D( 2076 (xSameOrientation) ? float.NaN : 0f, 2077 (ySameOrientation) ? float.NaN : 0f, 2078 (zSameOrientation) ? float.NaN : 0f); 2079 if( ( ((coord & COPCoordinates.X) != COPCoordinates.X) || xSameOrientation ) && 2080 ( ((coord & COPCoordinates.Y) != COPCoordinates.Y) || ySameOrientation ) && 2081 ( ((coord & COPCoordinates.Z) != COPCoordinates.Z) || zSameOrientation ) ) 2082 { 2083 return resultPoint; 2084 } 2085 2086 // Calculate the smallest interval (0.5 pixels) in relative coordinates 2087 SizeF interval = new SizeF(0.5f, 0.5f); 2088 #if WINFORMS_CONTROL 2089 interval.Width = interval.Width * 100F / ((float)(this.Common.Chart.Width - 1)); 2090 interval.Height = interval.Height * 100F / ((float)(this.Common.Chart.Height - 1)); 2091 #else 2092 interval.Width = interval.Width * 100F / ((float)(this.Common.Chart.Width.Value - 1)); 2093 interval.Height = interval.Height * 100F / ((float)(this.Common.Chart.Height.Value - 1)); 2094 #endif //#if WINFORMS_CONTROL 2095 2096 // Find middle point and check it's surface orientation 2097 bool doneFlag = false; 2098 while(!doneFlag) 2099 { 2100 // Find middle point 2101 Point3D middlePoint = new Point3D( 2102 (points[0].X + points[1].X) / 2f, 2103 (points[0].Y + points[1].Y) / 2f, 2104 (points[0].Z + points[1].Z) / 2f); 2105 2106 // Check if surfaces (points 1 & middle) has same orientation 2107 CheckSurfaceOrientation( 2108 coord, 2109 points[0], 2110 middlePoint, 2111 out xSameOrientation, 2112 out ySameOrientation, 2113 out zSameOrientation); 2114 2115 // Calculate points 1 & 2 depending on surface orientation of the middle point 2116 points[(xSameOrientation) ? 0 : 1].X = middlePoint.X; 2117 points[(ySameOrientation) ? 0 : 1].Y = middlePoint.Y; 2118 points[(zSameOrientation) ? 0 : 1].Z = middlePoint.Z; 2119 2120 // Check if no more calculations required 2121 doneFlag = true; 2122 if( (coord & COPCoordinates.X) == COPCoordinates.X && 2123 Math.Abs(points[1].X - points[0].X) >= interval.Width) 2124 { 2125 doneFlag = false; 2126 } 2127 if( (coord & COPCoordinates.Y) == COPCoordinates.Y && 2128 Math.Abs(points[1].Y - points[0].Y) >= interval.Height) 2129 { 2130 doneFlag = false; 2131 } 2132 if( (coord & COPCoordinates.Z) == COPCoordinates.Z && 2133 Math.Abs(points[1].Z - points[0].Z) >= interval.Width) 2134 { 2135 doneFlag = false; 2136 } 2137 } 2138 2139 // Calculate result point 2140 if(!float.IsNaN(resultPoint.X)) 2141 resultPoint.X = (points[0].X + points[1].X) / 2f; 2142 if(!float.IsNaN(resultPoint.Y)) 2143 resultPoint.Y = (points[0].Y + points[1].Y) / 2f; 2144 if(!float.IsNaN(resultPoint.Z)) 2145 resultPoint.Z = (points[0].Z + points[1].Z) / 2f; 2146 return resultPoint; 2147 } 2148 2149 /// <summary> 2150 /// Checks orientations of two normal surfaces for each coordinate X, Y and Z. 2151 /// </summary> 2152 /// <param name="coord">Defines coordinates to return.</param> 2153 /// <param name="point1">First point.</param> 2154 /// <param name="point2">Second point.</param> 2155 /// <param name="xSameOrientation">X surfaces orientation is the same.</param> 2156 /// <param name="ySameOrientation">Y surfaces orientation is the same.</param> 2157 /// <param name="zSameOrientation">Z surfaces orientation is the same.</param> CheckSurfaceOrientation( COPCoordinates coord, Point3D point1, Point3D point2, out bool xSameOrientation, out bool ySameOrientation, out bool zSameOrientation)2158 private void CheckSurfaceOrientation( 2159 COPCoordinates coord, 2160 Point3D point1, 2161 Point3D point2, 2162 out bool xSameOrientation, 2163 out bool ySameOrientation, 2164 out bool zSameOrientation) 2165 { 2166 Point3D[] pointsSurface = new Point3D[3]; 2167 bool surf1, surf2; 2168 2169 // Initialize returned values 2170 xSameOrientation = true; 2171 ySameOrientation = true; 2172 zSameOrientation = true; 2173 2174 // Check X axis coordinates (ledt & right surfaces) 2175 if( (coord & COPCoordinates.X) == COPCoordinates.X ) 2176 { 2177 // Define Left surface coordinates, transform them and check visibility 2178 pointsSurface[0] = new Point3D(point1.X, this.PlotAreaPosition.Y, 0f); 2179 pointsSurface[1] = new Point3D(point1.X, this.PlotAreaPosition.Bottom, 0f); 2180 pointsSurface[2] = new Point3D(point1.X, this.PlotAreaPosition.Bottom, this.areaSceneDepth); 2181 this.matrix3D.TransformPoints( pointsSurface ); 2182 surf1 = ChartGraphics.IsSurfaceVisible(pointsSurface[0], pointsSurface[1], pointsSurface[2]); 2183 2184 // Define Right surface coordinates, transform them and check visibility 2185 pointsSurface[0] = new Point3D(point2.X, this.PlotAreaPosition.Y, 0f); 2186 pointsSurface[1] = new Point3D(point2.X, this.PlotAreaPosition.Bottom, 0f); 2187 pointsSurface[2] = new Point3D(point2.X, this.PlotAreaPosition.Bottom, this.areaSceneDepth); 2188 this.matrix3D.TransformPoints( pointsSurface ); 2189 surf2 = ChartGraphics.IsSurfaceVisible(pointsSurface[0], pointsSurface[1], pointsSurface[2]); 2190 2191 // Check if surfaces have same visibility 2192 xSameOrientation = (surf1 == surf2); 2193 } 2194 2195 // Check Y axis coordinates (top & bottom surfaces) 2196 if( (coord & COPCoordinates.Y) == COPCoordinates.Y ) 2197 { 2198 // Define Bottom surface coordinates, transform them and check visibility 2199 pointsSurface[0] = new Point3D(this.PlotAreaPosition.X, point1.Y, this.areaSceneDepth); 2200 pointsSurface[1] = new Point3D(this.PlotAreaPosition.X, point1.Y, 0f); 2201 pointsSurface[2] = new Point3D(this.PlotAreaPosition.Right, point1.Y, 0f); 2202 this.matrix3D.TransformPoints( pointsSurface ); 2203 surf1 = ChartGraphics.IsSurfaceVisible(pointsSurface[0], pointsSurface[1], pointsSurface[2]); 2204 2205 // Define Top surface coordinates, transform them and check visibility 2206 pointsSurface[0] = new Point3D(this.PlotAreaPosition.X, point2.Y, this.areaSceneDepth); 2207 pointsSurface[1] = new Point3D(this.PlotAreaPosition.X, point2.Y, 0f); 2208 pointsSurface[2] = new Point3D(this.PlotAreaPosition.Right, point2.Y, 0f); 2209 this.matrix3D.TransformPoints( pointsSurface ); 2210 surf2 = ChartGraphics.IsSurfaceVisible(pointsSurface[0], pointsSurface[1], pointsSurface[2]); 2211 2212 // Check if surfaces have same visibility 2213 ySameOrientation = (surf1 == surf2); 2214 } 2215 2216 // Check Y axis coordinates (front & back surfaces) 2217 if( (coord & COPCoordinates.Z) == COPCoordinates.Z ) 2218 { 2219 // Define Front surface coordinates, transform them and check visibility 2220 pointsSurface[0] = new Point3D(this.PlotAreaPosition.X, this.PlotAreaPosition.Y, point1.Z); 2221 pointsSurface[1] = new Point3D(this.PlotAreaPosition.X, this.PlotAreaPosition.Bottom, point1.Z); 2222 pointsSurface[2] = new Point3D(this.PlotAreaPosition.Right, this.PlotAreaPosition.Bottom, point1.Z); 2223 this.matrix3D.TransformPoints( pointsSurface ); 2224 surf1 = ChartGraphics.IsSurfaceVisible(pointsSurface[0], pointsSurface[1], pointsSurface[2]); 2225 2226 // Define Back surface coordinates, transform them and check visibility 2227 pointsSurface[0] = new Point3D(this.PlotAreaPosition.X, this.PlotAreaPosition.Y, point2.Z); 2228 pointsSurface[1] = new Point3D(this.PlotAreaPosition.X, this.PlotAreaPosition.Bottom, point2.Z); 2229 pointsSurface[2] = new Point3D(this.PlotAreaPosition.Right, this.PlotAreaPosition.Bottom, point2.Z); 2230 this.matrix3D.TransformPoints( pointsSurface ); 2231 surf2 = ChartGraphics.IsSurfaceVisible(pointsSurface[0], pointsSurface[1], pointsSurface[2]); 2232 2233 // Check if surfaces have same visibility 2234 zSameOrientation = (surf1 == surf2); 2235 } 2236 } 2237 #endregion 2238 } 2239 } 2240