//------------------------------------------------------------- // // Copyright � Microsoft Corporation. All Rights Reserved. // //------------------------------------------------------------- // @owner=alexgor, deliant //================================================================= // File: ChartArea3D.cs // // Namespace: System.Web.UI.WebControls[Windows.Forms].Charting // // Classes: ChartArea3DStyle, ChartArea3D // // Purpose: ChartArea3D class represents 3D chart area. It contains // methods for coordinates transformation, drawing the 3D // scene and many 3D related helper methods. // // Reviewed: AG - Microsoft 16, 2007 // //=================================================================== #region Used namespaces using System; using System.Drawing; using System.Drawing.Drawing2D; using System.ComponentModel; using System.ComponentModel.Design; using System.Collections; using System.Globalization; using System.Collections.Generic; #if WINFORMS_CONTROL using System.Windows.Forms.DataVisualization.Charting; using System.Windows.Forms.DataVisualization.Charting.Data; using System.Windows.Forms.DataVisualization.Charting.ChartTypes; using System.Windows.Forms.DataVisualization.Charting.Utilities; using System.Windows.Forms.DataVisualization.Charting.Borders3D; #else using System.Web.UI.DataVisualization.Charting; using System.Web.UI.DataVisualization.Charting.ChartTypes; using System.Web.UI.DataVisualization.Charting.Utilities; using System.Web.UI; #endif #endregion #if WINFORMS_CONTROL namespace System.Windows.Forms.DataVisualization.Charting #else namespace System.Web.UI.DataVisualization.Charting #endif { #region 3D lightStyle style enumerations /// /// A lighting style for a 3D chart area. /// public enum LightStyle { /// /// No lighting. /// None, /// /// Simplistic lighting. /// Simplistic, /// /// Realistic lighting. /// Realistic } #endregion #region 3D Center of Projetion coordinates enumeration /// /// Coordinates of the Center Of Projection /// [Flags] internal enum COPCoordinates { /// /// Check X coordinate. /// X = 1, /// /// Check Y coordinate. /// Y = 2, /// /// Check Z coordinate. /// Z = 4 } #endregion /// /// The ChartArea3DStyleClass class provides the functionality for 3D attributes of chart areas, /// such as rotation angles and perspective. /// #if ASPPERM_35 [AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)] [AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)] #endif public class ChartArea3DStyle { #region Constructor and Initialization /// /// ChartArea3DStyle constructor. /// public ChartArea3DStyle() { } /// /// ChartArea3DStyle constructor. /// public ChartArea3DStyle(ChartArea chartArea) { this._chartArea = chartArea; } /// /// Initialize Chart area and axes /// /// Chart area object. internal void Initialize(ChartArea chartArea) { this._chartArea = chartArea; } #endregion #region Fields // Reference to the chart area object private ChartArea _chartArea = null; // Enables/disables 3D chart types in the area. private bool _enable3D = false; // Indicates that axes are set at the right angle independent of the rotation. private bool _isRightAngleAxes = true; // Indicates that series should be drawn as isClustered. private bool _isClustered = false; // 3D area lightStyle style. private LightStyle _lightStyle = LightStyle.Simplistic; // 3D area perspective which controls the scaleView of the chart depth. private int _perspective = 0; // Chart area rotation angle around the X axis. private int _inclination = 30; // Chart area rotation angle around the Y axis. private int _rotation = 30; // Chart area walls width. private int _wallWidth = 7; // Series points depth in percentages private int _pointDepth = 100; // Series points gap depth in percentages private int _pointGapDepth = 100; #endregion #region Properties /// /// Gets or sets a Boolean value that toggles 3D for a chart area on and off. /// [ SRCategory("CategoryAttribute3D"), Bindable(true), DefaultValue(false), SRDescription("DescriptionAttributeChartArea3DStyle_Enable3D"), ParenthesizePropertyNameAttribute(true) ] public bool Enable3D { get { return this._enable3D; } set { if (this._enable3D != value) { this._enable3D = value; if (this._chartArea != null) { #if SUBAXES // If one of the axes has sub axis the scales has to be recalculated foreach(Axis axis in this._chartArea.Axes) { if(axis.SubAxes.Count > 0) { this._chartArea.ResetAutoValues(); break; } } #endif // SUBAXES this._chartArea.Invalidate(); } } } } /// /// Gets or sets a Boolean that determines if a chart area is displayed using an isometric projection. /// [ SRCategory("CategoryAttribute3D"), Bindable(true), DefaultValue(true), SRDescription("DescriptionAttributeChartArea3DStyle_RightAngleAxes"), RefreshPropertiesAttribute(RefreshProperties.All) ] public bool IsRightAngleAxes { get { return _isRightAngleAxes; } set { _isRightAngleAxes = value; // Adjust 3D properties values if (_isRightAngleAxes) { // Disable perspective if right angle axis are used this._perspective = 0; } if (this._chartArea != null) { this._chartArea.Invalidate(); } } } /// /// Gets or sets a Boolean value that determines if bar chart or column /// chart data series are clustered (displayed along distinct rows). /// [ SRCategory("CategoryAttribute3D"), Bindable(true), DefaultValue(false), SRDescription("DescriptionAttributeChartArea3DStyle_Clustered"), ] public bool IsClustered { get { return _isClustered; } set { _isClustered = value; if (this._chartArea != null) { this._chartArea.Invalidate(); } } } /// /// Gets or sets the style of lighting for a 3D chart area. /// [ SRCategory("CategoryAttribute3D"), Bindable(true), DefaultValue(typeof(LightStyle), "Simplistic"), SRDescription("DescriptionAttributeChartArea3DStyle_Light"), ] public LightStyle LightStyle { get { return _lightStyle; } set { _lightStyle = value; if (this._chartArea != null) { this._chartArea.Invalidate(); } } } /// /// Gets or sets the percent of perspective for a 3D chart area. /// [ SRCategory("CategoryAttribute3D"), Bindable(true), DefaultValue(0), SRDescription("DescriptionAttributeChartArea3DStyle_Perspective"), RefreshPropertiesAttribute(RefreshProperties.All) ] public int Perspective { get { return _perspective; } set { if(value < 0 || value > 100) { throw (new ArgumentOutOfRangeException("value", SR.ExceptionChartArea3DPerspectiveInvalid)); } _perspective = value; // Adjust 3D properties values if (_perspective != 0) { // Disable right angle axes this._isRightAngleAxes = false; } if (this._chartArea != null) { this._chartArea.Invalidate(); } } } /// /// Gets or sets the inclination for a 3D chart area. /// [ SRCategory("CategoryAttribute3D"), Bindable(true), DefaultValue(30), SRDescription("DescriptionAttributeChartArea3DStyle_Inclination"), RefreshPropertiesAttribute(RefreshProperties.All) ] public int Inclination { get { return _inclination; } set { if(value < -90 || value > 90) { throw (new ArgumentOutOfRangeException("value", SR.ExceptionChartArea3DInclinationInvalid)); } _inclination = value; if (this._chartArea != null) { this._chartArea.Invalidate(); } } } /// /// Gets or sets the rotation angle for a 3D chart area. /// [ SRCategory("CategoryAttribute3D"), Bindable(true), DefaultValue(30), SRDescription("DescriptionAttributeChartArea3DStyle_Rotation"), RefreshPropertiesAttribute(RefreshProperties.All) ] public int Rotation { get { return _rotation; } set { if(value < -180 || value > 180) { throw (new ArgumentOutOfRangeException("value", SR.ExceptionChartArea3DRotationInvalid)); } _rotation = value; if (this._chartArea != null) { this._chartArea.Invalidate(); } } } /// /// Gets or sets the width of the walls displayed in 3D chart areas. /// [ SRCategory("CategoryAttribute3D"), Bindable(true), DefaultValue(7), SRDescription("DescriptionAttributeChartArea3DStyle_WallWidth"), RefreshPropertiesAttribute(RefreshProperties.All) ] public int WallWidth { get { return _wallWidth; } set { if(value < 0 || value > 30) { throw (new ArgumentOutOfRangeException("value", SR.ExceptionChartArea3DWallWidthInvalid)); } _wallWidth = value; if (this._chartArea != null) { this._chartArea.Invalidate(); } } } /// /// Gets or sets the depth of data points displayed in 3D chart areas (0-1000%). /// [ SRCategory("CategoryAttribute3D"), Bindable(true), DefaultValue(100), SRDescription("DescriptionAttributeChartArea3DStyle_PointDepth"), RefreshPropertiesAttribute(RefreshProperties.All) ] public int PointDepth { get { return _pointDepth; } set { if(value < 0 || value > 1000) { throw (new ArgumentOutOfRangeException("value", SR.ExceptionChartArea3DPointsDepthInvalid)); } _pointDepth = value; if (this._chartArea != null) { this._chartArea.Invalidate(); } } } /// /// Gets or sets the distance between series rows in 3D chart areas (0-1000%). /// [ SRCategory("CategoryAttribute3D"), Bindable(true), DefaultValue(100), SRDescription("DescriptionAttributeChartArea3DStyle_PointGapDepth"), RefreshPropertiesAttribute(RefreshProperties.All) ] public int PointGapDepth { get { return _pointGapDepth; } set { if(value < 0 || value > 1000) { throw (new ArgumentOutOfRangeException("value", SR.ExceptionChartArea3DPointsGapInvalid)); } _pointGapDepth = value; if (this._chartArea != null) { this._chartArea.Invalidate(); } } } #endregion } /// /// ChartArea3D class represents 3D chart area. It contains all the 3D /// scene settings and methods for drawing the 3D plotting area, and calculating /// the depth of chart elements. /// public partial class ChartArea { #region Fields // Chart area 3D style attribuytes private ChartArea3DStyle _area3DStyle = new ChartArea3DStyle(); // Coordinate convertion matrix internal Matrix3D matrix3D = new Matrix3D(); // Chart area scene wall width in relative coordinates internal SizeF areaSceneWallWidth = SizeF.Empty; // Chart area scene depth internal float areaSceneDepth = 0; // Visible surfaces in plotting area private SurfaceNames _visibleSurfaces; // Z axis depth of series points private double _pointsDepth = 0; // Z axis depth of the gap between isClustered series private double _pointsGapDepth = 0; /// /// Indicates that series order should be reversed to simulate Y axis rotation. /// private bool _reverseSeriesOrder = false; /// /// Old X axis reversed flag /// internal bool oldReverseX = false; /// /// Old Y axis reversed flag /// internal bool oldReverseY = false; /// /// Old Y axis rotation angle /// internal int oldYAngle = 30; /// /// List of all stack group names /// private ArrayList _stackGroupNames = null; /// /// This list contains an array of series names for each 3D cluster /// internal List> seriesClusters = null; #endregion #region 3D Style properties /// /// Gets or sets a ChartArea3DStyle object, used to draw all series in a chart area in 3D. /// [ SRCategory("CategoryAttribute3D"), Bindable(true), DefaultValue(null), SRDescription("DescriptionAttributeArea3DStyle"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), TypeConverter(typeof(NoNameExpandableObjectConverter)), #if !WINFORMS_CONTROL PersistenceMode(PersistenceMode.InnerProperty), #endif ] public ChartArea3DStyle Area3DStyle { get { return _area3DStyle; } set { _area3DStyle = value; // Initialize style object _area3DStyle.Initialize((ChartArea)this); } } /// /// Indicates that series order should be reversed to simulate Y axis rotation. /// internal bool ReverseSeriesOrder { get { return _reverseSeriesOrder; } } /// /// Gets the list of all stack group names /// internal ArrayList StackGroupNames { get { return _stackGroupNames; } } #endregion #region 3D Coordinates transfotmation methods /// /// Call this method to apply 3D transformations on an array of 3D points (must be done before calling GDI+ drawing methods). /// /// 3D Points array. public void TransformPoints( Point3D[] points ) { // Convert Z coordinates from 0-100% to axis values foreach(Point3D pt in points) { pt.Z = (pt.Z / 100f) * this.areaSceneDepth; } // Transform points this.matrix3D.TransformPoints( points ); } #endregion #region 3D Scene drawing methods /// /// Draws chart area 3D scene, which consists of 3 or 2 walls. /// /// Chart graphics object. /// Chart area 2D position. internal void DrawArea3DScene(ChartGraphics graph, RectangleF position) { // Reference to the chart area class ChartArea chartArea = (ChartArea)this; // Calculate relative size of the wall areaSceneWallWidth = graph.GetRelativeSize( new SizeF(this.Area3DStyle.WallWidth, this.Area3DStyle.WallWidth)); //*********************************************************** //** Calculate the depth of the chart area scene //*********************************************************** areaSceneDepth = GetArea3DSceneDepth(); //*********************************************************** //** Initialize coordinate transformation matrix //*********************************************************** this.matrix3D.Initialize( position, areaSceneDepth, this.Area3DStyle.Inclination, this.Area3DStyle.Rotation, this.Area3DStyle.Perspective, this.Area3DStyle.IsRightAngleAxes); //*********************************************************** //** Initialize Lighting //*********************************************************** this.matrix3D.InitLight( this.Area3DStyle.LightStyle ); //*********************************************************** //** Find chart area visible surfaces //*********************************************************** _visibleSurfaces = graph.GetVisibleSurfaces( position, 0, areaSceneDepth, this.matrix3D); //*********************************************************** //** Chech if area scene should be drawn //*********************************************************** Color sceneBackColor = chartArea.BackColor; // Do not draw the transparent walls if(sceneBackColor == Color.Transparent) { // Area wall is not visible areaSceneWallWidth = SizeF.Empty; return; } // If color is not set (default) - use LightGray if(sceneBackColor == Color.Empty) { sceneBackColor = Color.LightGray; } //*********************************************************** //** Adjust scene 2D rectangle so that wall are drawn //** outside plotting area. //*********************************************************** // If bottom wall is visible if(IsBottomSceneWallVisible()) { position.Height += areaSceneWallWidth.Height; } // Adjust for the left/right wall position.Width += areaSceneWallWidth.Width; if(this.Area3DStyle.Rotation > 0) { position.X -= areaSceneWallWidth.Width; } //*********************************************************** //** Draw scene walls //*********************************************************** // Draw back wall RectangleF wallRect2D = new RectangleF(position.Location, position.Size); float wallDepth = areaSceneWallWidth.Width; float wallZPosition = -wallDepth; // For isometric projection Front wall should be visible sometimes if( IsMainSceneWallOnFront()) { wallZPosition = areaSceneDepth; } graph.Fill3DRectangle( wallRect2D, wallZPosition, wallDepth, this.matrix3D, chartArea.Area3DStyle.LightStyle, sceneBackColor, chartArea.BorderColor, chartArea.BorderWidth, chartArea.BorderDashStyle, DrawingOperationTypes.DrawElement ); // Draw side wall on the left or right side wallRect2D = new RectangleF(position.Location, position.Size); wallRect2D.Width = areaSceneWallWidth.Width; if(!IsSideSceneWallOnLeft()) { // Wall is on the right side wallRect2D.X = position.Right - areaSceneWallWidth.Width; } graph.Fill3DRectangle( wallRect2D, 0f, areaSceneDepth, this.matrix3D, chartArea.Area3DStyle.LightStyle, sceneBackColor, chartArea.BorderColor, chartArea.BorderWidth, chartArea.BorderDashStyle, DrawingOperationTypes.DrawElement); // Draw bottom wall if(IsBottomSceneWallVisible()) { wallRect2D = new RectangleF(position.Location, position.Size); wallRect2D.Height = areaSceneWallWidth.Height; wallRect2D.Y = position.Bottom - areaSceneWallWidth.Height; wallRect2D.Width -= areaSceneWallWidth.Width; if(IsSideSceneWallOnLeft()) { wallRect2D.X += areaSceneWallWidth.Width; } wallZPosition = 0; graph.Fill3DRectangle( wallRect2D, 0f, areaSceneDepth, this.matrix3D, chartArea.Area3DStyle.LightStyle, sceneBackColor, chartArea.BorderColor, chartArea.BorderWidth, chartArea.BorderDashStyle, DrawingOperationTypes.DrawElement ); } } /// /// Helper method which return True if bottom wall of the /// chart area scene is visible. /// /// True if bottom wall is visible. internal bool IsBottomSceneWallVisible() { return (this.Area3DStyle.Inclination >= 0); } /// /// Helper method which return True if main wall of the /// chart area scene is displayed on the front side. /// /// True if front wall is visible. internal bool IsMainSceneWallOnFront() { // Note: Not used in this version! return false; } /// /// Helper method which return True if side wall of the /// chart area scene is displayed on the left side. /// /// True if bottom wall is visible. internal bool IsSideSceneWallOnLeft() { return (this.Area3DStyle.Rotation > 0); } #endregion #region 3D Scene depth claculation methods /// /// Call this method to get the Z position of a series (useful for custom drawing). /// /// The series to retrieve the Z position for. /// The Z position of the specified series. Measured as a percentage of the chart area's depth. public float GetSeriesZPosition(Series series) { float positionZ, depth; GetSeriesZPositionAndDepth(series, out depth, out positionZ); return ((positionZ + depth/2f) / this.areaSceneDepth) * 100f; } /// /// Call this method to get the depth of a series in a chart area. /// /// The series to retrieve the depth for. /// The depth of the specified series. Measured as a percentage of the chart area's depth. public float GetSeriesDepth(Series series) { float positionZ, depth; GetSeriesZPositionAndDepth(series, out depth, out positionZ); return (depth / this.areaSceneDepth) * 100f; } /// /// Calculates area 3D scene depth depending on the number of isClustered /// series and interval between points. /// /// Returns the depth of the chart area scene. private float GetArea3DSceneDepth() { //*********************************************************** //** Calculate the smallest interval between points //*********************************************************** // Check if any series attached to the area is indexed bool indexedSeries = ChartHelper.IndexedSeries(this.Common, this._series.ToArray()); // Smallest interval series Series smallestIntervalSeries = null; if(this._series.Count > 0) { smallestIntervalSeries = this.Common.DataManager.Series[(string)this._series[0]]; } // Get X axis Axis xAxis = ((ChartArea)this).AxisX; if(this._series.Count > 0) { Series firstSeries = this.Common.DataManager.Series[this._series[0]]; if(firstSeries != null && firstSeries.XAxisType == AxisType.Secondary) { xAxis = ((ChartArea)this).AxisX2; } } // Get smallest interval between points (use interval 1 for indexed series) double clusteredInterval = 1; if(!indexedSeries) { bool sameInterval; clusteredInterval = this.GetPointsInterval(this._series, xAxis.IsLogarithmic, xAxis.logarithmBase, false, out sameInterval, out smallestIntervalSeries); } //*********************************************************** //** Check if "DrawSideBySide" attribute is set. //*********************************************************** bool drawSideBySide = false; if(smallestIntervalSeries != null) { drawSideBySide = Common.ChartTypeRegistry.GetChartType(smallestIntervalSeries.ChartTypeName).SideBySideSeries; foreach(string seriesName in this._series) { if(this.Common.DataManager.Series[seriesName].IsCustomPropertySet(CustomPropertyName.DrawSideBySide)) { string attribValue = this.Common.DataManager.Series[seriesName][CustomPropertyName.DrawSideBySide]; if(String.Compare(attribValue, "False", StringComparison.OrdinalIgnoreCase) == 0) { drawSideBySide = false; } else if(String.Compare(attribValue, "True", StringComparison.OrdinalIgnoreCase) == 0) { drawSideBySide = true; } else if (String.Compare(attribValue, "Auto", StringComparison.OrdinalIgnoreCase) == 0) { // Do nothing } else { throw (new InvalidOperationException(SR.ExceptionAttributeDrawSideBySideInvalid)); } } } } // Get smallest interval cate----cal axis Axis categoricalAxis = ((ChartArea)this).AxisX; if(smallestIntervalSeries != null && smallestIntervalSeries.XAxisType == AxisType.Secondary) { categoricalAxis = ((ChartArea)this).AxisX2; } //*********************************************************** //** If series with the smallest interval is displayed //** side-by-side - devide the interval by number of series //** of the same chart type. //*********************************************************** double pointWidthSize = 0.8; int seriesNumber = 1; if(smallestIntervalSeries != null) { // Check if series is side-by-side if(this.Area3DStyle.IsClustered && drawSideBySide) { // Count number of side-by-side series seriesNumber = 0; foreach(string seriesName in this._series) { // Get series object from name Series curSeries = this.Common.DataManager.Series[seriesName]; if(String.Compare(curSeries.ChartTypeName, smallestIntervalSeries.ChartTypeName, StringComparison.OrdinalIgnoreCase) == 0 ) { ++seriesNumber; } } } } //*********************************************************** //** Stacked column and bar charts can be drawn side-by-side //** using the StackGroupName custom properties. The code //** checks if multiple groups are used how many of these //** groups exsist. //** //** If isClustered mode enabled each stack group is drawn //** using it's own cluster. //*********************************************************** if(smallestIntervalSeries != null && this.Area3DStyle.IsClustered) { // Check series support stack groups if(Common.ChartTypeRegistry.GetChartType(smallestIntervalSeries.ChartTypeName).SupportStackedGroups) { // Calculate how many stack groups exsist seriesNumber = 0; ArrayList stackGroupNames = new ArrayList(); foreach(string seriesName in this._series) { // Get series object from name Series curSeries = this.Common.DataManager.Series[seriesName]; if(String.Compare(curSeries.ChartTypeName, smallestIntervalSeries.ChartTypeName, StringComparison.OrdinalIgnoreCase) == 0 ) { string seriesStackGroupName = string.Empty; if(curSeries.IsCustomPropertySet(CustomPropertyName.StackedGroupName)) { seriesStackGroupName = curSeries[CustomPropertyName.StackedGroupName]; } // Add group name if it do not already exsist if(!stackGroupNames.Contains(seriesStackGroupName)) { stackGroupNames.Add(seriesStackGroupName); } } } seriesNumber = stackGroupNames.Count; } } //*********************************************************** //** Check if series provide custom value for point\gap depth //*********************************************************** _pointsDepth = clusteredInterval * pointWidthSize * this.Area3DStyle.PointDepth / 100.0; _pointsDepth = categoricalAxis.GetPixelInterval(_pointsDepth); if(smallestIntervalSeries != null) { _pointsDepth = smallestIntervalSeries.GetPointWidth( this.Common.graph, categoricalAxis, clusteredInterval, 0.8) / seriesNumber; _pointsDepth *= this.Area3DStyle.PointDepth / 100.0; } _pointsGapDepth = (_pointsDepth * 0.8) * this.Area3DStyle.PointGapDepth / 100.0; // Get point depth and gap from series if(smallestIntervalSeries != null) { smallestIntervalSeries.GetPointDepthAndGap( this.Common.graph, categoricalAxis, ref _pointsDepth, ref _pointsGapDepth); } //*********************************************************** //** Calculate scene depth //*********************************************************** return (float)((_pointsGapDepth + _pointsDepth) * GetNumberOfClusters()); } /// /// Calculates the depth and Z position for specified series. /// /// Series object. /// Returns series depth. /// Returns series Z position. internal void GetSeriesZPositionAndDepth(Series series, out float depth, out float positionZ) { // Check arguments if (series == null) throw new ArgumentNullException("series"); // Get series cluster index int seriesIndex = GetSeriesClusterIndex(series); // Initialize the output parameters depth = (float)_pointsDepth; positionZ = (float)(_pointsGapDepth / 2.0 + (_pointsDepth + _pointsGapDepth) * seriesIndex); } /// /// Returns number of clusters on the Z axis. /// /// Number of clusters on the Z axis. internal int GetNumberOfClusters() { if(this.seriesClusters == null) { // Lists that hold processed chart types and stacked groups ArrayList processedChartTypes = new ArrayList(); ArrayList processedStackedGroups = new ArrayList(); // Reset series cluster list this.seriesClusters = new List>(); // Iterate through all series that belong to this chart area int clusterIndex = -1; foreach(string seriesName in this._series) { // Get series object by name Series curSeries = this.Common.DataManager.Series[seriesName]; // Check if stacked chart type is using multiple groups that // can be displayed in individual clusters if(!this.Area3DStyle.IsClustered && Common.ChartTypeRegistry.GetChartType(curSeries.ChartTypeName).SupportStackedGroups) { // Get group name string stackGroupName = StackedColumnChart.GetSeriesStackGroupName(curSeries); // Check if group was already counted if(processedStackedGroups.Contains(stackGroupName)) { // Find in which cluster this stacked group is located bool found = false; for(int index = 0; !found && index < this.seriesClusters.Count; index++) { foreach(string name in this.seriesClusters[index]) { // Get series object by name Series ser = this.Common.DataManager.Series[name]; if(stackGroupName == StackedColumnChart.GetSeriesStackGroupName(ser)) { clusterIndex = index; found = true; } } } } else { // Increase cluster index clusterIndex = this.seriesClusters.Count; // Add processed group name processedStackedGroups.Add(stackGroupName); } } // Chech if series is displayed in the same cluster than other series else if( Common.ChartTypeRegistry.GetChartType(curSeries.ChartTypeName).Stacked || (this.Area3DStyle.IsClustered && Common.ChartTypeRegistry.GetChartType(curSeries.ChartTypeName).SideBySideSeries) ) { // Check if this chart type is already in the list if(processedChartTypes.Contains(curSeries.ChartTypeName.ToUpper(System.Globalization.CultureInfo.InvariantCulture))) { // Find in which cluster this chart type is located bool found = false; for(int index = 0; !found && index < this.seriesClusters.Count; index++) { foreach(string name in this.seriesClusters[index]) { // Get series object by name Series ser = this.Common.DataManager.Series[name]; if(ser.ChartTypeName.ToUpper(System.Globalization.CultureInfo.InvariantCulture) == curSeries.ChartTypeName.ToUpper(System.Globalization.CultureInfo.InvariantCulture)) { clusterIndex = index; found = true; } } } } else { // Increase cluster index clusterIndex = this.seriesClusters.Count; // Add new chart type into the collection processedChartTypes.Add(curSeries.ChartTypeName.ToUpper(System.Globalization.CultureInfo.InvariantCulture)); } } else { // Create New cluster clusterIndex = this.seriesClusters.Count; } // Create an item in the cluster list that will hold all series names if(this.seriesClusters.Count <= clusterIndex) { this.seriesClusters.Add(new List()); } // Add series name into the current cluster this.seriesClusters[clusterIndex].Add(seriesName); } } return this.seriesClusters.Count; } /// /// Get series cluster index. /// /// Series object. /// Series cluster index. internal int GetSeriesClusterIndex(Series series) { // Fill list of clusters if(this.seriesClusters == null) { this.GetNumberOfClusters(); } // Iterate through all clusters for(int clusterIndex = 0; clusterIndex < this.seriesClusters.Count; clusterIndex++) { List seriesNames = this.seriesClusters[clusterIndex]; // Iterate through all series names foreach(string seriesName in seriesNames) { if(seriesName == series.Name) { // Check if series are drawn in reversed order if(this._reverseSeriesOrder) { clusterIndex = (this.seriesClusters.Count - 1) - clusterIndex; } return clusterIndex; } } } return 0; } #endregion #region 3D Scene helper methods /// /// This method is used to calculate estimated scene /// depth. Regular scene depth method can not be used /// because Plot area position is zero. Instead, Chart /// area position is used to find depth of the scene. /// Algorithm which draws axis labels will decide what /// should be size and position of plotting area. /// /// Returns estimated scene depth private float GetEstimatedSceneDepth() { float sceneDepth; ChartArea area = (ChartArea) this; // Reset current list of clusters this.seriesClusters = null; ElementPosition plottingAreaRect = area.InnerPlotPosition; area.AxisX.PlotAreaPosition = area.Position; area.AxisY.PlotAreaPosition = area.Position; area.AxisX2.PlotAreaPosition = area.Position; area.AxisY2.PlotAreaPosition = area.Position; sceneDepth = GetArea3DSceneDepth(); area.AxisX.PlotAreaPosition = plottingAreaRect; area.AxisY.PlotAreaPosition = plottingAreaRect; area.AxisX2.PlotAreaPosition = plottingAreaRect; area.AxisY2.PlotAreaPosition = plottingAreaRect; return sceneDepth; } /// /// Estimate Interval for 3D Charts. When scene is rotated the /// number of labels should be changed. /// /// Chart graphics object. internal void Estimate3DInterval(ChartGraphics graph ) { // Reference to the chart area class ChartArea area = (ChartArea)this; // Calculate relative size of the wall areaSceneWallWidth = graph.GetRelativeSize( new SizeF(this.Area3DStyle.WallWidth, this.Area3DStyle.WallWidth)); //*********************************************************** //** Calculate the depth of the chart area scene //*********************************************************** areaSceneDepth = GetEstimatedSceneDepth(); RectangleF plottingRect = area.Position.ToRectangleF(); // Check if plot area position was recalculated. // If not and non-auto InnerPlotPosition & Position were // specified - do all needed calculations if(PlotAreaPosition.Width == 0 && PlotAreaPosition.Height == 0 && !area.InnerPlotPosition.Auto && !area.Position.Auto) { // Initialize plotting area position if(!area.InnerPlotPosition.Auto) { plottingRect.X += (area.Position.Width / 100F) * area.InnerPlotPosition.X; plottingRect.Y += (area.Position.Height / 100F) * area.InnerPlotPosition.Y; plottingRect.Width = (area.Position.Width / 100F) * area.InnerPlotPosition.Width; plottingRect.Height = (area.Position.Height / 100F) * area.InnerPlotPosition.Height; } } int yAngle = GetRealYAngle( ); //*********************************************************** //** Initialize coordinate transformation matrix //*********************************************************** Matrix3D intervalMatrix3D = new Matrix3D(); intervalMatrix3D.Initialize( plottingRect, areaSceneDepth, this.Area3DStyle.Inclination, yAngle, this.Area3DStyle.Perspective, this.Area3DStyle.IsRightAngleAxes); bool notUsed; float zPosition; double size; Point3D [] points = new Point3D[8]; if( area.switchValueAxes ) { // X Axis zPosition = axisX.GetMarksZPosition( out notUsed ); points[0] = new Point3D( plottingRect.X, plottingRect.Y, zPosition ); points[1] = new Point3D( plottingRect.X, plottingRect.Bottom, zPosition ); // Y Axis zPosition = axisY.GetMarksZPosition( out notUsed ); points[2] = new Point3D( plottingRect.X, plottingRect.Bottom, zPosition ); points[3] = new Point3D( plottingRect.Right, plottingRect.Bottom, zPosition ); // X2 Axis zPosition = axisX2.GetMarksZPosition( out notUsed ); points[4] = new Point3D( plottingRect.X, plottingRect.Y, zPosition ); points[5] = new Point3D( plottingRect.X, plottingRect.Bottom, zPosition ); // Y2 Axis zPosition = axisY2.GetMarksZPosition( out notUsed ); points[6] = new Point3D( plottingRect.X, plottingRect.Y, zPosition ); points[7] = new Point3D( plottingRect.Right, plottingRect.Y, zPosition ); } else { // X Axis zPosition = axisX.GetMarksZPosition( out notUsed ); points[0] = new Point3D( plottingRect.X, plottingRect.Bottom, zPosition ); points[1] = new Point3D( plottingRect.Right, plottingRect.Bottom, zPosition ); // Y Axis zPosition = axisY.GetMarksZPosition( out notUsed ); points[2] = new Point3D( plottingRect.X, plottingRect.Y, zPosition ); points[3] = new Point3D( plottingRect.X, plottingRect.Bottom, zPosition ); // X2 Axis zPosition = axisX2.GetMarksZPosition( out notUsed ); points[4] = new Point3D( plottingRect.X, plottingRect.Y, zPosition ); points[5] = new Point3D( plottingRect.Right, plottingRect.Y, zPosition ); // Y2 Axis zPosition = axisY2.GetMarksZPosition( out notUsed ); points[6] = new Point3D( plottingRect.X, plottingRect.Y, zPosition ); points[7] = new Point3D( plottingRect.X, plottingRect.Bottom, zPosition ); } // Crossing has to be reset because interval and // sometimes minimum and maximum are changed. foreach( Axis axis in area.Axes ) { axis.crossing = axis.tempCrossing; } // Transform all points intervalMatrix3D.TransformPoints( points ); int axisIndx = 0; foreach( Axis axis in area.Axes ) { // Find size of projected axis size = Math.Sqrt( ( points[axisIndx].X - points[axisIndx+1].X ) * ( points[axisIndx].X - points[axisIndx+1].X ) + ( points[axisIndx].Y - points[axisIndx+1].Y ) * ( points[axisIndx].Y - points[axisIndx+1].Y ) ); // At the beginning plotting area chart is not calculated because // algorithm for labels calculates plotting area position. To // calculate labels position we need interval and interval // need this correction. Because of that Chart area is used // instead of plotting area position. If secondary label is // enabled error for using chart area position instead of // plotting area position is much bigger. This value // corrects this error. float plottingChartAreaCorrection = 1; if( !area.switchValueAxes ) { plottingChartAreaCorrection = 0.5F; } // Set correction for axis size if( axis.AxisName == AxisName.X || axis.AxisName == AxisName.X2 ) { if( area.switchValueAxes ) axis.interval3DCorrection = size / plottingRect.Height; else axis.interval3DCorrection = size / plottingRect.Width; } else { if( area.switchValueAxes ) axis.interval3DCorrection = size / plottingRect.Width; else axis.interval3DCorrection = size / plottingRect.Height * plottingChartAreaCorrection; } // There is a limit for correction if( axis.interval3DCorrection < 0.15 ) axis.interval3DCorrection = 0.15; // There is a limit for correction if( axis.interval3DCorrection > 0.8 ) axis.interval3DCorrection = 1.0; axisIndx += 2; } } /// /// Calculates real Y angle from Y angle and reverseSeriesOrder field /// /// Real Y angle internal int GetRealYAngle( ) { int yAngle; // Case from -90 to 90 yAngle = this.Area3DStyle.Rotation; // Case from 90 to 180 if( this._reverseSeriesOrder && this.Area3DStyle.Rotation >= 0 ) yAngle = this.Area3DStyle.Rotation - 180; // Case from -90 to -180 if( this._reverseSeriesOrder && this.Area3DStyle.Rotation <= 0 ) yAngle = this.Area3DStyle.Rotation + 180; return yAngle; } /// /// Check if surface element should be drawn on the Back or Front layer. /// /// Surface name. /// Back/front layer. /// Indicates that element is on the edge (drawn on the back layer). /// True if element should be drawn. internal bool ShouldDrawOnSurface(SurfaceNames surfaceName, bool backLayer, bool onEdge) { // Check if surface element should be drawn on the Back or Front layer. bool isVisible = ((this._visibleSurfaces & surfaceName) == surfaceName); // Elements on the edge are drawn on the back layer if(onEdge) { return backLayer; } return (backLayer == (!isVisible) ); } /// /// Indicates that data points in all series of this /// chart area should be drawn in reversed order. /// /// True if series points should be drawn in reversed order. internal bool DrawPointsInReverseOrder() { return (this.Area3DStyle.Rotation <= 0); } /// /// Checks if points should be drawn from sides to center. /// /// Which coordinate of COP (X, Y or Z) to test for surface overlapping /// True if points should be drawn from sides to center. internal bool DrawPointsToCenter(ref COPCoordinates coord) { bool result = false; COPCoordinates resultCoordinates = 0; // Check only if perspective is set if(this.Area3DStyle.Perspective != 0) { if( (coord & COPCoordinates.X) == COPCoordinates.X ) { // Only when Left & Right sides of plotting area are invisible if ((this._visibleSurfaces & SurfaceNames.Left) == 0 && (this._visibleSurfaces & SurfaceNames.Right) == 0) { result = true; } resultCoordinates = resultCoordinates | COPCoordinates.X; } if( (coord & COPCoordinates.Y) == COPCoordinates.Y ) { // Only when Top & Bottom sides of plotting area are invisible if ((this._visibleSurfaces & SurfaceNames.Top) == 0 && (this._visibleSurfaces & SurfaceNames.Bottom) == 0) { result = true; } resultCoordinates = resultCoordinates | COPCoordinates.Y; } if( (coord & COPCoordinates.Z) == COPCoordinates.Z ) { // Only when Front & Back sides of plotting area are invisible if ((this._visibleSurfaces & SurfaceNames.Front) == 0 && (this._visibleSurfaces & SurfaceNames.Back) == 0) { result = true; } resultCoordinates = resultCoordinates | COPCoordinates.Z; } } return result; } /// /// Checks if series should be drawn from sides to center. /// /// True if series should be drawn from sides to center. internal bool DrawSeriesToCenter() { // Check only if perspective is set if(this.Area3DStyle.Perspective != 0) { // Only when Left & Right sides of plotting area are invisible if ((this._visibleSurfaces & SurfaceNames.Front) == 0 && (this._visibleSurfaces & SurfaceNames.Back) == 0) { return true; } } return false; } #endregion #region 3D Series drawing and selection methods /// /// Draws 3D series in the chart area. /// /// Chart graphics object. internal void PaintChartSeries3D( ChartGraphics graph ) { // Reference to the chart area object ChartArea area = (ChartArea)this; // Get order of series drawing List seriesDrawingOrder = GetSeriesDrawingOrder(_reverseSeriesOrder); // Loop through all series in the order of drawing IChartType type; foreach( object seriesObj in seriesDrawingOrder) { Series series = (Series)seriesObj; type = Common.ChartTypeRegistry.GetChartType(series.ChartTypeName); type.Paint( graph, Common, area, series ); } } #endregion #region 3D Series & Points drawing order methods /// /// Gets a list of series names that belong to the same 3D cluster. /// /// One of the series names that belongs to the cluster. /// List of all series names that belong to the same cluster as specified series. internal List GetClusterSeriesNames(string seriesName) { // Iterate through all clusters foreach(List seriesNames in this.seriesClusters) { if(seriesNames.Contains(seriesName)) { return seriesNames; } } return new List(); } /// /// Gets the series list in drawing order. /// /// Series order should be reversed because of the Y axis angle. /// Gets the series list in drawing order. private List GetSeriesDrawingOrder(bool reverseSeriesOrder) { // Create list of series List seriesList = new List(); // Iterate through all clusters foreach(List seriesNames in this.seriesClusters) { // Make sure there is at least one series if(seriesNames.Count > 0) { // Get first series object in the current cluster Series series = Common.DataManager.Series[seriesNames[0]]; // Add series into the drawing list seriesList.Add(series); } } // Reversed series list if(reverseSeriesOrder) { seriesList.Reverse(); } // Check if series should be drawn from sides into the center if(DrawSeriesToCenter() && this.matrix3D.IsInitialized()) { // Get Z coordinate of Center Of Projection Point3D areaProjectionCenter = new Point3D(float.NaN, float.NaN, float.NaN); areaProjectionCenter = this.GetCenterOfProjection(COPCoordinates.Z); if(!float.IsNaN(areaProjectionCenter.Z)) { // Loop through all series for(int seriesIndex = 0; seriesIndex < seriesList.Count; seriesIndex++) { // Check if series is not empty if(((Series)seriesList[seriesIndex]).Points.Count == 0) { continue; } // Get series Z position float seriesDepth, seriesZPosition; this.GetSeriesZPositionAndDepth((Series)seriesList[seriesIndex], out seriesDepth, out seriesZPosition); // Check if series passes the Z coordinate of Center of Projection if(seriesZPosition >= areaProjectionCenter.Z) { // Reversed all series order staring from previous series --seriesIndex; if(seriesIndex < 0) seriesIndex = 0; seriesList.Reverse(seriesIndex, seriesList.Count - seriesIndex); break; } } } } return seriesList; } /// /// Gets number of stack groups in specified array of series names. /// /// Array of series names. /// Number of stack groups. One by default. private int GetNumberOfStackGroups(IList seriesNamesList) { this._stackGroupNames = new ArrayList(); foreach( object seriesName in seriesNamesList ) { // Get series object Series ser = this.Common.DataManager.Series[(string)seriesName]; // Get stack group name from the series string stackGroupName = string.Empty; if(ser.IsCustomPropertySet(CustomPropertyName.StackedGroupName)) { stackGroupName = ser[CustomPropertyName.StackedGroupName]; } // Add group name if it do not already exsist if(!this._stackGroupNames.Contains(stackGroupName)) { this._stackGroupNames.Add(stackGroupName); } } return this._stackGroupNames.Count; } /// /// Gets index of the series stack group. /// /// Series to get the index for. /// Group name this series belongs to. /// Index of series stack group. internal int GetSeriesStackGroupIndex(Series series, ref string stackGroupName) { stackGroupName = string.Empty; if(this._stackGroupNames != null) { // Get stack group name from the series if(series.IsCustomPropertySet(CustomPropertyName.StackedGroupName)) { stackGroupName = series[CustomPropertyName.StackedGroupName]; } return this._stackGroupNames.IndexOf(stackGroupName); } return 0; } /// /// Determine the order of points drawing from one or several series of the same type. /// /// List of series names. /// Chart type. /// If True selection mode is active (points order should be reversed). /// Which coordinate of COP (X, Y or Z) to test for surface overlapping /// Points comparer class. Can be Null. /// Index of the main Y value. /// Series should be drawn side by side. /// Array list of points in drawing order. internal ArrayList GetDataPointDrawingOrder( List seriesNamesList, IChartType chartType, bool selection, COPCoordinates coord, IComparer comparer, int mainYValueIndex, bool sideBySide) { ChartArea area = (ChartArea)this; // Array of points in all series ArrayList pointsList = new ArrayList(); //************************************************************ //** Analyse input series //************************************************************ // Find the number of data series for side-by-side drawing double numOfSeries = 1; if(area.Area3DStyle.IsClustered && !chartType.Stacked && sideBySide) { numOfSeries = seriesNamesList.Count; } // Check stacked series group names if(chartType.SupportStackedGroups) { // Fill the list of group names and get the number of unique groups int numberOfGroups = this.GetNumberOfStackGroups(seriesNamesList); // If series are not isClustered set series number to the stacked group number if(this.Area3DStyle.IsClustered && seriesNamesList.Count > 0) { numOfSeries = numberOfGroups; } } // Check if chart series are indexed bool indexedSeries = ChartHelper.IndexedSeries(this.Common, seriesNamesList.ToArray()); //************************************************************ //** Loop through all series and fill array of points //************************************************************ int seriesIndx = 0; foreach( object seriesName in seriesNamesList ) { // Get series object Series ser = this.Common.DataManager.Series[(string)seriesName]; // Check if stacked groups present if(chartType.SupportStackedGroups && this._stackGroupNames != null) { // Get index of the series using stacked group string groupName = string.Empty; seriesIndx = this.GetSeriesStackGroupIndex(ser, ref groupName); // Set current group name StackedColumnChart stackedColumnChart = chartType as StackedColumnChart; if (stackedColumnChart != null) { stackedColumnChart.currentStackGroup = groupName; } else { StackedBarChart stackedBarChart = chartType as StackedBarChart; if (stackedBarChart != null) { stackedBarChart.currentStackGroup = groupName; } } } // Set active vertical/horizontal axis and their max/min values Axis vAxis = (ser.YAxisType == AxisType.Primary) ? area.AxisY : area.AxisY2; Axis hAxis = (ser.XAxisType == AxisType.Primary) ? area.AxisX : area.AxisX2; // Get points interval: // - set interval to 1 for indexed series // - if points are not equaly spaced, the minimum interval between points is selected. // - if points have same interval bars do not overlap each other. bool sameInterval = true; double interval = 1; if(!indexedSeries) { interval = area.GetPointsInterval( seriesNamesList, hAxis.IsLogarithmic, hAxis.logarithmBase, true, out sameInterval ); } // Get column width double width = ser.GetPointWidth(area.Common.graph, hAxis, interval, 0.8) / numOfSeries; // Get series depth and Z position float seriesDepth, seriesZPosition; this.GetSeriesZPositionAndDepth(ser, out seriesDepth, out seriesZPosition); //************************************************************ //** Loop through all points in series //************************************************************ int index = 0; foreach( DataPoint point in ser.Points ) { // Increase point index index++; // Set x position double xCenterVal; double xPosition; if( indexedSeries ) { // The formula for position is based on a distance //from the grid line or nPoints position. xPosition = hAxis.GetPosition( (double)index ) - width * ((double) numOfSeries) / 2.0 + width/2 + seriesIndx * width; xCenterVal = hAxis.GetPosition( (double)index ); } else if( sameInterval ) { xPosition = hAxis.GetPosition( point.XValue ) - width * ((double) numOfSeries) / 2.0 + width/2 + seriesIndx * width; xCenterVal = hAxis.GetPosition( point.XValue ); } else { xPosition = hAxis.GetPosition( point.XValue ); xCenterVal = hAxis.GetPosition( point.XValue ); } //************************************************************ //** Create and add new DataPoint3D object //************************************************************ DataPoint3D pointEx = new DataPoint3D(); pointEx.indexedSeries = indexedSeries; pointEx.dataPoint = point; pointEx.index = index; pointEx.xPosition = xPosition; pointEx.xCenterVal = xCenterVal; pointEx.width = ser.GetPointWidth(area.Common.graph, hAxis, interval, 0.8) / numOfSeries; pointEx.depth = seriesDepth; pointEx.zPosition = seriesZPosition; // Set Y value and height double yValue = chartType.GetYValue(Common, area, ser, point, index - 1, mainYValueIndex); if (point.IsEmpty && Double.IsNaN(yValue)) { yValue = 0.0; } pointEx.yPosition = vAxis.GetPosition(yValue); pointEx.height = vAxis.GetPosition(yValue - chartType.GetYValue(Common, area, ser, point, index - 1, -1)); pointsList.Add(pointEx); } // Data series index if(numOfSeries > 1 && sideBySide) { seriesIndx++; } } //************************************************************ //** Sort points in drawing order //************************************************************ if(comparer == null) { comparer = new PointsDrawingOrderComparer((ChartArea)this, selection, coord); } pointsList.Sort(comparer); return pointsList; } #endregion #region Points drawing order comparer class /// /// Used to compare points in array and sort them by drawing order. /// internal class PointsDrawingOrderComparer : IComparer { /// /// Chart area object reference. /// private ChartArea _area = null; /// /// Area X position where visible sides are switched. /// private Point3D _areaProjectionCenter = new Point3D(float.NaN, float.NaN, float.NaN); /// /// Selection mode. Points order should be reversed. /// private bool _selection = false; /// /// Public constructor. /// /// Chart area. /// Selection indicator. /// Which coordinate of COP (X, Y or Z) to test for surface overlapping public PointsDrawingOrderComparer(ChartArea area, bool selection, COPCoordinates coord) { this._area = area; this._selection = selection; // Get center of projection if(area.DrawPointsToCenter(ref coord)) { _areaProjectionCenter = area.GetCenterOfProjection(coord); } } /// /// Comparer method. /// /// First object. /// Second object. /// Comparison result. public int Compare(object o1, object o2) { DataPoint3D point1 = (DataPoint3D) o1; DataPoint3D point2 = (DataPoint3D) o2; int result = 0; if(point1.xPosition < point2.xPosition) { result = -1; } else if(point1.xPosition > point2.xPosition) { result = 1; } else { // If X coordinate is the same - filter by Y coordinate if(point1.yPosition < point2.yPosition) { result = 1; } else if(point1.yPosition > point2.yPosition) { result = -1; } // Order points from sides to center if(!float.IsNaN(_areaProjectionCenter.Y)) { double yMin1 = Math.Min(point1.yPosition, point1.height); double yMax1 = Math.Max(point1.yPosition, point1.height); double yMin2 = Math.Min(point2.yPosition, point2.height); double yMax2 = Math.Max(point2.yPosition, point2.height); if(_area.IsBottomSceneWallVisible()) { if( yMin1 <= _areaProjectionCenter.Y && yMin2 <= _areaProjectionCenter.Y ) { result *= -1; } else if( yMin1 <= _areaProjectionCenter.Y) { result = 1; } } else { if( yMax1 >= _areaProjectionCenter.Y && yMax2 >= _areaProjectionCenter.Y ) { result *= 1; } else if( yMax1 >= _areaProjectionCenter.Y) { result = 1; } else { result *= -1; } } } // Reversed order if looking from the bottom else if(!_area.IsBottomSceneWallVisible()) { result *= -1; } } if(point1.xPosition != point2.xPosition) { // Order points from sides to center if (!float.IsNaN(_areaProjectionCenter.X)) { if ((point1.xPosition + point1.width / 2f) >= _areaProjectionCenter.X && (point2.xPosition + point2.width / 2f) >= _areaProjectionCenter.X) { result *= -1; } } // Reversed order of points by X value else if (_area.DrawPointsInReverseOrder()) { result *= -1; } } return (_selection) ? -result : result; } } #endregion #region Center of Projection calculation methods /// /// Returns one or many (X, Y, Z) coordinates of the center of projection. /// /// Defines coordinates to return. /// Center of projection. Value can be set to float.NaN if not defined or outside plotting area. internal Point3D GetCenterOfProjection(COPCoordinates coord) { // Define 2 points in the opposite corners of the plotting area Point3D[] points = new Point3D[2]; points[0] = new Point3D(this.PlotAreaPosition.X, this.PlotAreaPosition.Bottom, 0f); points[1] = new Point3D(this.PlotAreaPosition.Right, this.PlotAreaPosition.Y, this.areaSceneDepth); // Check if surfaces (points 1 & 2) has same orientation bool xSameOrientation, ySameOrientation, zSameOrientation; CheckSurfaceOrientation( coord, points[0], points[1], out xSameOrientation, out ySameOrientation, out zSameOrientation); // If orientation of all surfaces is the same - no futher processing is required (COP is outside of plotting area) Point3D resultPoint = new Point3D( (xSameOrientation) ? float.NaN : 0f, (ySameOrientation) ? float.NaN : 0f, (zSameOrientation) ? float.NaN : 0f); if( ( ((coord & COPCoordinates.X) != COPCoordinates.X) || xSameOrientation ) && ( ((coord & COPCoordinates.Y) != COPCoordinates.Y) || ySameOrientation ) && ( ((coord & COPCoordinates.Z) != COPCoordinates.Z) || zSameOrientation ) ) { return resultPoint; } // Calculate the smallest interval (0.5 pixels) in relative coordinates SizeF interval = new SizeF(0.5f, 0.5f); #if WINFORMS_CONTROL interval.Width = interval.Width * 100F / ((float)(this.Common.Chart.Width - 1)); interval.Height = interval.Height * 100F / ((float)(this.Common.Chart.Height - 1)); #else interval.Width = interval.Width * 100F / ((float)(this.Common.Chart.Width.Value - 1)); interval.Height = interval.Height * 100F / ((float)(this.Common.Chart.Height.Value - 1)); #endif //#if WINFORMS_CONTROL // Find middle point and check it's surface orientation bool doneFlag = false; while(!doneFlag) { // Find middle point Point3D middlePoint = new Point3D( (points[0].X + points[1].X) / 2f, (points[0].Y + points[1].Y) / 2f, (points[0].Z + points[1].Z) / 2f); // Check if surfaces (points 1 & middle) has same orientation CheckSurfaceOrientation( coord, points[0], middlePoint, out xSameOrientation, out ySameOrientation, out zSameOrientation); // Calculate points 1 & 2 depending on surface orientation of the middle point points[(xSameOrientation) ? 0 : 1].X = middlePoint.X; points[(ySameOrientation) ? 0 : 1].Y = middlePoint.Y; points[(zSameOrientation) ? 0 : 1].Z = middlePoint.Z; // Check if no more calculations required doneFlag = true; if( (coord & COPCoordinates.X) == COPCoordinates.X && Math.Abs(points[1].X - points[0].X) >= interval.Width) { doneFlag = false; } if( (coord & COPCoordinates.Y) == COPCoordinates.Y && Math.Abs(points[1].Y - points[0].Y) >= interval.Height) { doneFlag = false; } if( (coord & COPCoordinates.Z) == COPCoordinates.Z && Math.Abs(points[1].Z - points[0].Z) >= interval.Width) { doneFlag = false; } } // Calculate result point if(!float.IsNaN(resultPoint.X)) resultPoint.X = (points[0].X + points[1].X) / 2f; if(!float.IsNaN(resultPoint.Y)) resultPoint.Y = (points[0].Y + points[1].Y) / 2f; if(!float.IsNaN(resultPoint.Z)) resultPoint.Z = (points[0].Z + points[1].Z) / 2f; return resultPoint; } /// /// Checks orientations of two normal surfaces for each coordinate X, Y and Z. /// /// Defines coordinates to return. /// First point. /// Second point. /// X surfaces orientation is the same. /// Y surfaces orientation is the same. /// Z surfaces orientation is the same. private void CheckSurfaceOrientation( COPCoordinates coord, Point3D point1, Point3D point2, out bool xSameOrientation, out bool ySameOrientation, out bool zSameOrientation) { Point3D[] pointsSurface = new Point3D[3]; bool surf1, surf2; // Initialize returned values xSameOrientation = true; ySameOrientation = true; zSameOrientation = true; // Check X axis coordinates (ledt & right surfaces) if( (coord & COPCoordinates.X) == COPCoordinates.X ) { // Define Left surface coordinates, transform them and check visibility pointsSurface[0] = new Point3D(point1.X, this.PlotAreaPosition.Y, 0f); pointsSurface[1] = new Point3D(point1.X, this.PlotAreaPosition.Bottom, 0f); pointsSurface[2] = new Point3D(point1.X, this.PlotAreaPosition.Bottom, this.areaSceneDepth); this.matrix3D.TransformPoints( pointsSurface ); surf1 = ChartGraphics.IsSurfaceVisible(pointsSurface[0], pointsSurface[1], pointsSurface[2]); // Define Right surface coordinates, transform them and check visibility pointsSurface[0] = new Point3D(point2.X, this.PlotAreaPosition.Y, 0f); pointsSurface[1] = new Point3D(point2.X, this.PlotAreaPosition.Bottom, 0f); pointsSurface[2] = new Point3D(point2.X, this.PlotAreaPosition.Bottom, this.areaSceneDepth); this.matrix3D.TransformPoints( pointsSurface ); surf2 = ChartGraphics.IsSurfaceVisible(pointsSurface[0], pointsSurface[1], pointsSurface[2]); // Check if surfaces have same visibility xSameOrientation = (surf1 == surf2); } // Check Y axis coordinates (top & bottom surfaces) if( (coord & COPCoordinates.Y) == COPCoordinates.Y ) { // Define Bottom surface coordinates, transform them and check visibility pointsSurface[0] = new Point3D(this.PlotAreaPosition.X, point1.Y, this.areaSceneDepth); pointsSurface[1] = new Point3D(this.PlotAreaPosition.X, point1.Y, 0f); pointsSurface[2] = new Point3D(this.PlotAreaPosition.Right, point1.Y, 0f); this.matrix3D.TransformPoints( pointsSurface ); surf1 = ChartGraphics.IsSurfaceVisible(pointsSurface[0], pointsSurface[1], pointsSurface[2]); // Define Top surface coordinates, transform them and check visibility pointsSurface[0] = new Point3D(this.PlotAreaPosition.X, point2.Y, this.areaSceneDepth); pointsSurface[1] = new Point3D(this.PlotAreaPosition.X, point2.Y, 0f); pointsSurface[2] = new Point3D(this.PlotAreaPosition.Right, point2.Y, 0f); this.matrix3D.TransformPoints( pointsSurface ); surf2 = ChartGraphics.IsSurfaceVisible(pointsSurface[0], pointsSurface[1], pointsSurface[2]); // Check if surfaces have same visibility ySameOrientation = (surf1 == surf2); } // Check Y axis coordinates (front & back surfaces) if( (coord & COPCoordinates.Z) == COPCoordinates.Z ) { // Define Front surface coordinates, transform them and check visibility pointsSurface[0] = new Point3D(this.PlotAreaPosition.X, this.PlotAreaPosition.Y, point1.Z); pointsSurface[1] = new Point3D(this.PlotAreaPosition.X, this.PlotAreaPosition.Bottom, point1.Z); pointsSurface[2] = new Point3D(this.PlotAreaPosition.Right, this.PlotAreaPosition.Bottom, point1.Z); this.matrix3D.TransformPoints( pointsSurface ); surf1 = ChartGraphics.IsSurfaceVisible(pointsSurface[0], pointsSurface[1], pointsSurface[2]); // Define Back surface coordinates, transform them and check visibility pointsSurface[0] = new Point3D(this.PlotAreaPosition.X, this.PlotAreaPosition.Y, point2.Z); pointsSurface[1] = new Point3D(this.PlotAreaPosition.X, this.PlotAreaPosition.Bottom, point2.Z); pointsSurface[2] = new Point3D(this.PlotAreaPosition.Right, this.PlotAreaPosition.Bottom, point2.Z); this.matrix3D.TransformPoints( pointsSurface ); surf2 = ChartGraphics.IsSurfaceVisible(pointsSurface[0], pointsSurface[1], pointsSurface[2]); // Check if surfaces have same visibility zSameOrientation = (surf1 == surf2); } } #endregion } }