536cd135cc
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
2240 lines
68 KiB
C#
2240 lines
68 KiB
C#
//-------------------------------------------------------------
|
||
// <copyright company=<3D>Microsoft Corporation<6F>>
|
||
// Copyright <20> Microsoft Corporation. All Rights Reserved.
|
||
// </copyright>
|
||
//-------------------------------------------------------------
|
||
// @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
|
||
|
||
/// <summary>
|
||
/// A lighting style for a 3D chart area.
|
||
/// </summary>
|
||
public enum LightStyle
|
||
{
|
||
/// <summary>
|
||
/// No lighting.
|
||
/// </summary>
|
||
None,
|
||
/// <summary>
|
||
/// Simplistic lighting.
|
||
/// </summary>
|
||
Simplistic,
|
||
/// <summary>
|
||
/// Realistic lighting.
|
||
/// </summary>
|
||
Realistic
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 3D Center of Projetion coordinates enumeration
|
||
|
||
/// <summary>
|
||
/// Coordinates of the Center Of Projection
|
||
/// </summary>
|
||
[Flags]
|
||
internal enum COPCoordinates
|
||
{
|
||
/// <summary>
|
||
/// Check X coordinate.
|
||
/// </summary>
|
||
X = 1,
|
||
/// <summary>
|
||
/// Check Y coordinate.
|
||
/// </summary>
|
||
Y = 2,
|
||
/// <summary>
|
||
/// Check Z coordinate.
|
||
/// </summary>
|
||
Z = 4
|
||
}
|
||
|
||
#endregion
|
||
|
||
/// <summary>
|
||
/// The ChartArea3DStyleClass class provides the functionality for 3D attributes of chart areas,
|
||
/// such as rotation angles and perspective.
|
||
/// </summary>
|
||
#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
|
||
|
||
/// <summary>
|
||
/// ChartArea3DStyle constructor.
|
||
/// </summary>
|
||
public ChartArea3DStyle()
|
||
{
|
||
}
|
||
|
||
/// <summary>
|
||
/// ChartArea3DStyle constructor.
|
||
/// </summary>
|
||
public ChartArea3DStyle(ChartArea chartArea)
|
||
{
|
||
this._chartArea = chartArea;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Initialize Chart area and axes
|
||
/// </summary>
|
||
/// <param name="chartArea">Chart area object.</param>
|
||
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
|
||
|
||
/// <summary>
|
||
/// Gets or sets a Boolean value that toggles 3D for a chart area on and off.
|
||
/// </summary>
|
||
[
|
||
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();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// Gets or sets a Boolean that determines if a chart area is displayed using an isometric projection.
|
||
/// </summary>
|
||
[
|
||
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();
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// Gets or sets a Boolean value that determines if bar chart or column
|
||
/// chart data series are clustered (displayed along distinct rows).
|
||
/// </summary>
|
||
[
|
||
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();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets or sets the style of lighting for a 3D chart area.
|
||
/// </summary>
|
||
[
|
||
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();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets or sets the percent of perspective for a 3D chart area.
|
||
/// </summary>
|
||
[
|
||
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();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets or sets the inclination for a 3D chart area.
|
||
/// </summary>
|
||
[
|
||
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();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets or sets the rotation angle for a 3D chart area.
|
||
/// </summary>
|
||
[
|
||
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();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets or sets the width of the walls displayed in 3D chart areas.
|
||
/// </summary>
|
||
[
|
||
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();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets or sets the depth of data points displayed in 3D chart areas (0-1000%).
|
||
/// </summary>
|
||
[
|
||
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();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets or sets the distance between series rows in 3D chart areas (0-1000%).
|
||
/// </summary>
|
||
[
|
||
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
|
||
}
|
||
|
||
/// <summary>
|
||
/// 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.
|
||
/// </summary>
|
||
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;
|
||
|
||
/// <summary>
|
||
/// Indicates that series order should be reversed to simulate Y axis rotation.
|
||
/// </summary>
|
||
private bool _reverseSeriesOrder = false;
|
||
|
||
/// <summary>
|
||
/// Old X axis reversed flag
|
||
/// </summary>
|
||
internal bool oldReverseX = false;
|
||
|
||
/// <summary>
|
||
/// Old Y axis reversed flag
|
||
/// </summary>
|
||
internal bool oldReverseY = false;
|
||
|
||
/// <summary>
|
||
/// Old Y axis rotation angle
|
||
/// </summary>
|
||
internal int oldYAngle = 30;
|
||
|
||
/// <summary>
|
||
/// List of all stack group names
|
||
/// </summary>
|
||
private ArrayList _stackGroupNames = null;
|
||
|
||
/// <summary>
|
||
/// This list contains an array of series names for each 3D cluster
|
||
/// </summary>
|
||
internal List<List<string>> seriesClusters = null;
|
||
|
||
#endregion
|
||
|
||
#region 3D Style properties
|
||
|
||
/// <summary>
|
||
/// Gets or sets a ChartArea3DStyle object, used to draw all series in a chart area in 3D.
|
||
/// </summary>
|
||
[
|
||
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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Indicates that series order should be reversed to simulate Y axis rotation.
|
||
/// </summary>
|
||
internal bool ReverseSeriesOrder
|
||
{
|
||
get { return _reverseSeriesOrder; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets the list of all stack group names
|
||
/// </summary>
|
||
internal ArrayList StackGroupNames
|
||
{
|
||
get { return _stackGroupNames; }
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 3D Coordinates transfotmation methods
|
||
|
||
/// <summary>
|
||
/// Call this method to apply 3D transformations on an array of 3D points (must be done before calling GDI+ drawing methods).
|
||
/// </summary>
|
||
/// <param name="points">3D Points array.</param>
|
||
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
|
||
|
||
/// <summary>
|
||
/// Draws chart area 3D scene, which consists of 3 or 2 walls.
|
||
/// </summary>
|
||
/// <param name="graph">Chart graphics object.</param>
|
||
/// <param name="position">Chart area 2D position.</param>
|
||
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 );
|
||
}
|
||
|
||
}
|
||
|
||
/// <summary>
|
||
/// Helper method which return True if bottom wall of the
|
||
/// chart area scene is visible.
|
||
/// </summary>
|
||
/// <returns>True if bottom wall is visible.</returns>
|
||
internal bool IsBottomSceneWallVisible()
|
||
{
|
||
return (this.Area3DStyle.Inclination >= 0);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Helper method which return True if main wall of the
|
||
/// chart area scene is displayed on the front side.
|
||
/// </summary>
|
||
/// <returns>True if front wall is visible.</returns>
|
||
internal bool IsMainSceneWallOnFront()
|
||
{
|
||
// Note: Not used in this version!
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Helper method which return True if side wall of the
|
||
/// chart area scene is displayed on the left side.
|
||
/// </summary>
|
||
/// <returns>True if bottom wall is visible.</returns>
|
||
internal bool IsSideSceneWallOnLeft()
|
||
{
|
||
return (this.Area3DStyle.Rotation > 0);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 3D Scene depth claculation methods
|
||
|
||
/// <summary>
|
||
/// Call this method to get the Z position of a series (useful for custom drawing).
|
||
/// </summary>
|
||
/// <param name="series">The series to retrieve the Z position for.</param>
|
||
/// <returns>The Z position of the specified series. Measured as a percentage of the chart area's depth.</returns>
|
||
public float GetSeriesZPosition(Series series)
|
||
{
|
||
float positionZ, depth;
|
||
GetSeriesZPositionAndDepth(series, out depth, out positionZ);
|
||
return ((positionZ + depth/2f) / this.areaSceneDepth) * 100f;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Call this method to get the depth of a series in a chart area.
|
||
/// </summary>
|
||
/// <param name="series">The series to retrieve the depth for.</param>
|
||
/// <returns>The depth of the specified series. Measured as a percentage of the chart area's depth.</returns>
|
||
public float GetSeriesDepth(Series series)
|
||
{
|
||
float positionZ, depth;
|
||
GetSeriesZPositionAndDepth(series, out depth, out positionZ);
|
||
return (depth / this.areaSceneDepth) * 100f;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Calculates area 3D scene depth depending on the number of isClustered
|
||
/// series and interval between points.
|
||
/// </summary>
|
||
/// <returns>Returns the depth of the chart area scene.</returns>
|
||
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());
|
||
}
|
||
|
||
/// <summary>
|
||
/// Calculates the depth and Z position for specified series.
|
||
/// </summary>
|
||
/// <param name="series">Series object.</param>
|
||
/// <param name="depth">Returns series depth.</param>
|
||
/// <param name="positionZ">Returns series Z position.</param>
|
||
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);
|
||
}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// Returns number of clusters on the Z axis.
|
||
/// </summary>
|
||
/// <returns>Number of clusters on the Z axis.</returns>
|
||
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<List<string>>();
|
||
|
||
// 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<string>());
|
||
}
|
||
|
||
// Add series name into the current cluster
|
||
this.seriesClusters[clusterIndex].Add(seriesName);
|
||
}
|
||
}
|
||
|
||
return this.seriesClusters.Count;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get series cluster index.
|
||
/// </summary>
|
||
/// <param name="series">Series object.</param>
|
||
/// <returns>Series cluster index.</returns>
|
||
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<string> 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
|
||
|
||
/// <summary>
|
||
/// 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.
|
||
/// </summary>
|
||
/// <returns>Returns estimated scene depth</returns>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Estimate Interval for 3D Charts. When scene is rotated the
|
||
/// number of labels should be changed.
|
||
/// </summary>
|
||
/// <param name="graph">Chart graphics object.</param>
|
||
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;
|
||
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// Calculates real Y angle from Y angle and reverseSeriesOrder field
|
||
/// </summary>
|
||
/// <returns>Real Y angle</returns>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Check if surface element should be drawn on the Back or Front layer.
|
||
/// </summary>
|
||
/// <param name="surfaceName">Surface name.</param>
|
||
/// <param name="backLayer">Back/front layer.</param>
|
||
/// <param name="onEdge">Indicates that element is on the edge (drawn on the back layer).</param>
|
||
/// <returns>True if element should be drawn.</returns>
|
||
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) );
|
||
}
|
||
|
||
/// <summary>
|
||
/// Indicates that data points in all series of this
|
||
/// chart area should be drawn in reversed order.
|
||
/// </summary>
|
||
/// <returns>True if series points should be drawn in reversed order.</returns>
|
||
internal bool DrawPointsInReverseOrder()
|
||
{
|
||
return (this.Area3DStyle.Rotation <= 0);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Checks if points should be drawn from sides to center.
|
||
/// </summary>
|
||
/// <param name="coord">Which coordinate of COP (X, Y or Z) to test for surface overlapping</param>
|
||
/// <returns>True if points should be drawn from sides to center.</returns>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Checks if series should be drawn from sides to center.
|
||
/// </summary>
|
||
/// <returns>True if series should be drawn from sides to center.</returns>
|
||
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
|
||
|
||
/// <summary>
|
||
/// Draws 3D series in the chart area.
|
||
/// </summary>
|
||
/// <param name="graph">Chart graphics object.</param>
|
||
internal void PaintChartSeries3D( ChartGraphics graph )
|
||
{
|
||
// Reference to the chart area object
|
||
ChartArea area = (ChartArea)this;
|
||
|
||
// Get order of series drawing
|
||
List<Series> 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
|
||
|
||
/// <summary>
|
||
/// Gets a list of series names that belong to the same 3D cluster.
|
||
/// </summary>
|
||
/// <param name="seriesName">One of the series names that belongs to the cluster.</param>
|
||
/// <returns>List of all series names that belong to the same cluster as specified series.</returns>
|
||
internal List<string> GetClusterSeriesNames(string seriesName)
|
||
{
|
||
// Iterate through all clusters
|
||
foreach(List<string> seriesNames in this.seriesClusters)
|
||
{
|
||
if(seriesNames.Contains(seriesName))
|
||
{
|
||
return seriesNames;
|
||
}
|
||
}
|
||
return new List<string>();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets the series list in drawing order.
|
||
/// </summary>
|
||
/// <param name="reverseSeriesOrder">Series order should be reversed because of the Y axis angle.</param>
|
||
/// <returns>Gets the series list in drawing order.</returns>
|
||
private List<Series> GetSeriesDrawingOrder(bool reverseSeriesOrder)
|
||
{
|
||
// Create list of series
|
||
List<Series> seriesList = new List<Series>();
|
||
|
||
// Iterate through all clusters
|
||
foreach(List<string> 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;
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// Gets number of stack groups in specified array of series names.
|
||
/// </summary>
|
||
/// <param name="seriesNamesList">Array of series names.</param>
|
||
/// <returns>Number of stack groups. One by default.</returns>
|
||
private int GetNumberOfStackGroups(IList<string> 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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets index of the series stack group.
|
||
/// </summary>
|
||
/// <param name="series">Series to get the index for.</param>
|
||
/// <param name="stackGroupName">Group name this series belongs to.</param>
|
||
/// <returns>Index of series stack group.</returns>
|
||
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;
|
||
}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// Determine the order of points drawing from one or several series of the same type.
|
||
/// </summary>
|
||
/// <param name="seriesNamesList">List of series names.</param>
|
||
/// <param name="chartType">Chart type.</param>
|
||
/// <param name="selection">If True selection mode is active (points order should be reversed).</param>
|
||
/// <param name="coord">Which coordinate of COP (X, Y or Z) to test for surface overlapping</param>
|
||
/// <param name="comparer">Points comparer class. Can be Null.</param>
|
||
/// <param name="mainYValueIndex">Index of the main Y value.</param>
|
||
/// <param name="sideBySide">Series should be drawn side by side.</param>
|
||
/// <returns>Array list of points in drawing order.</returns>
|
||
internal ArrayList GetDataPointDrawingOrder(
|
||
List<string> 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
|
||
|
||
/// <summary>
|
||
/// Used to compare points in array and sort them by drawing order.
|
||
/// </summary>
|
||
internal class PointsDrawingOrderComparer : IComparer
|
||
{
|
||
/// <summary>
|
||
/// Chart area object reference.
|
||
/// </summary>
|
||
private ChartArea _area = null;
|
||
|
||
/// <summary>
|
||
/// Area X position where visible sides are switched.
|
||
/// </summary>
|
||
private Point3D _areaProjectionCenter = new Point3D(float.NaN, float.NaN, float.NaN);
|
||
|
||
/// <summary>
|
||
/// Selection mode. Points order should be reversed.
|
||
/// </summary>
|
||
private bool _selection = false;
|
||
|
||
/// <summary>
|
||
/// Public constructor.
|
||
/// </summary>
|
||
/// <param name="area">Chart area.</param>
|
||
/// <param name="selection">Selection indicator.</param>
|
||
/// <param name="coord">Which coordinate of COP (X, Y or Z) to test for surface overlapping</param>
|
||
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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Comparer method.
|
||
/// </summary>
|
||
/// <param name="o1">First object.</param>
|
||
/// <param name="o2">Second object.</param>
|
||
/// <returns>Comparison result.</returns>
|
||
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
|
||
|
||
/// <summary>
|
||
/// Returns one or many (X, Y, Z) coordinates of the center of projection.
|
||
/// </summary>
|
||
/// <param name="coord">Defines coordinates to return.</param>
|
||
/// <returns>Center of projection. Value can be set to float.NaN if not defined or outside plotting area.</returns>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Checks orientations of two normal surfaces for each coordinate X, Y and Z.
|
||
/// </summary>
|
||
/// <param name="coord">Defines coordinates to return.</param>
|
||
/// <param name="point1">First point.</param>
|
||
/// <param name="point2">Second point.</param>
|
||
/// <param name="xSameOrientation">X surfaces orientation is the same.</param>
|
||
/// <param name="ySameOrientation">Y surfaces orientation is the same.</param>
|
||
/// <param name="zSameOrientation">Z surfaces orientation is the same.</param>
|
||
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
|
||
}
|
||
}
|