1015 lines
29 KiB
C#
1015 lines
29 KiB
C#
|
//-------------------------------------------------------------
|
|||
|
// <copyright company=<3D>Microsoft Corporation<6F>>
|
|||
|
// Copyright <20> Microsoft Corporation. All Rights Reserved.
|
|||
|
// </copyright>
|
|||
|
//-------------------------------------------------------------
|
|||
|
// @owner=alexgor, deliant
|
|||
|
//=================================================================
|
|||
|
// File: AxisScaleSegments.cs
|
|||
|
//
|
|||
|
// Namespace: System.Web.UI.WebControls[Windows.Forms].Charting
|
|||
|
//
|
|||
|
// Classes: AxisScaleSegment, AxisScaleSegmentCollection
|
|||
|
//
|
|||
|
// Purpose:
|
|||
|
//
|
|||
|
// Reviewed:
|
|||
|
//
|
|||
|
//===================================================================
|
|||
|
|
|||
|
#region Used namespaces
|
|||
|
|
|||
|
using System;
|
|||
|
using System.Collections;
|
|||
|
using System.Collections.Specialized;
|
|||
|
using System.ComponentModel;
|
|||
|
using System.ComponentModel.Design;
|
|||
|
using System.Data;
|
|||
|
using System.Drawing;
|
|||
|
using System.Drawing.Design;
|
|||
|
using System.Drawing.Drawing2D;
|
|||
|
using System.Globalization;
|
|||
|
#if Microsoft_CONTROL
|
|||
|
|
|||
|
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;
|
|||
|
using System.Windows.Forms.DataVisualization.Charting;
|
|||
|
|
|||
|
#else
|
|||
|
using System.Web;
|
|||
|
using System.Web.UI;
|
|||
|
using System.Web.UI.DataVisualization.Charting;
|
|||
|
using System.Web.UI.DataVisualization.Charting.Data;
|
|||
|
using System.Web.UI.DataVisualization.Charting.ChartTypes;
|
|||
|
using System.Web.UI.DataVisualization.Charting.Utilities;
|
|||
|
#endif
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#if Microsoft_CONTROL
|
|||
|
namespace System.Windows.Forms.DataVisualization.Charting
|
|||
|
#else
|
|||
|
namespace System.Web.UI.DataVisualization.Charting
|
|||
|
|
|||
|
#endif
|
|||
|
{
|
|||
|
/// <summary>
|
|||
|
/// <b>AxisScaleSegment</b> class represents a single segment of the axis with
|
|||
|
/// it's own scale and intervals.
|
|||
|
/// </summary>
|
|||
|
[
|
|||
|
SRDescription("DescriptionAttributeAxisScaleSegment_AxisScaleSegment"),
|
|||
|
]
|
|||
|
internal class AxisScaleSegment
|
|||
|
{
|
|||
|
#region Fields
|
|||
|
|
|||
|
// Associated axis
|
|||
|
internal Axis axis = null;
|
|||
|
|
|||
|
// Axis segment position in percent of the axis size
|
|||
|
private double _position = 0.0;
|
|||
|
|
|||
|
// Axis segment size in percent of the axis size
|
|||
|
private double _size = 0.0;
|
|||
|
|
|||
|
// Axis segment spacing in percent of the axis size
|
|||
|
private double _spacing = 0.0;
|
|||
|
|
|||
|
// Axis segment scale minimum value
|
|||
|
private double _scaleMinimum = 0.0;
|
|||
|
|
|||
|
// Axis segment scale maximum value
|
|||
|
private double _scaleMaximum = 0.0;
|
|||
|
|
|||
|
// Axis segment interval offset.
|
|||
|
private double _intervalOffset = 0;
|
|||
|
|
|||
|
// Axis segment interval.
|
|||
|
private double _interval = 0;
|
|||
|
|
|||
|
// Axis segment interval units type.
|
|||
|
private DateTimeIntervalType _intervalType = DateTimeIntervalType.Auto;
|
|||
|
|
|||
|
// Axis segment interval offset units type.
|
|||
|
private DateTimeIntervalType _intervalOffsetType = DateTimeIntervalType.Auto;
|
|||
|
|
|||
|
// Object associated with the segment
|
|||
|
private object _tag = null;
|
|||
|
|
|||
|
// Stack used to save/load axis settings
|
|||
|
private Stack _oldAxisSettings = new Stack();
|
|||
|
|
|||
|
#endregion // Fields
|
|||
|
|
|||
|
#region Constructor
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Default object constructor.
|
|||
|
/// </summary>
|
|||
|
public AxisScaleSegment()
|
|||
|
{
|
|||
|
}
|
|||
|
|
|||
|
#endregion // Constructor
|
|||
|
|
|||
|
#region Properties
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Axis segment position in axis size percentage.
|
|||
|
/// </summary>
|
|||
|
[
|
|||
|
SRCategory("CategoryAttributeMisc"),
|
|||
|
DefaultValue(0.0),
|
|||
|
SRDescription("DescriptionAttributeAxisScaleSegment_Position"),
|
|||
|
]
|
|||
|
public double Position
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
return this._position;
|
|||
|
}
|
|||
|
set
|
|||
|
{
|
|||
|
if(value < 0.0 || value > 100.0)
|
|||
|
{
|
|||
|
throw (new ArgumentOutOfRangeException("value", SR.ExceptionAxisScaleSegmentsPositionInvalid));
|
|||
|
}
|
|||
|
this._position = value;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Axis segment size in axis size percentage.
|
|||
|
/// </summary>
|
|||
|
[
|
|||
|
SRCategory("CategoryAttributeMisc"),
|
|||
|
DefaultValue(0.0),
|
|||
|
SRDescription("DescriptionAttributeAxisScaleSegment_Size"),
|
|||
|
]
|
|||
|
public double Size
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
return this._size;
|
|||
|
}
|
|||
|
set
|
|||
|
{
|
|||
|
if(value < 0.0 || value > 100.0)
|
|||
|
{
|
|||
|
throw (new ArgumentOutOfRangeException("value", SR.ExceptionAxisScaleSegmentsSizeInvalid));
|
|||
|
}
|
|||
|
this._size = value;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Axis segment spacing in axis size percentage.
|
|||
|
/// </summary>
|
|||
|
[
|
|||
|
SRCategory("CategoryAttributeMisc"),
|
|||
|
DefaultValue(0.0),
|
|||
|
SRDescription("DescriptionAttributeAxisScaleSegment_Spacing"),
|
|||
|
]
|
|||
|
public double Spacing
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
return this._spacing;
|
|||
|
}
|
|||
|
set
|
|||
|
{
|
|||
|
if(value < 0.0 || value > 100.0)
|
|||
|
{
|
|||
|
throw (new ArgumentOutOfRangeException("value", SR.ExceptionAxisScaleSegmentsSpacingInvalid));
|
|||
|
}
|
|||
|
this._spacing = value;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Axis segment scale maximum value.
|
|||
|
/// </summary>
|
|||
|
[
|
|||
|
SRCategory("CategoryAttributeMisc"),
|
|||
|
DefaultValue(0.0),
|
|||
|
SRDescription("DescriptionAttributeAxisScaleSegment_ScaleMaximum"),
|
|||
|
]
|
|||
|
public double ScaleMaximum
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
return this._scaleMaximum;
|
|||
|
}
|
|||
|
set
|
|||
|
{
|
|||
|
this._scaleMaximum = value;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Axis segment scale minimum value.
|
|||
|
/// </summary>
|
|||
|
[
|
|||
|
SRCategory("CategoryAttributeMisc"),
|
|||
|
DefaultValue(0.0),
|
|||
|
SRDescription("DescriptionAttributeAxisScaleSegment_ScaleMinimum"),
|
|||
|
]
|
|||
|
public double ScaleMinimum
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
return this._scaleMinimum;
|
|||
|
}
|
|||
|
set
|
|||
|
{
|
|||
|
this._scaleMinimum = value;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Axis segment interval size.
|
|||
|
/// </summary>
|
|||
|
[
|
|||
|
SRCategory("CategoryAttributeInterval"),
|
|||
|
DefaultValue(0.0),
|
|||
|
SRDescription("DescriptionAttributeAxisScaleSegment_Interval"),
|
|||
|
TypeConverter(typeof(AxisIntervalValueConverter)),
|
|||
|
]
|
|||
|
public double Interval
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
return this._interval;
|
|||
|
}
|
|||
|
set
|
|||
|
{
|
|||
|
// Axis interval properties must be set
|
|||
|
if(double.IsNaN(value))
|
|||
|
{
|
|||
|
this._interval = 0;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
this._interval = value;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Axis segment interval offset.
|
|||
|
/// </summary>
|
|||
|
[
|
|||
|
SRCategory("CategoryAttributeInterval"),
|
|||
|
DefaultValue(0.0),
|
|||
|
SRDescription("DescriptionAttributeAxisScaleSegment_IntervalOffset"),
|
|||
|
TypeConverter(typeof(AxisIntervalValueConverter))
|
|||
|
]
|
|||
|
public double IntervalOffset
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
return _intervalOffset;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Axis segment interval type.
|
|||
|
/// </summary>
|
|||
|
[
|
|||
|
SRCategory("CategoryAttributeInterval"),
|
|||
|
DefaultValue(DateTimeIntervalType.Auto),
|
|||
|
SRDescription("DescriptionAttributeAxisScaleSegment_IntervalType"),
|
|||
|
]
|
|||
|
public DateTimeIntervalType IntervalType
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
return this._intervalType;
|
|||
|
}
|
|||
|
set
|
|||
|
{
|
|||
|
// Axis interval properties must be set
|
|||
|
if(value == DateTimeIntervalType.NotSet)
|
|||
|
{
|
|||
|
this._intervalType = DateTimeIntervalType.Auto;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
_intervalType = value;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Axis segment interval offset type.
|
|||
|
/// </summary>
|
|||
|
[
|
|||
|
SRCategory("CategoryAttributeInterval"),
|
|||
|
DefaultValue(DateTimeIntervalType.Auto),
|
|||
|
SRDescription("DescriptionAttributeAxisScaleSegment_IntervalOffsetType"),
|
|||
|
]
|
|||
|
public DateTimeIntervalType IntervalOffsetType
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
return this._intervalOffsetType;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Object associated with axis scale segment.
|
|||
|
/// </summary>
|
|||
|
[
|
|||
|
SRCategory("CategoryAttributeMisc"),
|
|||
|
Browsable(false),
|
|||
|
DefaultValue(null),
|
|||
|
SRDescription("DescriptionAttributeAxisScaleSegment_Tag"),
|
|||
|
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
|
|||
|
SerializationVisibility(SerializationVisibility.Hidden),
|
|||
|
]
|
|||
|
public object Tag
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
return this._tag;
|
|||
|
}
|
|||
|
set
|
|||
|
{
|
|||
|
this._tag = value;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#endregion // Properties
|
|||
|
|
|||
|
#region Break Line Painting Methods
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Paints the axis break line.
|
|||
|
/// </summary>
|
|||
|
/// <param name="graph">Chart graphics to use.</param>
|
|||
|
/// <param name="nextSegment">Axis scale segment next to current.</param>
|
|||
|
internal void PaintBreakLine(ChartGraphics graph, AxisScaleSegment nextSegment)
|
|||
|
{
|
|||
|
// Get break line position
|
|||
|
RectangleF breakPosition = this.GetBreakLinePosition(graph, nextSegment);
|
|||
|
|
|||
|
// Get top line graphics path
|
|||
|
GraphicsPath breakLinePathTop = this.GetBreakLinePath(breakPosition, true);
|
|||
|
GraphicsPath breakLinePathBottom = null;
|
|||
|
|
|||
|
// Clear break line space using chart color behind the area
|
|||
|
if(breakPosition.Width > 0f && breakPosition.Height > 0f)
|
|||
|
{
|
|||
|
// Get bottom line graphics path
|
|||
|
breakLinePathBottom = this.GetBreakLinePath(breakPosition, false);
|
|||
|
|
|||
|
// Clear plotting area background
|
|||
|
using(GraphicsPath fillPath = new GraphicsPath())
|
|||
|
{
|
|||
|
// Create fill path out of top and bottom break lines
|
|||
|
fillPath.AddPath(breakLinePathTop, true);
|
|||
|
fillPath.Reverse();
|
|||
|
fillPath.AddPath(breakLinePathBottom, true);
|
|||
|
fillPath.CloseAllFigures();
|
|||
|
|
|||
|
// Use chart back color to fill the area
|
|||
|
using(Brush fillBrush = this.GetChartFillBrush(graph))
|
|||
|
{
|
|||
|
graph.FillPath(fillBrush, fillPath);
|
|||
|
|
|||
|
// Check if shadow exsits in chart area
|
|||
|
if( this.axis.ChartArea.ShadowOffset != 0 && !this.axis.ChartArea.ShadowColor.IsEmpty)
|
|||
|
{
|
|||
|
// Clear shadow
|
|||
|
RectangleF shadowPartRect = breakPosition;
|
|||
|
if( this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left )
|
|||
|
{
|
|||
|
shadowPartRect.Y += this.axis.ChartArea.ShadowOffset;
|
|||
|
shadowPartRect.Height -= this.axis.ChartArea.ShadowOffset;
|
|||
|
shadowPartRect.X = shadowPartRect.Right - 1;
|
|||
|
shadowPartRect.Width = this.axis.ChartArea.ShadowOffset + 2;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
shadowPartRect.X += this.axis.ChartArea.ShadowOffset;
|
|||
|
shadowPartRect.Width -= this.axis.ChartArea.ShadowOffset;
|
|||
|
shadowPartRect.Y = shadowPartRect.Bottom - 1;
|
|||
|
shadowPartRect.Height = this.axis.ChartArea.ShadowOffset + 2;
|
|||
|
}
|
|||
|
graph.FillRectangle(fillBrush, shadowPartRect);
|
|||
|
|
|||
|
// Draw new shadow
|
|||
|
using(GraphicsPath shadowPath = new GraphicsPath())
|
|||
|
{
|
|||
|
shadowPath.AddPath(breakLinePathTop, false);
|
|||
|
|
|||
|
// Define maximum size
|
|||
|
float size = this.axis.ChartArea.ShadowOffset;
|
|||
|
if( this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left )
|
|||
|
{
|
|||
|
size = Math.Min(size, breakPosition.Height);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
size = Math.Min(size, breakPosition.Width);
|
|||
|
}
|
|||
|
|
|||
|
// Define step to increase transperancy
|
|||
|
int transparencyStep = (int)(this.axis.ChartArea.ShadowColor.A / size);
|
|||
|
|
|||
|
// Set clip region to achieve spacing of the shadow
|
|||
|
// Start with the plotting rectangle position
|
|||
|
RectangleF clipRegion = graph.GetAbsoluteRectangle(this.axis.PlotAreaPosition.ToRectangleF());
|
|||
|
if( this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left )
|
|||
|
{
|
|||
|
clipRegion.X += this.axis.ChartArea.ShadowOffset;
|
|||
|
clipRegion.Width += this.axis.ChartArea.ShadowOffset;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
clipRegion.Y += this.axis.ChartArea.ShadowOffset;
|
|||
|
clipRegion.Height += this.axis.ChartArea.ShadowOffset;
|
|||
|
}
|
|||
|
graph.SetClip(graph.GetRelativeRectangle(clipRegion));
|
|||
|
|
|||
|
// Draw several lines to form shadow
|
|||
|
for(int index = 0; index < size; index ++)
|
|||
|
{
|
|||
|
using(Matrix newMatrix = new Matrix())
|
|||
|
{
|
|||
|
// Shift top break line by 1 pixel
|
|||
|
if( this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left )
|
|||
|
{
|
|||
|
newMatrix.Translate(0f, 1f);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
newMatrix.Translate(1f, 0f);
|
|||
|
}
|
|||
|
shadowPath.Transform(newMatrix);
|
|||
|
}
|
|||
|
|
|||
|
// Get line color
|
|||
|
Color color = Color.FromArgb(
|
|||
|
this.axis.ChartArea.ShadowColor.A - transparencyStep * index,
|
|||
|
this.axis.ChartArea.ShadowColor);
|
|||
|
|
|||
|
using(Pen shadowPen = new Pen(color, 1))
|
|||
|
{
|
|||
|
// Draw shadow
|
|||
|
graph.DrawPath(shadowPen, shadowPath);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
graph.ResetClip();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Draw Separator Line(s)
|
|||
|
if(this.axis.ScaleBreakStyle.BreakLineStyle != BreakLineStyle.None)
|
|||
|
{
|
|||
|
using(Pen pen = new Pen(this.axis.ScaleBreakStyle.LineColor, this.axis.ScaleBreakStyle.LineWidth))
|
|||
|
{
|
|||
|
// Set line style
|
|||
|
pen.DashStyle = graph.GetPenStyle(this.axis.ScaleBreakStyle.LineDashStyle);
|
|||
|
|
|||
|
// Draw break lines
|
|||
|
graph.DrawPath(pen, breakLinePathTop);
|
|||
|
if(breakPosition.Width > 0f && breakPosition.Height > 0f)
|
|||
|
{
|
|||
|
graph.DrawPath(pen, breakLinePathBottom);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Dispose break line paths
|
|||
|
breakLinePathTop.Dispose();
|
|||
|
breakLinePathTop = null;
|
|||
|
if(breakLinePathBottom != null)
|
|||
|
{
|
|||
|
breakLinePathBottom.Dispose();
|
|||
|
breakLinePathBottom = null;
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Get fill brush used to fill axis break lines spacing.
|
|||
|
/// </summary>
|
|||
|
/// <param name="graph">chart graphics.</param>
|
|||
|
/// <returns>Fill brush.</returns>
|
|||
|
private Brush GetChartFillBrush(ChartGraphics graph)
|
|||
|
{
|
|||
|
Chart chart = this.axis.ChartArea.Common.Chart;
|
|||
|
Brush brush = null;
|
|||
|
|
|||
|
if( chart.BackGradientStyle == GradientStyle.None )
|
|||
|
{
|
|||
|
brush = new SolidBrush(chart.BackColor);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// If a gradient type is set create a brush with gradient
|
|||
|
brush = graph.GetGradientBrush(new RectangleF(0, 0, chart.chartPicture.Width - 1, chart.chartPicture.Height - 1), chart.BackColor, chart.BackSecondaryColor, chart.BackGradientStyle);
|
|||
|
}
|
|||
|
|
|||
|
if( chart.BackHatchStyle != ChartHatchStyle.None )
|
|||
|
{
|
|||
|
brush = graph.GetHatchBrush( chart.BackHatchStyle, chart.BackColor, chart.BackSecondaryColor );
|
|||
|
}
|
|||
|
|
|||
|
if( chart.BackImage.Length > 0 &&
|
|||
|
chart.BackImageWrapMode != ChartImageWrapMode.Unscaled &&
|
|||
|
chart.BackImageWrapMode != ChartImageWrapMode.Scaled)
|
|||
|
{
|
|||
|
brush = graph.GetTextureBrush(chart.BackImage, chart.BackImageTransparentColor, chart.BackImageWrapMode, chart.BackColor );
|
|||
|
}
|
|||
|
|
|||
|
return brush;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Gets a path of the break line for specified position.
|
|||
|
/// </summary>
|
|||
|
/// <param name="breakLinePosition">Break line position.</param>
|
|||
|
/// <param name="top">Indicates if top or bottom break line path should be retrieved.</param>
|
|||
|
/// <returns>Graphics path with break line path.</returns>
|
|||
|
private GraphicsPath GetBreakLinePath(RectangleF breakLinePosition, bool top)
|
|||
|
{
|
|||
|
GraphicsPath path = new GraphicsPath();
|
|||
|
|
|||
|
if(this.axis.ScaleBreakStyle.BreakLineStyle == BreakLineStyle.Wave)
|
|||
|
{
|
|||
|
PointF[] points = null;
|
|||
|
int pointNumber = 0;
|
|||
|
if( this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left )
|
|||
|
{
|
|||
|
float startX = breakLinePosition.X;
|
|||
|
float endX = breakLinePosition.Right;
|
|||
|
float y = (top) ? breakLinePosition.Y : breakLinePosition.Bottom;
|
|||
|
pointNumber = ((int)(endX - startX) / 40) * 2 ;
|
|||
|
if(pointNumber < 2)
|
|||
|
{
|
|||
|
pointNumber = 2;
|
|||
|
}
|
|||
|
float step = (endX - startX) / pointNumber;
|
|||
|
points = new PointF[pointNumber + 1];
|
|||
|
for(int pointIndex = 1; pointIndex < pointNumber + 1; pointIndex++)
|
|||
|
{
|
|||
|
points[pointIndex] = new PointF(
|
|||
|
startX + pointIndex * step,
|
|||
|
y + ((pointIndex%2 == 0) ? -2f : 2f) );
|
|||
|
}
|
|||
|
|
|||
|
points[0] = new PointF(startX, y);
|
|||
|
points[points.Length - 1] = new PointF(endX, y);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
float startY = breakLinePosition.Y;
|
|||
|
float endY = breakLinePosition.Bottom;
|
|||
|
float x = (top) ? breakLinePosition.X : breakLinePosition.Right;
|
|||
|
pointNumber = ((int)(endY - startY) / 40) * 2 ;
|
|||
|
if(pointNumber < 2)
|
|||
|
{
|
|||
|
pointNumber = 2;
|
|||
|
}
|
|||
|
float step = (endY - startY) / pointNumber;
|
|||
|
points = new PointF[pointNumber + 1];
|
|||
|
for(int pointIndex = 1; pointIndex < pointNumber + 1; pointIndex++)
|
|||
|
{
|
|||
|
points[pointIndex] = new PointF(
|
|||
|
x + ((pointIndex%2 == 0) ? -2f : 2f),
|
|||
|
startY + pointIndex * step);
|
|||
|
}
|
|||
|
|
|||
|
points[0] = new PointF(x, startY);
|
|||
|
points[points.Length - 1] = new PointF(x, endY);
|
|||
|
}
|
|||
|
|
|||
|
path.AddCurve(points, 0, pointNumber, 0.8f);
|
|||
|
}
|
|||
|
else if(this.axis.ScaleBreakStyle.BreakLineStyle == BreakLineStyle.Ragged)
|
|||
|
{
|
|||
|
PointF[] points = null;
|
|||
|
Random rand = new Random(435657);
|
|||
|
if( this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left )
|
|||
|
{
|
|||
|
float startX = breakLinePosition.X;
|
|||
|
float endX = breakLinePosition.Right;
|
|||
|
float y = (top) ? breakLinePosition.Y : breakLinePosition.Bottom;
|
|||
|
float step = 10f;
|
|||
|
int pointNumber = (int)((endX - startX) / step);
|
|||
|
if(pointNumber < 2)
|
|||
|
{
|
|||
|
pointNumber = 2;
|
|||
|
}
|
|||
|
points = new PointF[pointNumber];
|
|||
|
|
|||
|
for(int pointIndex = 1; pointIndex < pointNumber - 1; pointIndex++)
|
|||
|
{
|
|||
|
points[pointIndex] = new PointF(
|
|||
|
startX + pointIndex * step,
|
|||
|
y + rand.Next(-3, 3) );
|
|||
|
}
|
|||
|
|
|||
|
points[0] = new PointF(startX, y);
|
|||
|
points[points.Length - 1] = new PointF(endX, y);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
float startY = breakLinePosition.Y;
|
|||
|
float endY = breakLinePosition.Bottom;
|
|||
|
float x = (top) ? breakLinePosition.X : breakLinePosition.Right;
|
|||
|
float step = 10f;
|
|||
|
int pointNumber = (int)((endY - startY) / step);
|
|||
|
if(pointNumber < 2)
|
|||
|
{
|
|||
|
pointNumber = 2;
|
|||
|
}
|
|||
|
points = new PointF[pointNumber];
|
|||
|
|
|||
|
for(int pointIndex = 1; pointIndex < pointNumber - 1; pointIndex++)
|
|||
|
{
|
|||
|
points[pointIndex] = new PointF(
|
|||
|
x + rand.Next(-3, 3),
|
|||
|
startY + pointIndex * step );
|
|||
|
}
|
|||
|
|
|||
|
points[0] = new PointF(x, startY);
|
|||
|
points[points.Length - 1] = new PointF(x, endY);
|
|||
|
}
|
|||
|
|
|||
|
path.AddLines(points);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if( this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left )
|
|||
|
{
|
|||
|
if(top)
|
|||
|
{
|
|||
|
path.AddLine(breakLinePosition.X, breakLinePosition.Y, breakLinePosition.Right, breakLinePosition.Y);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
path.AddLine(breakLinePosition.X, breakLinePosition.Bottom, breakLinePosition.Right, breakLinePosition.Bottom);
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if(top)
|
|||
|
{
|
|||
|
path.AddLine(breakLinePosition.X, breakLinePosition.Y, breakLinePosition.X, breakLinePosition.Bottom);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
path.AddLine(breakLinePosition.Right, breakLinePosition.Y, breakLinePosition.Right, breakLinePosition.Bottom);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
return path;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Gets position of the axis break line. Break line may be shown as a single
|
|||
|
/// line or two lines separated with a spacing.
|
|||
|
/// </summary>
|
|||
|
/// <param name="graph">Chart graphics.</param>
|
|||
|
/// <param name="nextSegment">Next segment reference.</param>
|
|||
|
/// <returns>Position of the axis break line in pixel coordinates.</returns>
|
|||
|
internal RectangleF GetBreakLinePosition(ChartGraphics graph, AxisScaleSegment nextSegment)
|
|||
|
{
|
|||
|
// Start with the plotting rectangle position
|
|||
|
RectangleF breakPosition = this.axis.PlotAreaPosition.ToRectangleF();
|
|||
|
|
|||
|
// Find maximum scale value of the current segment and minimuj of the next
|
|||
|
double from = this.axis.GetLinearPosition(nextSegment.ScaleMinimum);
|
|||
|
double to = this.axis.GetLinearPosition(this.ScaleMaximum);
|
|||
|
if( this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left )
|
|||
|
{
|
|||
|
breakPosition.Y = (float)Math.Min(from, to);
|
|||
|
breakPosition.Height = (float)Math.Max(from, to);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
breakPosition.X = (float)Math.Min(from, to);
|
|||
|
breakPosition.Width = (float)Math.Max(from, to);;
|
|||
|
}
|
|||
|
|
|||
|
// Convert to pixels
|
|||
|
breakPosition = Rectangle.Round(graph.GetAbsoluteRectangle(breakPosition));
|
|||
|
|
|||
|
// Add border width
|
|||
|
if( this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left )
|
|||
|
{
|
|||
|
breakPosition.Height = (float)Math.Abs(breakPosition.Y - breakPosition.Height);
|
|||
|
breakPosition.X -= this.axis.ChartArea.BorderWidth;
|
|||
|
breakPosition.Width += 2 * this.axis.ChartArea.BorderWidth;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
breakPosition.Width = (float)Math.Abs(breakPosition.X - breakPosition.Width);
|
|||
|
breakPosition.Y -= this.axis.ChartArea.BorderWidth;
|
|||
|
breakPosition.Height += 2 * this.axis.ChartArea.BorderWidth;
|
|||
|
}
|
|||
|
|
|||
|
return breakPosition;
|
|||
|
}
|
|||
|
|
|||
|
#endregion // Break Line Painting Methods
|
|||
|
|
|||
|
#region Helper Methods
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Gets segment scale position and size in relative coordinates.
|
|||
|
/// Method takes in consideration segment spacing and space required fro separatorType.
|
|||
|
/// </summary>
|
|||
|
/// <param name="plotAreaSize">Plotting area size in relative coordinates.</param>
|
|||
|
/// <param name="scalePosition">Returns scale position.</param>
|
|||
|
/// <param name="scaleSize">Returns scale size.</param>
|
|||
|
internal void GetScalePositionAndSize(double plotAreaSize, out double scalePosition, out double scaleSize)
|
|||
|
{
|
|||
|
scaleSize = (this.Size - this.Spacing) * (plotAreaSize / 100.0);
|
|||
|
scalePosition = this.Position * (plotAreaSize / 100.0);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Saves axis settings and set them from the current segment.
|
|||
|
/// </summary>
|
|||
|
internal void SetTempAxisScaleAndInterval()
|
|||
|
{
|
|||
|
// Save current axis settings
|
|||
|
if(_oldAxisSettings.Count == 0)
|
|||
|
{
|
|||
|
_oldAxisSettings.Push(this.axis.maximum);
|
|||
|
_oldAxisSettings.Push(this.axis.minimum);
|
|||
|
|
|||
|
_oldAxisSettings.Push(this.axis.majorGrid.interval);
|
|||
|
_oldAxisSettings.Push(this.axis.majorGrid.intervalType);
|
|||
|
_oldAxisSettings.Push(this.axis.majorGrid.intervalOffset);
|
|||
|
_oldAxisSettings.Push(this.axis.majorGrid.intervalOffsetType);
|
|||
|
|
|||
|
_oldAxisSettings.Push(this.axis.majorTickMark.interval);
|
|||
|
_oldAxisSettings.Push(this.axis.majorTickMark.intervalType);
|
|||
|
_oldAxisSettings.Push(this.axis.majorTickMark.intervalOffset);
|
|||
|
_oldAxisSettings.Push(this.axis.majorTickMark.intervalOffsetType);
|
|||
|
|
|||
|
_oldAxisSettings.Push(this.axis.LabelStyle.interval);
|
|||
|
_oldAxisSettings.Push(this.axis.LabelStyle.intervalType);
|
|||
|
_oldAxisSettings.Push(this.axis.LabelStyle.intervalOffset);
|
|||
|
_oldAxisSettings.Push(this.axis.LabelStyle.intervalOffsetType);
|
|||
|
}
|
|||
|
|
|||
|
// Copy settings from the segment into the axis
|
|||
|
this.axis.maximum = this.ScaleMaximum;
|
|||
|
this.axis.minimum = this.ScaleMinimum;
|
|||
|
|
|||
|
this.axis.majorGrid.interval = this.Interval;
|
|||
|
this.axis.majorGrid.intervalType = this.IntervalType;
|
|||
|
this.axis.majorGrid.intervalOffset = this.IntervalOffset;
|
|||
|
this.axis.majorGrid.intervalOffsetType = this.IntervalOffsetType;
|
|||
|
|
|||
|
this.axis.majorTickMark.interval = this.Interval;
|
|||
|
this.axis.majorTickMark.intervalType = this.IntervalType;
|
|||
|
this.axis.majorTickMark.intervalOffset = this.IntervalOffset;
|
|||
|
this.axis.majorTickMark.intervalOffsetType = this.IntervalOffsetType;
|
|||
|
|
|||
|
this.axis.LabelStyle.interval = this.Interval;
|
|||
|
this.axis.LabelStyle.intervalType = this.IntervalType;
|
|||
|
this.axis.LabelStyle.intervalOffset = this.IntervalOffset;
|
|||
|
this.axis.LabelStyle.intervalOffsetType = this.IntervalOffsetType;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Restore previously saved axis settings.
|
|||
|
/// </summary>
|
|||
|
internal void RestoreAxisScaleAndInterval()
|
|||
|
{
|
|||
|
if(_oldAxisSettings.Count > 0)
|
|||
|
{
|
|||
|
this.axis.LabelStyle.intervalOffsetType = (DateTimeIntervalType)_oldAxisSettings.Pop();
|
|||
|
this.axis.LabelStyle.intervalOffset = (double)_oldAxisSettings.Pop();
|
|||
|
this.axis.LabelStyle.intervalType = (DateTimeIntervalType)_oldAxisSettings.Pop();
|
|||
|
this.axis.LabelStyle.interval = (double)_oldAxisSettings.Pop();
|
|||
|
|
|||
|
this.axis.majorTickMark.intervalOffsetType = (DateTimeIntervalType)_oldAxisSettings.Pop();
|
|||
|
this.axis.majorTickMark.intervalOffset = (double)_oldAxisSettings.Pop();
|
|||
|
this.axis.majorTickMark.intervalType = (DateTimeIntervalType)_oldAxisSettings.Pop();
|
|||
|
this.axis.majorTickMark.interval = (double)_oldAxisSettings.Pop();
|
|||
|
|
|||
|
this.axis.majorGrid.intervalOffsetType = (DateTimeIntervalType)_oldAxisSettings.Pop();
|
|||
|
this.axis.majorGrid.intervalOffset = (double)_oldAxisSettings.Pop();
|
|||
|
this.axis.majorGrid.intervalType = (DateTimeIntervalType)_oldAxisSettings.Pop();
|
|||
|
this.axis.majorGrid.interval = (double)_oldAxisSettings.Pop();
|
|||
|
|
|||
|
this.axis.minimum = (double)_oldAxisSettings.Pop();
|
|||
|
this.axis.maximum = (double)_oldAxisSettings.Pop();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#endregion // Helper Methods
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// <b>AxisScaleSegmentCollection</b> is a class that stores collection of axis segments.
|
|||
|
/// </summary>
|
|||
|
[
|
|||
|
SRDescription("DescriptionAttributeAxisScaleSegmentCollection_AxisScaleSegmentCollection"),
|
|||
|
]
|
|||
|
internal class AxisScaleSegmentCollection : CollectionBase
|
|||
|
{
|
|||
|
#region Fields
|
|||
|
|
|||
|
// Axis this segment collection belongs to.
|
|||
|
private Axis _axis = null;
|
|||
|
|
|||
|
// Segment which is always used to convert scale values.
|
|||
|
// This value is set tmporarly when only one segment has
|
|||
|
// to handle all the values.
|
|||
|
private AxisScaleSegment _enforcedSegment = null;
|
|||
|
|
|||
|
// Indicates that values allowed to be outside of the scale segment.
|
|||
|
// Otherwise they will be rounded to Min and Max values.
|
|||
|
internal bool AllowOutOfScaleValues = false;
|
|||
|
|
|||
|
#endregion // Fields
|
|||
|
|
|||
|
#region Construction and Initialization
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Default public constructor.
|
|||
|
/// </summary>
|
|||
|
/// <remarks>
|
|||
|
/// This constructor is for internal use and should not be part of documentation.
|
|||
|
/// </remarks>
|
|||
|
public AxisScaleSegmentCollection()
|
|||
|
{
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Default public constructor.
|
|||
|
/// </summary>
|
|||
|
/// <remarks>
|
|||
|
/// This constructor is for internal use and should not be part of documentation.
|
|||
|
/// </remarks>
|
|||
|
/// <param name="axis">
|
|||
|
/// Chart axis this collection belongs to
|
|||
|
/// </param>
|
|||
|
internal AxisScaleSegmentCollection(Axis axis)
|
|||
|
{
|
|||
|
this._axis = axis;
|
|||
|
}
|
|||
|
|
|||
|
#endregion // Construction and Initialization
|
|||
|
|
|||
|
#region Indexer
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Axis scale segment collection indexer.
|
|||
|
/// </summary>
|
|||
|
/// <remarks>
|
|||
|
/// The <b>AxisScaleSegment</b> object index can be provided as a parameter. Returns the <see cref="AxisScaleSegment"/> object.
|
|||
|
/// </remarks>
|
|||
|
[
|
|||
|
SRDescription("DescriptionAttributeAxisScaleSegmentCollection_Item"),
|
|||
|
]
|
|||
|
public AxisScaleSegment this[int index]
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
return (AxisScaleSegment)this.List[(int)index];
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#endregion // Indexer
|
|||
|
|
|||
|
#region Collection Add and Insert methods
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Adds a segment to the end of the collection.
|
|||
|
/// </summary>
|
|||
|
/// <param name="segment">
|
|||
|
/// <see cref="AxisScaleSegment"/> object to add.
|
|||
|
/// </param>
|
|||
|
/// <returns>
|
|||
|
/// Index of the newly added object.
|
|||
|
/// </returns>
|
|||
|
public int Add(AxisScaleSegment segment)
|
|||
|
{
|
|||
|
return this.List.Add(segment);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
#endregion // Collection Add and Insert methods
|
|||
|
|
|||
|
#region Items Inserting and Removing Notification methods
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// After new item inserted.
|
|||
|
/// </summary>
|
|||
|
/// <param name="index">Item index.</param>
|
|||
|
/// <param name="value">Item object.</param>
|
|||
|
/// <remarks>
|
|||
|
/// This is an internal method and should not be part of the documentation.
|
|||
|
/// </remarks>
|
|||
|
protected override void OnInsertComplete(int index, object value)
|
|||
|
{
|
|||
|
((AxisScaleSegment)value).axis = this._axis;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// After items is set.
|
|||
|
/// </summary>
|
|||
|
/// <param name="index">The zero-based index at which oldValue can be found.</param>
|
|||
|
/// <param name="oldValue">The value to replace with newValue.</param>
|
|||
|
/// <param name="newValue">The new value of the element at index.</param>
|
|||
|
/// <remarks>
|
|||
|
/// This is an internal method and should not be part of the documentation.
|
|||
|
/// </remarks>
|
|||
|
protected override void OnSetComplete(int index, object oldValue, object newValue)
|
|||
|
{
|
|||
|
((AxisScaleSegment)newValue).axis = this._axis;
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Helper Methods
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Ensures that specified axis scale segment is used for all coordinate transformations.
|
|||
|
/// Set tot NULL to reset.
|
|||
|
/// </summary>
|
|||
|
/// <param name="segment"></param>
|
|||
|
internal void EnforceSegment(AxisScaleSegment segment)
|
|||
|
{
|
|||
|
this._enforcedSegment = segment;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Find axis scale segment that should be used to translate axis value to relative coordinates.
|
|||
|
/// </summary>
|
|||
|
/// <param name="axisValue">Axis value to convert.</param>
|
|||
|
/// <returns>Scale segment to use for the convertion.</returns>
|
|||
|
public AxisScaleSegment FindScaleSegmentForAxisValue(double axisValue)
|
|||
|
{
|
|||
|
// Check if no segments defined
|
|||
|
if(this.List.Count == 0)
|
|||
|
{
|
|||
|
return null;
|
|||
|
}
|
|||
|
|
|||
|
// Check if segment enforcment is enabled
|
|||
|
if(_enforcedSegment != null)
|
|||
|
{
|
|||
|
return _enforcedSegment;
|
|||
|
}
|
|||
|
|
|||
|
// Iterate through all segments
|
|||
|
for(int index = 0; index < this.Count; index++)
|
|||
|
{
|
|||
|
if(axisValue < this[index].ScaleMinimum)
|
|||
|
{
|
|||
|
if(index == 0)
|
|||
|
{
|
|||
|
return this[index];
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Find the segment which is "closer" to the value
|
|||
|
if( Math.Abs(this[index].ScaleMinimum - axisValue) < Math.Abs(axisValue - this[index - 1].ScaleMaximum))
|
|||
|
{
|
|||
|
return this[index];
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
return this[index - 1];
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if(axisValue <= this[index].ScaleMaximum)
|
|||
|
{
|
|||
|
return this[index];
|
|||
|
}
|
|||
|
else if(index == this.Count - 1)
|
|||
|
{
|
|||
|
return this[index];
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return null;
|
|||
|
}
|
|||
|
|
|||
|
#endregion // Helper Methods
|
|||
|
}
|
|||
|
}
|
|||
|
|