// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. // // Purpose: Axis related properties and methods. Axis class gives // information to Common.Chart series about // position in the Common.Chart area and keeps all necessary // information about axes. // 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.Text; using System.Drawing.Drawing2D; using System.Diagnostics.CodeAnalysis; using FastReport.DataVisualization.Charting; using FastReport.DataVisualization.Charting.Data; using FastReport.DataVisualization.Charting.ChartTypes; using FastReport.DataVisualization.Charting.Utilities; using FastReport.DataVisualization.Charting.Borders3D; namespace FastReport.DataVisualization.Charting { using Point = System.Drawing.Point; #region Axis name enumeration /// /// An enumeration of auto-fitting styles of the axis labels. /// [Flags] public enum LabelAutoFitStyles { /// /// No auto-fitting. /// None = 0, /// /// Allow font size increasing. /// IncreaseFont = 1, /// /// Allow font size decreasing. /// DecreaseFont = 2, /// /// Allow using staggered labels. /// StaggeredLabels = 4, /// /// Allow changing labels angle using values of 0, 30, 60 and 90 degrees. /// LabelsAngleStep30 = 8, /// /// Allow changing labels angle using values of 0, 45, 90 degrees. /// LabelsAngleStep45 = 16, /// /// Allow changing labels angle using values of 0 and 90 degrees. /// LabelsAngleStep90 = 32, /// /// Allow replacing spaces with the new line character. /// WordWrap = 64, } /// /// An enumeration of axis names. /// public enum AxisName { /// /// Primary X Axis. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "X")] X = 0, /// /// Primary Y Axis. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Y")] Y = 1, /// /// Secondary X Axis. /// X2 = 2, /// /// Secondary Y Axis. /// Y2 = 3 } #endregion /// /// The Axis class gives information to the Common.Chart series /// about positions in the Common.Chart area and keeps all of /// the data about the axis. /// [ SRDescription("DescriptionAttributeAxis_Axis"), DefaultProperty("Enabled"), ] public partial class Axis : ChartNamedElement { #region Axis fields /// /// Plot area position /// internal ElementPosition PlotAreaPosition; // This field synchronies Store and Reset temporary values private bool _storeValuesEnabled = true; private FontCache _fontCache = new FontCache(); private Font _titleFont; private Color _titleForeColor = Color.Black; private StringAlignment _titleAlignment = StringAlignment.Center; private string _title = ""; private int _lineWidth = 1; private ChartDashStyle _lineDashStyle = ChartDashStyle.Solid; private Color _lineColor = Color.Black; private bool _isLabelAutoFit = true; private AxisArrowStyle _arrowStyle = AxisArrowStyle.None; private StripLinesCollection _stripLines = null; private bool _isMarksNextToAxis = true; // Default text orientation private TextOrientation _textOrientation = TextOrientation.Auto; // Size of the axis elements in percentage internal float titleSize = 0F; internal float labelSize = 0F; internal float labelNearOffset = 0F; internal float labelFarOffset = 0F; internal float unRotatedLabelSize = 0F; internal float markSize = 0F; internal float scrollBarSize = 0F; internal float totlaGroupingLabelsSize = 0F; internal float[] groupingLabelSizes = null; internal float totlaGroupingLabelsSizeAdjustment = 0f; private LabelAutoFitStyles _labelAutoFitStyle = LabelAutoFitStyles.DecreaseFont | LabelAutoFitStyles.IncreaseFont | LabelAutoFitStyles.LabelsAngleStep30 | LabelAutoFitStyles.StaggeredLabels | LabelAutoFitStyles.WordWrap; // Auto calculated font for labels internal Font autoLabelFont = null; internal int autoLabelAngle = -1000; internal int autoLabelOffset = -1; // Labels auto fitting constants private float _aveLabelFontSize = 10F; private float _minLabelFontSize = 5F; // Determines maximum label size of the chart area. private float _maximumAutoSize = 75f; // Chart title position rectangle private RectangleF _titlePosition = RectangleF.Empty; // Element spacing size internal const float elementSpacing = 1F; // Maximum total size of the axis's elements in percentage private const float maxAxisElementsSize = 75F; // Maximum size of the axis title in percentage private const float maxAxisTitleSize = 20F; // Maximum size of the axis second row of labels in percentage // of the total labels size private const float maxAxisLabelRow2Size = 45F; // Maximum size of the axis tick marks in percentage private const float maxAxisMarkSize = 20F; // Minimum cached value from data series. internal double minimumFromData = double.NaN; // Maximum cached value from data series. internal double maximumFromData = double.NaN; // Flag, which tells to Set Data method to take, again values from // data source and not to use cached values. internal bool refreshMinMaxFromData = true; // Flag, which tells to Set Data method to take, again values from // data source and not to use cached values. internal int numberOfPointsInAllSeries = 0; // Original axis scaleView position private double _originalViewPosition = double.NaN; /// /// Indicates that isInterlaced strip lines will be displayed for the axis. /// private bool _isInterlaced = false; /// /// Color used to draw isInterlaced strip lines for the axis. /// private Color _interlacedColor = Color.Empty; /// /// Axis interval offset. /// private double _intervalOffset = 0; /// /// Axis interval. /// internal double interval = 0; /// /// Axis interval units type. /// internal DateTimeIntervalType intervalType = DateTimeIntervalType.Auto; /// /// Axis interval offset units type. /// internal DateTimeIntervalType intervalOffsetType = DateTimeIntervalType.Auto; /// /// Minimum font size that can be used by the labels auto-fitting algorithm. /// internal int labelAutoFitMinFontSize = 6; /// /// Maximum font size that can be used by the labels auto-fitting algorithm. /// internal int labelAutoFitMaxFontSize = 10; /// /// Axis tooltip /// private string _toolTip = String.Empty; /// /// Axis HREF /// private string _url = String.Empty; #endregion #region Axis constructor and initialization /// /// Default constructor of Axis. /// public Axis() : base(null, GetName(AxisName.X)) { Initialize(AxisName.X); } /// /// Axis constructor. /// /// The chart area the axis belongs to. /// The type of the axis. public Axis(ChartArea chartArea, AxisName axisTypeName) : base(chartArea, GetName(axisTypeName)) { Initialize(axisTypeName); } /// /// Initialize axis class /// /// Name of the axis type. private void Initialize(AxisName axisTypeName) { // DT: Axis could be already created. Don't recreate new labelstyle and other objects. // Initialize axis labels if (labelStyle == null) { labelStyle = new LabelStyle(this); } if (_customLabels == null) { _customLabels = new CustomLabelsCollection(this); } if (_scaleView == null) { // Create axis data scaleView object _scaleView = new AxisScaleView(this); } if (scrollBar == null) { // Create axis croll bar class scrollBar = new AxisScrollBar(this); } this.axisType = axisTypeName; // Create grid & tick marks objects if (minorTickMark == null) { minorTickMark = new TickMark(this, false); } if (majorTickMark == null) { majorTickMark = new TickMark(this, true); majorTickMark.Interval = double.NaN; majorTickMark.IntervalOffset = double.NaN; majorTickMark.IntervalType = DateTimeIntervalType.NotSet; majorTickMark.IntervalOffsetType = DateTimeIntervalType.NotSet; } if (minorGrid == null) { minorGrid = new Grid(this, false); } if (majorGrid == null) { majorGrid = new Grid(this, true); majorGrid.Interval = double.NaN; majorGrid.IntervalOffset = double.NaN; majorGrid.IntervalType = DateTimeIntervalType.NotSet; majorGrid.IntervalOffsetType = DateTimeIntervalType.NotSet; } if (this._stripLines == null) { this._stripLines = new StripLinesCollection(this); } if (_titleFont == null) { _titleFont = _fontCache.DefaultFont; } #if SUBAXES if(this.subAxes == null) { this.subAxes = new SubAxisCollection(this); } #endif // Initialize axis scroll bar class this.ScrollBar.Initialize(); // Create collection of scale segments if (this.scaleSegments == null) { this.scaleSegments = new AxisScaleSegmentCollection(this); } // Create scale break style if (this.axisScaleBreakStyle == null) { this.axisScaleBreakStyle = new AxisScaleBreakStyle(this); } } /// /// Initialize axis class /// /// Chart area that the axis belongs. /// Axis type. internal void Initialize(ChartArea chartArea, AxisName axisTypeName) { this.Initialize(axisTypeName); this.Parent = chartArea; this.Name = GetName(axisTypeName); } /// /// Set Axis Name /// internal static string GetName(AxisName axisName) { // Set axis name. // NOTE: Strings below should neber be localized. Name properties in the chart are never localized // and represent consisten object name in all locales. switch (axisName) { case (AxisName.X): return "X axis"; case (AxisName.Y): return "Y (Value) axis"; case (AxisName.X2): return "Secondary X axis"; case (AxisName.Y2): return "Secondary Y (Value) axis"; } return null; } #endregion #region Axis properies // Internal internal ChartArea ChartArea { get { return Parent as ChartArea; } } /// /// Text orientation. /// [ SRCategory("CategoryAttributeTitle"), Bindable(true), DefaultValue(TextOrientation.Auto), SRDescription("DescriptionAttribute_TextOrientation"), NotifyParentPropertyAttribute(true), ] public TextOrientation TextOrientation { get { return this._textOrientation; } set { this._textOrientation = value; this.Invalidate(); CallOnModifing(); } } /// /// Returns sub-axis name. /// virtual internal string SubAxisName { get { return string.Empty; } } #if SUBAXES /// /// Indicates if this axis object present the main or sub axis. /// virtual internal bool IsSubAxis { get { return false; } } private SubAxisCollection subAxes = null; /// /// Sub-axes collection. /// [ SRCategory("CategoryAttributeSubAxes"), Bindable(true), SRDescription("DescriptionAttributeSubAxes"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), Editor(Editors.ChartCollectionEditor.Editor, Editors.ChartCollectionEditor.Base) ] virtual public SubAxisCollection SubAxes { get { return this.subAxes; } } #endif // SUBAXES /// /// Gets or sets a flag which indicates whether interlaced strip lines will be displayed for the axis. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(false), SRDescription("DescriptionAttributeInterlaced"), NotifyParentPropertyAttribute(true), ] public bool IsInterlaced { get { return _isInterlaced; } set { _isInterlaced = value; this.Invalidate(); CallOnModifing(); } } /// /// Gets or sets the color used to draw interlaced strip lines for the axis. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(typeof(Color), ""), SRDescription("DescriptionAttributeInterlacedColor"), NotifyParentPropertyAttribute(true), TypeConverter(typeof(ColorConverter)), #if DESIGNER Editor(typeof(ChartColorEditor), typeof(UITypeEditor)) #endif ] public Color InterlacedColor { get { return _interlacedColor; } set { _interlacedColor = value; this.Invalidate(); CallOnModifing(); } } /// /// Axis name. This field is reserved for internal use only. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), Browsable(false), DefaultValue(""), SRDescription("DescriptionAttributeAxis_Name"), DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden), SerializationVisibilityAttribute(SerializationVisibility.Hidden) ] public override string Name { get { return base.Name; } set { base.Name = value; CallOnModifing(); } } /// /// Axis name. This field is reserved for internal use only. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), Browsable(false), DefaultValue(""), SRDescription("DescriptionAttributeType"), DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden), SerializationVisibilityAttribute(SerializationVisibility.Hidden) ] virtual public AxisName AxisName { get { return axisType; } } /// /// Gets or sets the arrow style used for the axis. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(AxisArrowStyle.None), NotifyParentPropertyAttribute(true), SRDescription("DescriptionAttributeArrows"), ] public AxisArrowStyle ArrowStyle { get { return _arrowStyle; } set { _arrowStyle = value; this.Invalidate(); CallOnModifing(); } } /// /// Gets or sets the properties used for the major gridlines. /// [ SRCategory("CategoryAttributeGridTickMarks"), Bindable(true), NotifyParentPropertyAttribute(true), SRDescription("DescriptionAttributeMajorGrid"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), TypeConverter(typeof(NoNameExpandableObjectConverter)) ] public Grid MajorGrid { get { return majorGrid; } set { majorGrid = value; majorGrid.Axis = this; majorGrid.majorGridTick = true; if (!majorGrid.intervalChanged) majorGrid.Interval = double.NaN; if (!majorGrid.intervalOffsetChanged) majorGrid.IntervalOffset = double.NaN; if (!majorGrid.intervalTypeChanged) majorGrid.IntervalType = DateTimeIntervalType.NotSet; if (!majorGrid.intervalOffsetTypeChanged) majorGrid.IntervalOffsetType = DateTimeIntervalType.NotSet; this.Invalidate(); CallOnModifing(); } } /// /// Gets or sets the properties used for the minor gridlines. /// [ SRCategory("CategoryAttributeGridTickMarks"), Bindable(true), NotifyParentPropertyAttribute(true), SRDescription("DescriptionAttributeMinorGrid"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), TypeConverter(typeof(NoNameExpandableObjectConverter)) ] public Grid MinorGrid { get { return minorGrid; } set { minorGrid = value; minorGrid.Initialize(this, false); this.Invalidate(); CallOnModifing(); } } /// /// Gets or sets the properties used for the major tick marks. /// [ SRCategory("CategoryAttributeGridTickMarks"), Bindable(true), NotifyParentPropertyAttribute(true), SRDescription("DescriptionAttributeMajorTickMark"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), TypeConverter(typeof(NoNameExpandableObjectConverter)) ] public TickMark MajorTickMark { get { return majorTickMark; } set { majorTickMark = value; majorTickMark.Axis = this; majorTickMark.majorGridTick = true; if (!majorTickMark.intervalChanged) majorTickMark.Interval = double.NaN; if (!majorTickMark.intervalOffsetChanged) majorTickMark.IntervalOffset = double.NaN; if (!majorTickMark.intervalTypeChanged) majorTickMark.IntervalType = DateTimeIntervalType.NotSet; if (!majorTickMark.intervalOffsetTypeChanged) majorTickMark.IntervalOffsetType = DateTimeIntervalType.NotSet; this.Invalidate(); CallOnModifing(); } } /// /// Gets or sets the properties used for the minor tick marks. /// [ SRCategory("CategoryAttributeGridTickMarks"), Bindable(true), NotifyParentPropertyAttribute(true), SRDescription("DescriptionAttributeMinorTickMark"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), TypeConverter(typeof(NoNameExpandableObjectConverter)) ] public TickMark MinorTickMark { get { return minorTickMark; } set { minorTickMark = value; minorTickMark.Initialize(this, false); this.Invalidate(); CallOnModifing(); } } /// /// Gets or sets a flag which indicates whether auto-fitting of labels is enabled. /// [ SRCategory("CategoryAttributeLabels"), Bindable(true), DefaultValue(true), SRDescription("DescriptionAttributeLabelsAutoFit"), NotifyParentPropertyAttribute(true), RefreshPropertiesAttribute(RefreshProperties.All) ] public bool IsLabelAutoFit { get { return _isLabelAutoFit; } set { _isLabelAutoFit = value; this.Invalidate(); CallOnModifing(); } } /// /// Gets or sets the minimum font size that can be used by /// the label auto-fitting algorithm. /// [ SRCategory("CategoryAttributeLabels"), Bindable(true), DefaultValue(6), SRDescription("DescriptionAttributeLabelsAutoFitMinFontSize"), NotifyParentPropertyAttribute(true), RefreshPropertiesAttribute(RefreshProperties.All) ] public int LabelAutoFitMinFontSize { get { return this.labelAutoFitMinFontSize; } set { // Font size cannot be less than 5 if(value < 5) { throw (new InvalidOperationException(SR.ExceptionAxisLabelsAutoFitMinFontSizeValueInvalid)); } this.labelAutoFitMinFontSize = value; this.Invalidate(); CallOnModifing(); } } /// /// Gets or sets the maximum font size that can be used by /// the label auto-fitting algorithm. /// [ SRCategory("CategoryAttributeLabels"), Bindable(true), DefaultValue(10), SRDescription("DescriptionAttributeLabelsAutoFitMaxFontSize"), NotifyParentPropertyAttribute(true), RefreshPropertiesAttribute(RefreshProperties.All) ] public int LabelAutoFitMaxFontSize { get { return this.labelAutoFitMaxFontSize; } set { // Font size cannot be less than 5 if(value < 5) { throw (new InvalidOperationException(SR.ExceptionAxisLabelsAutoFitMaxFontSizeInvalid)); } this.labelAutoFitMaxFontSize = value; this.Invalidate(); CallOnModifing(); } } /// /// Gets or sets the auto-fitting style used for the labels. /// IsLabelAutoFit must be set to true. /// [ SRCategory("CategoryAttributeLabels"), Bindable(true), DefaultValue(LabelAutoFitStyles.DecreaseFont | LabelAutoFitStyles.IncreaseFont | LabelAutoFitStyles.LabelsAngleStep30 | LabelAutoFitStyles.StaggeredLabels | LabelAutoFitStyles.WordWrap), SRDescription("DescriptionAttributeLabelsAutoFitStyle"), NotifyParentPropertyAttribute(true), #if DESIGNER Editor(typeof(FlagsEnumUITypeEditor), typeof(UITypeEditor)) #endif ] public LabelAutoFitStyles LabelAutoFitStyle { get { return this._labelAutoFitStyle; } set { this._labelAutoFitStyle = value; this.Invalidate(); CallOnModifing(); } } /// /// Gets or sets a flag which indicates whether /// tick marks and labels move with the axis when /// the crossing value changes. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(true), SRDescription("DescriptionAttributeMarksNextToAxis"), NotifyParentPropertyAttribute(true), ] virtual public bool IsMarksNextToAxis { get { return _isMarksNextToAxis; } set { _isMarksNextToAxis = value; this.Invalidate(); CallOnModifing(); } } /// /// Gets or sets the axis title. /// [ SRCategory("CategoryAttributeTitle"), Bindable(true), DefaultValue(""), SRDescription("DescriptionAttributeTitle6"), NotifyParentPropertyAttribute(true), ] public string Title { get { return _title; } set { _title = value; this.Invalidate(); CallOnModifing(); } } /// /// Gets or sets the color of the axis title. /// [ SRCategory("CategoryAttributeTitle"), Bindable(true), DefaultValue(typeof(Color), "Black"), SRDescription("DescriptionAttributeTitleColor"), NotifyParentPropertyAttribute(true), TypeConverter(typeof(ColorConverter)), #if DESIGNER Editor(typeof(ChartColorEditor), typeof(UITypeEditor)) #endif ] public Color TitleForeColor { get { return _titleForeColor; } set { _titleForeColor = value; this.Invalidate(); CallOnModifing(); } } /// /// Gets or sets the alignment of the axis title. /// [ SRCategory("CategoryAttributeTitle"), Bindable(true), DefaultValue(typeof(StringAlignment), "Center"), SRDescription("DescriptionAttributeTitleAlignment"), NotifyParentPropertyAttribute(true), ] public StringAlignment TitleAlignment { get { return _titleAlignment; } set { _titleAlignment = value; this.Invalidate(); CallOnModifing(); } } /// /// Gets or sets the font used for the axis title. /// [ SRCategory("CategoryAttributeTitle"), Bindable(true), DefaultValue(typeof(Font), "Microsoft Sans Serif, 8pt"), SRDescription("DescriptionAttributeTitleFont"), NotifyParentPropertyAttribute(true), ] public Font TitleFont { get { return _titleFont; } set { _titleFont = value; this.Invalidate(); CallOnModifing(); } } /// /// Gets or sets the line color of the axis. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(typeof(Color), "Black"), SRDescription("DescriptionAttributeLineColor"), NotifyParentPropertyAttribute(true), TypeConverter(typeof(ColorConverter)), #if DESIGNER Editor(typeof(ChartColorEditor), typeof(UITypeEditor)) #endif ] public Color LineColor { get { return _lineColor; } set { _lineColor = value; this.Invalidate(); CallOnModifing(); } } /// /// Gets or sets the line width of the axis. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(1), SRDescription("DescriptionAttributeLineWidth"), NotifyParentPropertyAttribute(true), ] public int LineWidth { get { return _lineWidth; } set { if (value < 0) { throw (new ArgumentOutOfRangeException("value", SR.ExceptionAxisWidthIsNegative)); } _lineWidth = value; this.Invalidate(); CallOnModifing(); } } /// /// Gets or sets the line style of the axis. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(ChartDashStyle.Solid), SRDescription("DescriptionAttributeLineDashStyle"), NotifyParentPropertyAttribute(true), ] public ChartDashStyle LineDashStyle { get { return _lineDashStyle; } set { _lineDashStyle = value; this.Invalidate(); CallOnModifing(); } } /// /// The collection of strip lines of the axis. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), SRDescription("DescriptionAttributeStripLines"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), #if DESIGNER Editor(typeof(ChartCollectionEditor), typeof(UITypeEditor)) #endif ] public StripLinesCollection StripLines { get { return _stripLines; } } /// /// Gets or sets the maximum size (in percentage) of the axis used in the automatic layout algorithm. /// /// /// This property determines the maximum size of the axis, measured as a percentage of the chart area. /// [ SRCategory("CategoryAttributeLabels"), DefaultValue(75f), SRDescription("DescriptionAttributeAxis_MaxAutoSize"), ] public float MaximumAutoSize { get { return this._maximumAutoSize; } set { if (value < 0f || value > 100f) { throw (new ArgumentOutOfRangeException("value", SR.ExceptionValueMustBeInRange("MaximumAutoSize", "0", "100"))); } this._maximumAutoSize = value; this.Invalidate(); CallOnModifing(); } } #endregion #region IMapAreaAttributes Properties implementation /// /// Tooltip of the axis. /// [ SRCategory("CategoryAttributeMapArea"), Bindable(true), SRDescription("DescriptionAttributeToolTip"), DefaultValue(""), ] public string ToolTip { set { this._toolTip = value; } get { return this._toolTip; } } #endregion #region Axis Interavl properies /// /// Axis interval size. /// [ SRCategory("CategoryAttributeInterval"), Bindable(true), DefaultValue(0.0), SRDescription("DescriptionAttributeInterval4"), RefreshPropertiesAttribute(RefreshProperties.All), TypeConverter(typeof(AxisIntervalValueConverter)), ] public double Interval { get { return interval; } set { // Axis interval properties must be set if (double.IsNaN(value)) { interval = 0; } else { interval = value; } // Reset initial values majorGrid.interval = tempMajorGridInterval; majorTickMark.interval = tempMajorTickMarkInterval; minorGrid.interval = tempMinorGridInterval; minorTickMark.interval = tempMinorTickMarkInterval; labelStyle.interval = tempLabelInterval; this.Invalidate(); CallOnModifing(); } } /// /// Axis interval offset. /// [ SRCategory("CategoryAttributeInterval"), Bindable(true), DefaultValue(0.0), SRDescription("DescriptionAttributeIntervalOffset6"), RefreshPropertiesAttribute(RefreshProperties.All), TypeConverter(typeof(AxisIntervalValueConverter)) ] public double IntervalOffset { get { return _intervalOffset; } set { // Axis interval properties must be set if (double.IsNaN(value)) { _intervalOffset = 0; } else { _intervalOffset = value; } this.Invalidate(); CallOnModifing(); } } /// /// Axis interval type. /// [ SRCategory("CategoryAttributeInterval"), Bindable(true), DefaultValue(DateTimeIntervalType.Auto), SRDescription("DescriptionAttributeIntervalType4"), RefreshPropertiesAttribute(RefreshProperties.All) ] public DateTimeIntervalType IntervalType { get { return intervalType; } set { // Axis interval properties must be set if (value == DateTimeIntervalType.NotSet) { intervalType = DateTimeIntervalType.Auto; } else { intervalType = value; } // Reset initial values majorGrid.intervalType = tempGridIntervalType; majorTickMark.intervalType = tempTickMarkIntervalType; labelStyle.intervalType = tempLabelIntervalType; this.Invalidate(); CallOnModifing(); } } /// /// Axis interval offset type. /// [ SRCategory("CategoryAttributeInterval"), Bindable(true), DefaultValue(DateTimeIntervalType.Auto), SRDescription("DescriptionAttributeIntervalOffsetType4"), RefreshPropertiesAttribute(RefreshProperties.All) ] public DateTimeIntervalType IntervalOffsetType { get { return intervalOffsetType; } set { // Axis interval properties must be set if (value == DateTimeIntervalType.NotSet) { intervalOffsetType = DateTimeIntervalType.Auto; } else { intervalOffsetType = value; } this.Invalidate(); CallOnModifing(); } } #endregion #region Axis painting methods /// /// Checks if Common.Chart axis title is drawn vertically. /// Note: From the drawing perspective stacked text orientation is not vertical. /// /// True if text is vertical. private bool IsTextVertical { get { TextOrientation currentTextOrientation = this.GetTextOrientation(); return currentTextOrientation == TextOrientation.Rotated90 || currentTextOrientation == TextOrientation.Rotated270; } } /// /// Returns axis title text orientation. If set to Auto automatically determines the /// orientation based on the axis position. /// /// Current text orientation. private TextOrientation GetTextOrientation() { if (this.TextOrientation == TextOrientation.Auto) { if (this.AxisPosition == AxisPosition.Left) { return TextOrientation.Rotated270; } else if (this.AxisPosition == AxisPosition.Right) { return TextOrientation.Rotated90; } return TextOrientation.Horizontal; } return this.TextOrientation; } /// /// Paint Axis elements on the back of the 3D scene. /// /// Reference to the Chart Graphics object internal void PrePaint(ChartGraphics graph) { if (enabled != false) { // draw axis hot region DrawAxisLineHotRegion(graph, true); // Paint Major Tick Marks majorTickMark.Paint(graph, true); // Paint Minor Tick Marks minorTickMark.Paint(graph, true); // Draw axis line DrawAxisLine(graph, true); // Paint Labels labelStyle.Paint(graph, true); } #if SUBAXES // Process all sub-axis if(!ChartArea.Area3DStyle.Enable3D && !ChartArea.chartAreaIsCurcular) { foreach(SubAxis subAxis in this.SubAxes) { subAxis.PrePaint( graph ); } } #endif // SUBAXES } /// /// Paint Axis /// /// Reference to the Chart Graphics object internal void Paint(ChartGraphics graph) { // Only Y axis is drawn in the circular Common.Chart area if (ChartArea != null && ChartArea.chartAreaIsCurcular) { // Y circular axes if (this.axisType == AxisName.Y && enabled != false) { ICircularChartType chartType = ChartArea.GetCircularChartType(); if (chartType != null) { Matrix oldMatrix = graph.Transform; float[] axesLocation = chartType.GetYAxisLocations(ChartArea); bool drawLabels = true; foreach (float curentSector in axesLocation) { // Set graphics rotation matrix Matrix newMatrix = oldMatrix.Clone(); newMatrix.RotateAt( curentSector, graph.GetAbsolutePoint(ChartArea.circularCenter)); graph.Transform = newMatrix; // draw axis hot region DrawAxisLineHotRegion(graph, false); // Paint Minor Tick Marks minorTickMark.Paint(graph, false); // Paint Major Tick Marks majorTickMark.Paint(graph, false); // Draw axis line DrawAxisLine(graph, false); // Only first Y axis has labels if (drawLabels) { drawLabels = false; // Save current font angle int currentAngle = labelStyle.Angle; // Set labels text angle if (labelStyle.Angle == 0) { if (curentSector >= 45f && curentSector <= 180f) { labelStyle.angle = -90; } else if (curentSector > 180f && curentSector <= 315f) { labelStyle.angle = 90; } } // Draw labels labelStyle.Paint(graph, false); // Restore font angle labelStyle.angle = currentAngle; } } graph.Transform = oldMatrix; } } // X circular axes if (this.axisType == AxisName.X && enabled != false) { labelStyle.PaintCircular(graph); } DrawAxisTitle(graph); return; } // If axis is disabled draw only Title if (enabled != false) { // draw axis hot region DrawAxisLineHotRegion(graph, false); // Paint Minor Tick Marks minorTickMark.Paint(graph, false); // Paint Major Tick Marks majorTickMark.Paint(graph, false); // Draw axis line DrawAxisLine(graph, false); // Paint Labels labelStyle.Paint(graph, false); // Scroll bar is supoorted only in 2D charts if (ChartArea != null && ChartArea.Area3DStyle.Enable3D == false) { // Draw axis scroll bar ScrollBar.Paint(graph); } } // Draw axis title this.DrawAxisTitle(graph); #if SUBAXES // Process all sub-axis if(ChartArea.IsSubAxesSupported) { foreach(SubAxis subAxis in this.SubAxes) { subAxis.Paint( graph ); } } #endif // SUBAXES // Reset temp axis offset for side-by-side charts like column this.ResetTempAxisOffset(); } /// /// Paint Axis element when segmented axis scale feature is used. /// /// Reference to the Chart Graphics object internal void PaintOnSegmentedScalePassOne( ChartGraphics graph ) { // If axis is disabled draw only Title if( enabled != false ) { // Paint Minor Tick Marks minorTickMark.Paint( graph, false ); // Paint Major Tick Marks majorTickMark.Paint( graph, false ); } #if SUBAXES // Process all sub-axis if(ChartArea.IsSubAxesSupported) { foreach(SubAxis subAxis in this.SubAxes) { subAxis.PaintOnSegmentedScalePassOne( graph ); } } #endif // SUBAXES } /// /// Paint Axis element when segmented axis scale feature is used. /// /// Reference to the Chart Graphics object internal void PaintOnSegmentedScalePassTwo( ChartGraphics graph ) { // If axis is disabled draw only Title if( enabled != false ) { // Draw axis line DrawAxisLine( graph, false ); // Paint Labels labelStyle.Paint( graph, false); } // Draw axis title this.DrawAxisTitle( graph ); // Reset temp axis offset for side-by-side charts like column this.ResetTempAxisOffset(); #if SUBAXES // Process all sub-axis if(ChartArea.IsSubAxesSupported) { foreach(SubAxis subAxis in this.SubAxes) { subAxis.PaintOnSegmentedScalePassTwo( graph ); } } #endif // SUBAXES } /// /// Draw axis title /// /// Reference to the Chart Graphics object private void DrawAxisTitle(ChartGraphics graph) { if (!this.enabled) return; // Draw axis title if (this.Title.Length > 0) { Matrix oldTransform = null; // Draw title in 3D if (ChartArea.Area3DStyle.Enable3D && !ChartArea.chartAreaIsCurcular) { DrawAxis3DTitle(graph); return; } string axisTitle = this.Title; //****************************************************** //** Check axis position //****************************************************** float axisPosition = (float)this.GetAxisPosition(); if (this.AxisPosition == AxisPosition.Bottom) { if (!this.GetIsMarksNextToAxis()) { axisPosition = ChartArea.PlotAreaPosition.Bottom; } axisPosition = ChartArea.PlotAreaPosition.Bottom - axisPosition; } else if (this.AxisPosition == AxisPosition.Top) { if (!this.GetIsMarksNextToAxis()) { axisPosition = ChartArea.PlotAreaPosition.Y; } axisPosition = axisPosition - ChartArea.PlotAreaPosition.Y; } else if (this.AxisPosition == AxisPosition.Right) { if (!this.GetIsMarksNextToAxis()) { axisPosition = ChartArea.PlotAreaPosition.Right; } axisPosition = ChartArea.PlotAreaPosition.Right - axisPosition; } else if (this.AxisPosition == AxisPosition.Left) { if (!this.GetIsMarksNextToAxis()) { axisPosition = ChartArea.PlotAreaPosition.X; } axisPosition = axisPosition - ChartArea.PlotAreaPosition.X; } //****************************************************** //** Adjust axis elements size with axis position //****************************************************** // Calculate total size of axis elements float axisSize = this.markSize + this.labelSize; axisSize -= axisPosition; if (axisSize < 0) { axisSize = 0; } // Set title alignment using (StringFormat format = new StringFormat()) { format.Alignment = this.TitleAlignment; format.Trimming = StringTrimming.EllipsisCharacter; // VSTS #144398 // We need to have the StringFormatFlags set to FitBlackBox as othwerwise axis titles using Fonts like // "Algerian" or "Forte" are completly clipped (= not drawn) due to the fact that MeasureString returns // a bounding rectangle that is too small. format.FormatFlags |= StringFormatFlags.FitBlackBox; // Calculate title rectangle _titlePosition = ChartArea.PlotAreaPosition.ToRectangleF(); float titleSizeWithoutSpacing = this.titleSize - elementSpacing; if (this.AxisPosition == AxisPosition.Left) { _titlePosition.X = ChartArea.PlotAreaPosition.X - titleSizeWithoutSpacing - axisSize; _titlePosition.Y = ChartArea.PlotAreaPosition.Y; if (!this.IsTextVertical) { SizeF axisTitleSize = new SizeF(titleSizeWithoutSpacing, ChartArea.PlotAreaPosition.Height); _titlePosition.Width = axisTitleSize.Width; _titlePosition.Height = axisTitleSize.Height; format.Alignment = StringAlignment.Center; if (this.TitleAlignment == StringAlignment.Far) { format.LineAlignment = StringAlignment.Near; } else if (this.TitleAlignment == StringAlignment.Near) { format.LineAlignment = StringAlignment.Far; } else { format.LineAlignment = StringAlignment.Center; } } else { SizeF axisTitleSize = graph.GetAbsoluteSize(new SizeF(titleSizeWithoutSpacing, ChartArea.PlotAreaPosition.Height)); axisTitleSize = graph.GetRelativeSize(new SizeF(axisTitleSize.Height, axisTitleSize.Width)); _titlePosition.Width = axisTitleSize.Width; _titlePosition.Height = axisTitleSize.Height; _titlePosition.Y += ChartArea.PlotAreaPosition.Height / 2f - _titlePosition.Height / 2f; _titlePosition.X += titleSizeWithoutSpacing / 2f - _titlePosition.Width / 2f; // Set graphics rotation transformation oldTransform = this.SetRotationTransformation(graph, _titlePosition); // Set alignment format.LineAlignment = StringAlignment.Center; } } else if (this.AxisPosition == AxisPosition.Right) { _titlePosition.X = ChartArea.PlotAreaPosition.Right + axisSize; _titlePosition.Y = ChartArea.PlotAreaPosition.Y; if (!this.IsTextVertical) { SizeF axisTitleSize = new SizeF(titleSizeWithoutSpacing, ChartArea.PlotAreaPosition.Height); _titlePosition.Width = axisTitleSize.Width; _titlePosition.Height = axisTitleSize.Height; format.Alignment = StringAlignment.Center; if (this.TitleAlignment == StringAlignment.Far) { format.LineAlignment = StringAlignment.Near; } else if (this.TitleAlignment == StringAlignment.Near) { format.LineAlignment = StringAlignment.Far; } else { format.LineAlignment = StringAlignment.Center; } } else { SizeF axisTitleSize = graph.GetAbsoluteSize(new SizeF(titleSizeWithoutSpacing, ChartArea.PlotAreaPosition.Height)); axisTitleSize = graph.GetRelativeSize(new SizeF(axisTitleSize.Height, axisTitleSize.Width)); _titlePosition.Width = axisTitleSize.Width; _titlePosition.Height = axisTitleSize.Height; _titlePosition.Y += ChartArea.PlotAreaPosition.Height / 2f - _titlePosition.Height / 2f; _titlePosition.X += titleSizeWithoutSpacing / 2f - _titlePosition.Width / 2f; // Set graphics rotation transformation oldTransform = this.SetRotationTransformation(graph, _titlePosition); // Set alignment format.LineAlignment = StringAlignment.Center; } } else if (this.AxisPosition == AxisPosition.Top) { _titlePosition.Y = ChartArea.PlotAreaPosition.Y - titleSizeWithoutSpacing - axisSize; _titlePosition.Height = titleSizeWithoutSpacing; _titlePosition.X = ChartArea.PlotAreaPosition.X; _titlePosition.Width = ChartArea.PlotAreaPosition.Width; if (this.IsTextVertical) { // Set graphics rotation transformation oldTransform = this.SetRotationTransformation(graph, _titlePosition); } // Set alignment format.LineAlignment = StringAlignment.Center; } else if (this.AxisPosition == AxisPosition.Bottom) { _titlePosition.Y = ChartArea.PlotAreaPosition.Bottom + axisSize; _titlePosition.Height = titleSizeWithoutSpacing; _titlePosition.X = ChartArea.PlotAreaPosition.X; _titlePosition.Width = ChartArea.PlotAreaPosition.Width; if (this.IsTextVertical) { // Set graphics rotation transformation oldTransform = this.SetRotationTransformation(graph, _titlePosition); } // Set alignment format.LineAlignment = StringAlignment.Center; } #if DEBUG // TESTING CODE: Shows labels rectangle position. // RectangleF rr = graph.GetAbsoluteRectangle(_titlePosition); // graph.DrawRectangle(Pens.Blue, rr.X, rr.Y, rr.Width, rr.Height); #endif // DEBUG // Draw title using (Brush brush = new SolidBrush(this.TitleForeColor)) { graph.DrawStringRel( axisTitle.Replace("\\n", "\n"), this.TitleFont, brush, _titlePosition, format, this.GetTextOrientation()); } } // Process selection regions if (this.Common.ProcessModeRegions) { // NOTE: Solves Issue #4423 // Transform title position coordinates using curent Graphics matrix RectangleF transformedTitlePosition = graph.GetAbsoluteRectangle(_titlePosition); PointF[] rectPoints = new PointF[] { new PointF(transformedTitlePosition.X, transformedTitlePosition.Y), new PointF(transformedTitlePosition.Right, transformedTitlePosition.Bottom) }; graph.Transform.TransformPoints(rectPoints); transformedTitlePosition = new RectangleF( rectPoints[0].X, rectPoints[0].Y, rectPoints[1].X - rectPoints[0].X, rectPoints[1].Y - rectPoints[0].Y); if (transformedTitlePosition.Width < 0) { transformedTitlePosition.Width = Math.Abs(transformedTitlePosition.Width); transformedTitlePosition.X -= transformedTitlePosition.Width; } if (transformedTitlePosition.Height < 0) { transformedTitlePosition.Height = Math.Abs(transformedTitlePosition.Height); transformedTitlePosition.Y -= transformedTitlePosition.Height; } // Add hot region this.Common.HotRegionsList.AddHotRegion( transformedTitlePosition, this, ChartElementType.AxisTitle, false, false); } // Restore old transformation if (oldTransform != null) { graph.Transform = oldTransform; } } } /// /// Helper method which sets 90 or -90 degrees transformation in the middle of the /// specified rectangle. It is used to draw title text rotated 90 or 270 degrees. /// /// Chart graphics to apply transformation for. /// Title position. /// Old graphics transformation matrix. private Matrix SetRotationTransformation(ChartGraphics graph, RectangleF titlePosition) { // Save old graphics transformation Matrix oldTransform = graph.Transform.Clone(); // Rotate left tile 90 degrees at center PointF center = PointF.Empty; center.X = titlePosition.X + titlePosition.Width / 2F; center.Y = titlePosition.Y + titlePosition.Height / 2F; // Create and set new transformation matrix float angle = (this.GetTextOrientation() == TextOrientation.Rotated90) ? 90f : -90f; Matrix newMatrix = graph.Transform.Clone(); newMatrix.RotateAt(angle, graph.GetAbsolutePoint(center)); graph.Transform = newMatrix; return oldTransform; } /// /// Draws a radial line in circular Common.Chart area. /// /// Object requesting the painting. /// Graphics path. /// Line color. /// Line width. /// Line style. /// X axis circular position. internal void DrawRadialLine( object obj, ChartGraphics graph, Color color, int width, ChartDashStyle style, double position) { // Create circle position rectangle RectangleF rect = ChartArea.PlotAreaPosition.ToRectangleF(); rect = graph.GetAbsoluteRectangle(rect); // Make sure the rectangle width equals rectangle height for the circle if (rect.Width != rect.Height) { if (rect.Width > rect.Height) { rect.X += (rect.Width - rect.Height) / 2f; rect.Width = rect.Height; } else { rect.Y += (rect.Height - rect.Width) / 2f; rect.Height = rect.Width; } } // Convert axis position to angle float angle = ChartArea.CircularPositionToAngle(position); // Set clipping region to the polygon Region oldRegion = null; if (ChartArea.CircularUsePolygons) { oldRegion = graph.Clip; graph.Clip = new Region(graph.GetPolygonCirclePath(rect, ChartArea.CircularSectorsNumber)); } // Get center point PointF centerPoint = graph.GetAbsolutePoint(ChartArea.circularCenter); // Set graphics rotation matrix Matrix oldMatrix = graph.Transform; Matrix newMatrix = oldMatrix.Clone(); newMatrix.RotateAt( angle, centerPoint); graph.Transform = newMatrix; // Draw Line PointF endPoint = new PointF(rect.X + rect.Width / 2f, rect.Y); graph.DrawLineAbs(color, width, style, centerPoint, endPoint); // Process selection regions if (this.Common.ProcessModeRegions) { using (GraphicsPath path = new GraphicsPath()) { path.AddLine(centerPoint, endPoint); path.Transform(newMatrix); try { using (Pen pen = new Pen(Color.Black, width + 2)) { path.Widen(pen); this.Common.HotRegionsList.AddHotRegion(path, false, ChartElementType.Gridlines, obj); } } catch (OutOfMemoryException) { // GraphicsPath.Widen incorrectly throws OutOfMemoryException // catching here and reacting by not widening } catch (ArgumentException) { } } } // Restore graphics graph.Transform = oldMatrix; newMatrix.Dispose(); // Restore clip region if (ChartArea.CircularUsePolygons) { graph.Clip = oldRegion; } } /// /// Draws a circular line in circular Common.Chart area. /// /// Object requesting the painting. /// Graphics path. /// Line color. /// Line width. /// Line style. /// Line position. internal void DrawCircularLine( object obj, ChartGraphics graph, Color color, int width, ChartDashStyle style, float position ) { // Create circle position rectangle RectangleF rect = ChartArea.PlotAreaPosition.ToRectangleF(); rect = graph.GetAbsoluteRectangle(rect); // Make sure the rectangle width equals rectangle height for the circle if (rect.Width != rect.Height) { if (rect.Width > rect.Height) { rect.X += (rect.Width - rect.Height) / 2f; rect.Width = rect.Height; } else { rect.Y += (rect.Height - rect.Width) / 2f; rect.Height = rect.Width; } } // Inflate rectangle PointF absPoint = graph.GetAbsolutePoint(new PointF(position, position)); float rectInflate = absPoint.Y - rect.Top; rect.Inflate(-rectInflate, -rectInflate); // Create circle pen Pen circlePen = new Pen(color, width); circlePen.DashStyle = graph.GetPenStyle(style); // Draw circle if (ChartArea.CircularUsePolygons) { // Draw eaqula sides polygon graph.DrawCircleAbs(circlePen, null, rect, ChartArea.CircularSectorsNumber, false); } else { graph.DrawEllipse(circlePen, rect); } // Process selection regions if (this.Common.ProcessModeRegions) { // Bounding rectangle must be more than 1 pixel by 1 pixel if (rect.Width >= 1f && rect.Height > 1) { GraphicsPath path = null; try { if (ChartArea.CircularUsePolygons) { path = graph.GetPolygonCirclePath(rect, ChartArea.CircularSectorsNumber); } else { path = new GraphicsPath(); path.AddEllipse(rect); } circlePen.Width += 2; path.Widen(circlePen); this.Common.HotRegionsList.AddHotRegion(path, false, ChartElementType.Gridlines, obj); } catch (OutOfMemoryException) { // GraphicsPath.Widen incorrectly throws OutOfMemoryException // catching here and reacting by not widening } catch (ArgumentException) { } finally { path.Dispose(); } } } } /// /// Draw axis title in 3D. /// /// Reference to the Chart Graphics object private void DrawAxis3DTitle(ChartGraphics graph) { // Do not draw title if axis is not enabled if (!this.enabled) { return; } string axisTitle = this.Title; // Draw axis title PointF rotationCenter = PointF.Empty; int angle = 0; // Set title alignment using (StringFormat format = new StringFormat()) { format.Alignment = this.TitleAlignment; format.Trimming = StringTrimming.EllipsisCharacter; format.FormatFlags |= StringFormatFlags.LineLimit; // Measure title size for non-centered aligment SizeF realTitleSize = graph.MeasureString(axisTitle.Replace("\\n", "\n"), this.TitleFont, new SizeF(10000f, 10000f), format, this.GetTextOrientation()); SizeF axisTitleSize = SizeF.Empty; if (format.Alignment != StringAlignment.Center) { axisTitleSize = realTitleSize; if (this.IsTextVertical) { // Switch height and width for vertical axis float tempValue = axisTitleSize.Height; axisTitleSize.Height = axisTitleSize.Width; axisTitleSize.Width = tempValue; } // Get relative size axisTitleSize = graph.GetRelativeSize(axisTitleSize); // Change format aligment for the reversed mode if (ChartArea.ReverseSeriesOrder) { if (format.Alignment == StringAlignment.Near) { format.Alignment = StringAlignment.Far; } else { format.Alignment = StringAlignment.Near; } } } // Set text rotation angle based on the text orientation if (this.GetTextOrientation() == TextOrientation.Rotated90) { angle = 90; } else if (this.GetTextOrientation() == TextOrientation.Rotated270) { angle = -90; } // Calculate title center point on the axis if (this.AxisPosition == AxisPosition.Left) { rotationCenter = new PointF(ChartArea.PlotAreaPosition.X, ChartArea.PlotAreaPosition.Y + ChartArea.PlotAreaPosition.Height / 2f); if (format.Alignment == StringAlignment.Near) { rotationCenter.Y = ChartArea.PlotAreaPosition.Bottom - axisTitleSize.Height / 2f; } else if (format.Alignment == StringAlignment.Far) { rotationCenter.Y = ChartArea.PlotAreaPosition.Y + axisTitleSize.Height / 2f; } } else if (this.AxisPosition == AxisPosition.Right) { rotationCenter = new PointF(ChartArea.PlotAreaPosition.Right, ChartArea.PlotAreaPosition.Y + ChartArea.PlotAreaPosition.Height / 2f); if (format.Alignment == StringAlignment.Near) { rotationCenter.Y = ChartArea.PlotAreaPosition.Bottom - axisTitleSize.Height / 2f; } else if (format.Alignment == StringAlignment.Far) { rotationCenter.Y = ChartArea.PlotAreaPosition.Y + axisTitleSize.Height / 2f; } } else if (this.AxisPosition == AxisPosition.Top) { rotationCenter = new PointF(ChartArea.PlotAreaPosition.X + ChartArea.PlotAreaPosition.Width / 2f, ChartArea.PlotAreaPosition.Y); if (format.Alignment == StringAlignment.Near) { rotationCenter.X = ChartArea.PlotAreaPosition.X + axisTitleSize.Width / 2f; } else if (format.Alignment == StringAlignment.Far) { rotationCenter.X = ChartArea.PlotAreaPosition.Right - axisTitleSize.Width / 2f; } } else if (this.AxisPosition == AxisPosition.Bottom) { rotationCenter = new PointF(ChartArea.PlotAreaPosition.X + ChartArea.PlotAreaPosition.Width / 2f, ChartArea.PlotAreaPosition.Bottom); if (format.Alignment == StringAlignment.Near) { rotationCenter.X = ChartArea.PlotAreaPosition.X + axisTitleSize.Width / 2f; } else if (format.Alignment == StringAlignment.Far) { rotationCenter.X = ChartArea.PlotAreaPosition.Right - axisTitleSize.Width / 2f; } } // Transform center of title coordinates and calculate axis angle bool isOnEdge = false; float zPosition = this.GetMarksZPosition(out isOnEdge); Point3D[] rotationCenterPoints = null; float angleAxis = 0; if (this.AxisPosition == AxisPosition.Top || this.AxisPosition == AxisPosition.Bottom) { rotationCenterPoints = new Point3D[] { new Point3D(rotationCenter.X, rotationCenter.Y, zPosition), new Point3D(rotationCenter.X - 20f, rotationCenter.Y, zPosition) }; // Transform coordinates of text rotation point ChartArea.matrix3D.TransformPoints(rotationCenterPoints); rotationCenter = rotationCenterPoints[0].PointF; // Get absolute coordinates rotationCenterPoints[0].PointF = graph.GetAbsolutePoint(rotationCenterPoints[0].PointF); rotationCenterPoints[1].PointF = graph.GetAbsolutePoint(rotationCenterPoints[1].PointF); // Calculate X axis angle angleAxis = (float)Math.Atan( (rotationCenterPoints[1].Y - rotationCenterPoints[0].Y) / (rotationCenterPoints[1].X - rotationCenterPoints[0].X)); } else { rotationCenterPoints = new Point3D[] { new Point3D(rotationCenter.X, rotationCenter.Y, zPosition), new Point3D(rotationCenter.X, rotationCenter.Y - 20f, zPosition) }; // Transform coordinates of text rotation point ChartArea.matrix3D.TransformPoints(rotationCenterPoints); rotationCenter = rotationCenterPoints[0].PointF; // Get absolute coordinates rotationCenterPoints[0].PointF = graph.GetAbsolutePoint(rotationCenterPoints[0].PointF); rotationCenterPoints[1].PointF = graph.GetAbsolutePoint(rotationCenterPoints[1].PointF); // Calculate Y axis angle if (rotationCenterPoints[1].Y != rotationCenterPoints[0].Y) { angleAxis = -(float)Math.Atan( (rotationCenterPoints[1].X - rotationCenterPoints[0].X) / (rotationCenterPoints[1].Y - rotationCenterPoints[0].Y)); } } angle += (int)Math.Round(angleAxis * 180f / (float)Math.PI); // Calculate title center offset from the axis line float offset = this.labelSize + this.markSize + this.titleSize / 2f; float dX = 0f, dY = 0f; // Adjust center of title with labels, marker and title size if (this.AxisPosition == AxisPosition.Left) { dX = (float)(offset * Math.Cos(angleAxis)); rotationCenter.X -= dX; } else if (this.AxisPosition == AxisPosition.Right) { dX = (float)(offset * Math.Cos(angleAxis)); rotationCenter.X += dX; } else if (this.AxisPosition == AxisPosition.Top) { dY = (float)(offset * Math.Cos(angleAxis)); dX = (float)(offset * Math.Sin(angleAxis)); rotationCenter.Y -= dY; if (dY > 0) { rotationCenter.X += dX; } else { rotationCenter.X -= dX; } } else if (this.AxisPosition == AxisPosition.Bottom) { dY = (float)(offset * Math.Cos(angleAxis)); dX = (float)(offset * Math.Sin(angleAxis)); rotationCenter.Y += dY; if (dY > 0) { rotationCenter.X -= dX; } else { rotationCenter.X += dX; } } // Always align text in the center format.LineAlignment = StringAlignment.Center; format.Alignment = StringAlignment.Center; // SQL VSTS Fix #259954, Dev10: 591135 Windows 7 crashes on empty transformation. if (rotationCenter.IsEmpty || float.IsNaN(rotationCenter.X) || float.IsNaN(rotationCenter.Y)) { return; } // Draw 3D title using (Brush brush = new SolidBrush(this.TitleForeColor)) { graph.DrawStringRel( axisTitle.Replace("\\n", "\n"), this.TitleFont, brush, rotationCenter, format, angle, this.GetTextOrientation()); } // Add hot region if (Common.ProcessModeRegions) { using (GraphicsPath hotPath = graph.GetTranformedTextRectPath(rotationCenter, realTitleSize, angle)) { this.Common.HotRegionsList.AddHotRegion(hotPath, false, ChartElementType.AxisTitle, this); } } } } /// /// Select Axis line /// /// Reference to the Chart Graphics /// Back elements of the axis should be drawn in 3D scene. internal void DrawAxisLine(ChartGraphics graph, bool backElements) { Axis opositeAxis; ArrowOrientation arrowOrientation = ArrowOrientation.Top; PointF first = Point.Empty; PointF second = Point.Empty; // Set the position of axis switch (AxisPosition) { case AxisPosition.Left: first.X = (float)GetAxisPosition(); first.Y = PlotAreaPosition.Bottom; second.X = (float)GetAxisPosition(); second.Y = PlotAreaPosition.Y; if (isReversed) arrowOrientation = ArrowOrientation.Bottom; else arrowOrientation = ArrowOrientation.Top; break; case AxisPosition.Right: first.X = (float)GetAxisPosition(); first.Y = PlotAreaPosition.Bottom; second.X = (float)GetAxisPosition(); second.Y = PlotAreaPosition.Y; if (isReversed) arrowOrientation = ArrowOrientation.Bottom; else arrowOrientation = ArrowOrientation.Top; break; case AxisPosition.Bottom: first.X = PlotAreaPosition.X; first.Y = (float)GetAxisPosition(); second.X = PlotAreaPosition.Right; second.Y = (float)GetAxisPosition(); if (isReversed) arrowOrientation = ArrowOrientation.Left; else arrowOrientation = ArrowOrientation.Right; break; case AxisPosition.Top: first.X = PlotAreaPosition.X; first.Y = (float)GetAxisPosition(); second.X = PlotAreaPosition.Right; second.Y = (float)GetAxisPosition(); if (isReversed) arrowOrientation = ArrowOrientation.Left; else arrowOrientation = ArrowOrientation.Right; break; } // Update axis line position for circular area if (ChartArea.chartAreaIsCurcular) { first.Y = PlotAreaPosition.Y + PlotAreaPosition.Height / 2f; } if (Common.ProcessModePaint) { if (!ChartArea.Area3DStyle.Enable3D || ChartArea.chartAreaIsCurcular) { // Start Svg/Flash Selection mode graph.StartHotRegion( this._url, _toolTip ); // Draw the line graph.DrawLineRel(_lineColor, _lineWidth, _lineDashStyle, first, second); // End Svg/Flash Selection mode graph.EndHotRegion( ); // Opposite axis. Arrow uses this axis to find // a shift from Common.Chart area border. This shift // depend on Tick mark size. switch (arrowOrientation) { case ArrowOrientation.Left: opositeAxis = ChartArea.AxisX; break; case ArrowOrientation.Right: opositeAxis = ChartArea.AxisX2; break; case ArrowOrientation.Top: opositeAxis = ChartArea.AxisY2; break; case ArrowOrientation.Bottom: opositeAxis = ChartArea.AxisY; break; default: opositeAxis = ChartArea.AxisX; break; } // Draw arrow PointF arrowPosition; if (isReversed) arrowPosition = first; else arrowPosition = second; // Draw Arrow graph.DrawArrowRel(arrowPosition, arrowOrientation, _arrowStyle, _lineColor, _lineWidth, _lineDashStyle, opositeAxis.majorTickMark.Size, _lineWidth); } else { Draw3DAxisLine(graph, first, second, (this.AxisPosition == AxisPosition.Top || this.AxisPosition == AxisPosition.Bottom), backElements); } } } /// /// Draws the axis line hot region. /// /// The graph. /// set to true if we draw back elements. private void DrawAxisLineHotRegion(ChartGraphics graph, bool backElements) { if (Common.ProcessModeRegions) { //VSTS #229835: During the 3D rendering the axis is drawn twice: //1. In PrePaint() both axis and backelements (labels) are drawn. //2. In Paint() the axis is redrawn without labels and as a result it creates a second hot region which covered the labels' hotregions. //In order to avoid this we have to suppress the hotregion drawing in the Paint using the backElements flag (it's false during the Paint) //The circular charts and 2D charts are drawn only once in Paint() so we draw the hot regions. if (backElements || !ChartArea.Area3DStyle.Enable3D || ChartArea.chartAreaIsCurcular) { DrawAxisLineHotRegion(graph); } } } /// /// Adds the axis hot region /// /// The chart graphics instance. private void DrawAxisLineHotRegion(ChartGraphics graph) { using (GraphicsPath path = new GraphicsPath()) { // Find the topLeft(first) and bottomRight(second) points of the hotregion rectangle PointF first = PointF.Empty; PointF second = PointF.Empty; float axisPosition = (float)GetAxisPosition(); switch (this.AxisPosition) { case AxisPosition.Left: first.X = axisPosition - (labelSize + markSize); first.Y = PlotAreaPosition.Y; second.X = axisPosition; second.Y = PlotAreaPosition.Bottom; break; case AxisPosition.Right: first.X = axisPosition; first.Y = PlotAreaPosition.Y; second.X = axisPosition + labelSize + markSize; second.Y = PlotAreaPosition.Bottom; break; case AxisPosition.Bottom: first.X = PlotAreaPosition.X; first.Y = axisPosition; second.X = PlotAreaPosition.Right; second.Y = axisPosition + labelSize + markSize; break; case AxisPosition.Top: first.X = PlotAreaPosition.X; first.Y = axisPosition - (labelSize + markSize); second.X = PlotAreaPosition.Right; second.Y = axisPosition; break; } // Update axis line position for circular area if (ChartArea.chartAreaIsCurcular) { second.Y = PlotAreaPosition.Y + PlotAreaPosition.Height / 2f; } // Create rectangle and inflate it RectangleF rect = new RectangleF(first.X, first.Y, second.X - first.X, second.Y - first.Y); SizeF size = graph.GetRelativeSize(new SizeF(3, 3)); if (AxisPosition == AxisPosition.Top || AxisPosition == AxisPosition.Bottom) { rect.Inflate(2, size.Height); } else { rect.Inflate(size.Width, 2); } // Get the rectangle points PointF[] points = new PointF[] { new PointF(rect.Left, rect.Top), new PointF(rect.Right, rect.Top), new PointF(rect.Right, rect.Bottom), new PointF(rect.Left, rect.Bottom)}; // If we are dealing with the 3D - transform the rectangle if (ChartArea.Area3DStyle.Enable3D && !ChartArea.chartAreaIsCurcular) { Boolean axisOnEdge = false; float zPositon = GetMarksZPosition(out axisOnEdge); // Convert points to 3D Point3D[] points3D = new Point3D[points.Length]; for (int i = 0; i < points.Length; i++) { points3D[i] = new Point3D(points[i].X, points[i].Y, zPositon); } // Transform ChartArea.matrix3D.TransformPoints(points3D); // Convert to 2D for (int i = 0; i < points3D.Length; i++) { points[i] = points3D[i].PointF; } } // Transform points to absolute cooordinates for (int i = 0; i < points.Length; i++) { points[i] = graph.GetAbsolutePoint(points[i]); } // Add the points to the path path.AddPolygon(points); Common.HotRegionsList.AddHotRegion( graph, path, false, this._toolTip, string.Empty, string.Empty, string.Empty, this, ChartElementType.Axis); } } /// /// Draws axis line in 3D space. /// /// Reference to the Chart Graphics object. /// First line point. /// Second line point. /// Indicates that tick mark line is horizontal /// Only back elements of axis should be drawn. private void Draw3DAxisLine( ChartGraphics graph, PointF point1, PointF point2, bool horizontal, bool backElements ) { // Check if axis is positioned on the plot area adge bool onEdge = this.IsAxisOnAreaEdge; // Check if axis tick marks are drawn inside plotting area bool tickMarksOnEdge = onEdge; if (tickMarksOnEdge && this.MajorTickMark.TickMarkStyle == TickMarkStyle.AcrossAxis || this.MajorTickMark.TickMarkStyle == TickMarkStyle.InsideArea || this.MinorTickMark.TickMarkStyle == TickMarkStyle.AcrossAxis || this.MinorTickMark.TickMarkStyle == TickMarkStyle.InsideArea) { tickMarksOnEdge = false; } // Make sure first point of axis coordinates has smaller values if ((horizontal && point1.X > point2.X) || (!horizontal && point1.Y > point2.Y)) { PointF tempPoint = new PointF(point1.X, point1.Y); point1.X = point2.X; point1.Y = point2.Y; point2 = tempPoint; } // Check if the front/back wall is on the top drawing layer float zPositon = ChartArea.IsMainSceneWallOnFront() ? ChartArea.areaSceneDepth : 0f; SurfaceNames surfName = ChartArea.IsMainSceneWallOnFront() ? SurfaceNames.Front : SurfaceNames.Back; if (ChartArea.ShouldDrawOnSurface(SurfaceNames.Back, backElements, tickMarksOnEdge)) { // Start Svg Selection mode graph.StartHotRegion( this._url, _toolTip ); // Draw axis line on the back/front wall graph.Draw3DLine( ChartArea.matrix3D, _lineColor, _lineWidth, _lineDashStyle, new Point3D(point1.X, point1.Y, zPositon), new Point3D(point2.X, point2.Y, zPositon), Common, this, ChartElementType.Nothing ); // End Svg Selection mode graph.EndHotRegion(); } // Check if the back wall is on the top drawing layer zPositon = ChartArea.IsMainSceneWallOnFront() ? 0f : ChartArea.areaSceneDepth; surfName = ChartArea.IsMainSceneWallOnFront() ? SurfaceNames.Back : SurfaceNames.Front; if (ChartArea.ShouldDrawOnSurface(surfName, backElements, tickMarksOnEdge)) { // Draw axis line on the front wall if (!onEdge || (this.AxisPosition == AxisPosition.Bottom && ChartArea.IsBottomSceneWallVisible()) || (this.AxisPosition == AxisPosition.Left && ChartArea.IsSideSceneWallOnLeft()) || (this.AxisPosition == AxisPosition.Right && !ChartArea.IsSideSceneWallOnLeft())) { // Start Svg Selection mode graph.StartHotRegion( this._url, _toolTip ); graph.Draw3DLine( ChartArea.matrix3D, _lineColor, _lineWidth, _lineDashStyle, new Point3D(point1.X, point1.Y, zPositon), new Point3D(point2.X, point2.Y, zPositon), Common, this, ChartElementType.Nothing ); // End Svg Selection mode graph.EndHotRegion(); } } // Check if the left/top wall is on the top drawing layer SurfaceNames surfaceName = (this.AxisPosition == AxisPosition.Left || this.AxisPosition == AxisPosition.Right) ? SurfaceNames.Top : SurfaceNames.Left; if (ChartArea.ShouldDrawOnSurface(surfaceName, backElements, tickMarksOnEdge)) { // Draw axis line on the left/top side walls if (!onEdge || (this.AxisPosition == AxisPosition.Bottom && (ChartArea.IsBottomSceneWallVisible() || ChartArea.IsSideSceneWallOnLeft())) || (this.AxisPosition == AxisPosition.Left && ChartArea.IsSideSceneWallOnLeft()) || (this.AxisPosition == AxisPosition.Right && !ChartArea.IsSideSceneWallOnLeft()) || (this.AxisPosition == AxisPosition.Top && ChartArea.IsSideSceneWallOnLeft())) { // Start Svg Selection mode graph.StartHotRegion( this._url, _toolTip ); graph.Draw3DLine( ChartArea.matrix3D, _lineColor, _lineWidth, _lineDashStyle, new Point3D(point1.X, point1.Y, ChartArea.areaSceneDepth), new Point3D(point1.X, point1.Y, 0f), Common, this, ChartElementType.Nothing ); // End Svg Selection mode graph.EndHotRegion( ); } } // Check if the right/bottom wall is on the top drawing layer surfaceName = (this.AxisPosition == AxisPosition.Left || this.AxisPosition == AxisPosition.Right) ? SurfaceNames.Bottom : SurfaceNames.Right; if (ChartArea.ShouldDrawOnSurface(surfaceName, backElements, tickMarksOnEdge)) { // Draw axis line on the bottom/right side walls if (!onEdge || (this.AxisPosition == AxisPosition.Bottom && (ChartArea.IsBottomSceneWallVisible() || !ChartArea.IsSideSceneWallOnLeft())) || (this.AxisPosition == AxisPosition.Left && (ChartArea.IsSideSceneWallOnLeft() || ChartArea.IsBottomSceneWallVisible())) || (this.AxisPosition == AxisPosition.Right && (!ChartArea.IsSideSceneWallOnLeft() || ChartArea.IsBottomSceneWallVisible())) || (this.AxisPosition == AxisPosition.Top && !ChartArea.IsSideSceneWallOnLeft()) ) { // Start Svg Selection mode graph.StartHotRegion( this._url, _toolTip ); graph.Draw3DLine( ChartArea.matrix3D, _lineColor, _lineWidth, _lineDashStyle, new Point3D(point2.X, point2.Y, ChartArea.areaSceneDepth), new Point3D(point2.X, point2.Y, 0f), Common, this, ChartElementType.Nothing ); // End Svg Selection mode graph.EndHotRegion(); } } } /// /// Gets Z position of axis tick marks and labels. /// /// Returns true if axis is on the edge. /// Marks Z position. internal float GetMarksZPosition(out bool axisOnEdge) { axisOnEdge = this.IsAxisOnAreaEdge; if (!this.GetIsMarksNextToAxis()) { // Marks are forced to be on the area edge axisOnEdge = true; } float wallZPosition = 0f; if (this.AxisPosition == AxisPosition.Bottom && (ChartArea.IsBottomSceneWallVisible() || !axisOnEdge)) { wallZPosition = ChartArea.areaSceneDepth; } if (this.AxisPosition == AxisPosition.Left && (ChartArea.IsSideSceneWallOnLeft() || !axisOnEdge)) { wallZPosition = ChartArea.areaSceneDepth; } if (this.AxisPosition == AxisPosition.Right && (!ChartArea.IsSideSceneWallOnLeft() || !axisOnEdge)) { wallZPosition = ChartArea.areaSceneDepth; } if (this.AxisPosition == AxisPosition.Top && !axisOnEdge) { wallZPosition = ChartArea.areaSceneDepth; } // Check if front wall is shown if (ChartArea.IsMainSceneWallOnFront()) { // Switch Z position of tick mark wallZPosition = (wallZPosition == 0f) ? ChartArea.areaSceneDepth : 0f; } return wallZPosition; } /// /// Paint Axis Grid lines /// /// Reference to the Chart Graphics object internal void PaintGrids(ChartGraphics graph) { object obj; PaintGrids(graph, out obj); } /// /// Paint Axis Grid lines or /// hit test function for grid lines /// /// Reference to the Chart Graphics object /// Returns selected grid object internal void PaintGrids(ChartGraphics graph, out object obj) { obj = null; #if SUBAXES // Paint grids of sub-axis if(!ChartArea.Area3DStyle.Enable3D && !ChartArea.chartAreaIsCurcular) { foreach(SubAxis subAxis in this.SubAxes) { subAxis.PaintGrids( graph, out obj); } } #endif // SUBAXES // Axis is disabled if (enabled == false) return; // Paint Minor grid lines minorGrid.Paint(graph); // Paint Major grid lines majorGrid.Paint(graph); } /// /// Paint Axis Strip lines /// /// Reference to the Chart Graphics object /// Indicates if Lines or Stripes should be drawn. internal void PaintStrips(ChartGraphics graph, bool drawLinesOnly) { object obj; PaintStrips(graph, false, 0, 0, out obj, drawLinesOnly); } /// /// Paint Axis Strip lines or /// hit test function for Strip lines /// /// Reference to the Chart Graphics object /// The selection mode is active /// X coordinate /// Y coordinate /// Returns selected grid object /// Indicates if Lines or Stripes should be drawn. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "y"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "x"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "selectionMode")] internal void PaintStrips(ChartGraphics graph, bool selectionMode, int x, int y, out object obj, bool drawLinesOnly) { obj = null; #if SUBAXES // Paint strips of sub-axis if(ChartArea.IsSubAxesSupported) { foreach(SubAxis subAxis in this.SubAxes) { subAxis.PaintStrips( graph, selectionMode, x, y, out obj, drawLinesOnly); } } #endif // SUBAXES // Axis is disabled if (enabled == false) return; // Add axis isInterlaced strip lines into the collection bool interlacedStripAdded = AddInterlacedStrip(); // Draw axis strips and lines foreach (StripLine strip in this.StripLines) { strip.Paint(graph, this.Common, drawLinesOnly); } // Remove axis isInterlaced strip line from the collection after drawing if (interlacedStripAdded) { // Remove isInterlaced strips which always is the first strip line this.StripLines.RemoveAt(0); } } /// /// Helper function which adds temp. strip lines into the collection /// to display isInterlaced lines in axis. /// private bool AddInterlacedStrip() { bool addStrip = false; if (this.IsInterlaced) { StripLine stripLine = new StripLine(); stripLine.interlaced = true; // VSTS fix of 164115 IsInterlaced StripLines with no border are rendered with black border, regression of VSTS 136763 stripLine.BorderColor = Color.Empty; // Get interval from grid lines, tick marks or labels if (this.MajorGrid.Enabled && this.MajorGrid.GetInterval() != 0.0) { addStrip = true; stripLine.Interval = this.MajorGrid.GetInterval() * 2.0; stripLine.IntervalType = this.MajorGrid.GetIntervalType(); stripLine.IntervalOffset = this.MajorGrid.GetIntervalOffset(); stripLine.IntervalOffsetType = this.MajorGrid.GetIntervalOffsetType(); stripLine.StripWidth = this.MajorGrid.GetInterval(); stripLine.StripWidthType = this.MajorGrid.GetIntervalType(); } else if (this.MajorTickMark.Enabled && this.MajorTickMark.GetInterval() != 0.0) { addStrip = true; stripLine.Interval = this.MajorTickMark.GetInterval() * 2.0; stripLine.IntervalType = this.MajorTickMark.GetIntervalType(); stripLine.IntervalOffset = this.MajorTickMark.GetIntervalOffset(); stripLine.IntervalOffsetType = this.MajorTickMark.GetIntervalOffsetType(); stripLine.StripWidth = this.MajorTickMark.GetInterval(); stripLine.StripWidthType = this.MajorTickMark.GetIntervalType(); } else if (this.LabelStyle.Enabled && this.LabelStyle.GetInterval() != 0.0) { addStrip = true; stripLine.Interval = this.LabelStyle.GetInterval() * 2.0; stripLine.IntervalType = this.LabelStyle.GetIntervalType(); stripLine.IntervalOffset = this.LabelStyle.GetIntervalOffset(); stripLine.IntervalOffsetType = this.LabelStyle.GetIntervalOffsetType(); stripLine.StripWidth = this.LabelStyle.GetInterval(); stripLine.StripWidthType = this.LabelStyle.GetIntervalType(); } // Insert item into the strips collection if (addStrip) { // Define stip color if (this.InterlacedColor != Color.Empty) { stripLine.BackColor = this.InterlacedColor; } else { // If isInterlaced strips color is not set - use darker color of the area if (ChartArea.BackColor == Color.Empty) { stripLine.BackColor = (ChartArea.Area3DStyle.Enable3D) ? Color.DarkGray : Color.LightGray; } else if (ChartArea.BackColor == Color.Transparent) { if (Common.Chart.BackColor != Color.Transparent && Common.Chart.BackColor != Color.Black) { stripLine.BackColor = ChartGraphics.GetGradientColor(Common.Chart.BackColor, Color.Black, 0.2); } else { stripLine.BackColor = Color.LightGray; } } else { stripLine.BackColor = ChartGraphics.GetGradientColor(ChartArea.BackColor, Color.Black, 0.2); } } // Insert strip this.StripLines.Insert(0, stripLine); } } return addStrip; } #endregion #region Axis parameters recalculation and resizing methods /// /// This method will calculate the maximum and minimum values /// using interval on the X axis automatically. It will make a gap between /// data points and border of the Common.Chart area. /// Note that this method can only be called for primary or secondary X axes. /// public void RoundAxisValues() { this.roundedXValues = true; } /// /// RecalculateAxesScale axis. /// /// Plotting area position. internal void ReCalc(ElementPosition position) { PlotAreaPosition = position; #if SUBAXES // Recalculate all sub-axis foreach(SubAxis subAxis in this.SubAxes) { subAxis.ReCalc( position ); } #endif // SUBAXES } /// /// This method store Axis values as minimum, maximum, /// crossing, etc. Axis auto algorithm changes these /// values and they have to be set to default values /// after painting. /// internal void StoreAxisValues() { tempLabels = new CustomLabelsCollection(this); foreach (CustomLabel label in CustomLabels) { tempLabels.Add(label.Clone()); } paintMode = true; // This field synchronies the Storing and // resetting of temporary values if (_storeValuesEnabled) { tempMaximum = maximum; tempMinimum = minimum; tempCrossing = crossing; tempAutoMinimum = _autoMinimum; tempAutoMaximum = _autoMaximum; tempMajorGridInterval = majorGrid.interval; tempMajorTickMarkInterval = majorTickMark.interval; tempMinorGridInterval = minorGrid.interval; tempMinorTickMarkInterval = minorTickMark.interval; tempGridIntervalType = majorGrid.intervalType; tempTickMarkIntervalType = majorTickMark.intervalType; tempLabelInterval = labelStyle.interval; tempLabelIntervalType = labelStyle.intervalType; // Remember original ScaleView Position this._originalViewPosition = this.ScaleView.Position; // This field synchronies the Storing and // resetting of temporary values _storeValuesEnabled = false; } #if SUBAXES // Store values of all sub-axis if(ChartArea.IsSubAxesSupported) { foreach(SubAxis subAxis in this.SubAxes) { subAxis.StoreAxisValues( ); } } #endif // SUBAXES } /// /// This method reset Axis values as minimum, maximum, /// crossing, etc. Axis auto algorithm changes these /// values and they have to be set to default values /// after painting. /// internal void ResetAxisValues() { // Paint mode is finished paintMode = false; if(Common.Chart == null) { #if SUBAXES else if(this is SubAxis) { if( ((SubAxis)this).parentAxis != null) { this.Common = ((SubAxis)this).parentAxis.Common; Common.Chart = ((SubAxis)this).parentAxis.Common.Chart; } } #endif // SUBAXES } if(Common.Chart != null && Common.Chart.Site != null && Common.Chart.Site.DesignMode) { ResetAutoValues(); } // Reset back original custom labels if (tempLabels != null) { CustomLabels.Clear(); foreach (CustomLabel label in tempLabels) { CustomLabels.Add(label.Clone()); } tempLabels = null; } #if SUBAXES // Reset values of all sub-axis if(ChartArea.IsSubAxesSupported) { foreach(SubAxis subAxis in this.SubAxes) { subAxis.ResetAxisValues( ); } } #endif // SUBAXES } /// /// Reset auto calculated axis values /// internal void ResetAutoValues() { refreshMinMaxFromData = true; maximum = tempMaximum; minimum = tempMinimum; crossing = tempCrossing; _autoMinimum = tempAutoMinimum; _autoMaximum = tempAutoMaximum; majorGrid.interval = tempMajorGridInterval; majorTickMark.interval = tempMajorTickMarkInterval; minorGrid.interval = tempMinorGridInterval; minorTickMark.interval = tempMinorTickMarkInterval; labelStyle.interval = tempLabelInterval; majorGrid.intervalType = tempGridIntervalType; majorTickMark.intervalType = tempTickMarkIntervalType; labelStyle.intervalType = tempLabelIntervalType; // Restore original ScaleView Position if (Common.Chart != null) { if (!Common.Chart.serializing) { this.ScaleView.Position = this._originalViewPosition; } } // This field synchronies the Storing and // resetting of temporary values _storeValuesEnabled = true; #if SUBAXES // Reset auto values of all sub-axis if(ChartArea.IsSubAxesSupported) { foreach(SubAxis subAxis in this.SubAxes) { subAxis.ResetAutoValues( ); } } #endif // SUBAXES } /// /// Calculate size of the axis elements like title, labels and marks. /// /// Chart graphics object. /// The Chart area position. /// Plotting area size. /// Number of axis of the same orientation. /// Indicates that inner plot position is automatic. virtual internal void Resize( ChartGraphics chartGraph, ElementPosition chartAreaPosition, RectangleF plotArea, float axesNumber, bool autoPlotPosition) { #if SUBAXES // Resize all sub-axis if(ChartArea.IsSubAxesSupported) { foreach(SubAxis subAxis in this.SubAxes) { subAxis.Resize(chartGraph, chartAreaPosition, plotArea, axesNumber, autoPlotPosition); } } #endif // SUBAXES // Disable Common.Chart invalidation bool oldDisableInvalidates = Common.Chart.disableInvalidates; Common.Chart.disableInvalidates = true; // Set Common.Chart area position PlotAreaPosition = chartAreaPosition; // Initialize plot area size PlotAreaPosition.FromRectangleF(plotArea); //****************************************************** //** Calculate axis title size //****************************************************** this.titleSize = 0F; if (this.Title.Length > 0) { // Measure axis title SizeF titleStringSize = chartGraph.MeasureStringRel(this.Title.Replace("\\n", "\n"), this.TitleFont, new SizeF(10000f, 10000f), StringFormat.GenericTypographic, this.GetTextOrientation()); // Switch Width & Heigth for vertical axes // If axis is horizontal float maxTitlesize = 0; if (this.AxisPosition == AxisPosition.Bottom || this.AxisPosition == AxisPosition.Top) { maxTitlesize = (plotArea.Height / 100F) * (Axis.maxAxisTitleSize / axesNumber); if (this.IsTextVertical) { this.titleSize = Math.Min(titleStringSize.Width, maxTitlesize); } else { this.titleSize = Math.Min(titleStringSize.Height, maxTitlesize); } } // If axis is vertical else { titleStringSize = chartGraph.GetAbsoluteSize(titleStringSize); titleStringSize = chartGraph.GetRelativeSize(new SizeF(titleStringSize.Height, titleStringSize.Width)); maxTitlesize = (plotArea.Width / 100F) * (Axis.maxAxisTitleSize / axesNumber); if (this.IsTextVertical) { this.titleSize = Math.Min(titleStringSize.Width, maxTitlesize); } else { this.titleSize = Math.Min(titleStringSize.Height, maxTitlesize); } } } if (this.titleSize > 0) { this.titleSize += elementSpacing; } //********************************************************* //** Get arrow size of the opposite axis //********************************************************* float arrowSize = 0F; SizeF arrowSizePrimary = SizeF.Empty; SizeF arrowSizeSecondary = SizeF.Empty; ArrowOrientation arrowOrientation = ArrowOrientation.Bottom; if (this.axisType == AxisName.X || this.axisType == AxisName.X2) { if (ChartArea.AxisY.ArrowStyle != AxisArrowStyle.None) { arrowSizePrimary = ChartArea.AxisY.GetArrowSize(out arrowOrientation); if (!IsArrowInAxis(arrowOrientation, this.AxisPosition)) { arrowSizePrimary = SizeF.Empty; } } if (ChartArea.AxisY2.ArrowStyle != AxisArrowStyle.None) { arrowSizeSecondary = ChartArea.AxisY2.GetArrowSize(out arrowOrientation); if (!IsArrowInAxis(arrowOrientation, this.AxisPosition)) { arrowSizeSecondary = SizeF.Empty; } } } else { if (ChartArea.AxisX.ArrowStyle != AxisArrowStyle.None) { arrowSizePrimary = ChartArea.AxisX.GetArrowSize(out arrowOrientation); if (!IsArrowInAxis(arrowOrientation, this.AxisPosition)) { arrowSizePrimary = SizeF.Empty; } } if (ChartArea.AxisX2.ArrowStyle != AxisArrowStyle.None) { arrowSizeSecondary = ChartArea.AxisX2.GetArrowSize(out arrowOrientation); if (!IsArrowInAxis(arrowOrientation, this.AxisPosition)) { arrowSizeSecondary = SizeF.Empty; } } } // If axis is horizontal if (this.AxisPosition == AxisPosition.Bottom || this.AxisPosition == AxisPosition.Top) { arrowSize = Math.Max(arrowSizePrimary.Height, arrowSizeSecondary.Height); } // If axis is vertical else { arrowSize = Math.Max(arrowSizePrimary.Width, arrowSizeSecondary.Width); } //********************************************************* //** Calculate axis tick marks, axis thickness, arrow size //** and scroll bar size //********************************************************* this.markSize = 0F; // Get major and minor tick marks sizes float majorTickSize = 0; if (this.MajorTickMark.Enabled && this.MajorTickMark.TickMarkStyle != TickMarkStyle.None) { if (this.MajorTickMark.TickMarkStyle == TickMarkStyle.InsideArea) { majorTickSize = 0F; } else if (this.MajorTickMark.TickMarkStyle == TickMarkStyle.AcrossAxis) { majorTickSize = this.MajorTickMark.Size / 2F; } else if (this.MajorTickMark.TickMarkStyle == TickMarkStyle.OutsideArea) { majorTickSize = this.MajorTickMark.Size; } } float minorTickSize = 0; if (this.MinorTickMark.Enabled && this.MinorTickMark.TickMarkStyle != TickMarkStyle.None && this.MinorTickMark.GetInterval() != 0) { if (this.MinorTickMark.TickMarkStyle == TickMarkStyle.InsideArea) { minorTickSize = 0F; } else if (this.MinorTickMark.TickMarkStyle == TickMarkStyle.AcrossAxis) { minorTickSize = this.MinorTickMark.Size / 2F; } else if (this.MinorTickMark.TickMarkStyle == TickMarkStyle.OutsideArea) { minorTickSize = this.MinorTickMark.Size; } } this.markSize += (float)Math.Max(majorTickSize, minorTickSize); // Add axis line size SizeF borderSize = chartGraph.GetRelativeSize(new SizeF(this.LineWidth, this.LineWidth)); // If axis is horizontal if (this.AxisPosition == AxisPosition.Bottom || this.AxisPosition == AxisPosition.Top) { this.markSize += borderSize.Height / 2f; this.markSize = Math.Min(this.markSize, (plotArea.Height / 100F) * (Axis.maxAxisMarkSize / axesNumber)); } // If axis is vertical else { this.markSize += borderSize.Width / 2f; this.markSize = Math.Min(this.markSize, (plotArea.Width / 100F) * (Axis.maxAxisMarkSize / axesNumber)); } // Add axis scroll bar size (if it's visible) this.scrollBarSize = 0f; if (this.ScrollBar.IsVisible && (this.IsAxisOnAreaEdge || !this.IsMarksNextToAxis)) { if (this.ScrollBar.IsPositionedInside) { this.markSize += (float)this.ScrollBar.GetScrollBarRelativeSize(); } else { this.scrollBarSize = (float)this.ScrollBar.GetScrollBarRelativeSize(); } } //********************************************************* //** Adjust mark size using area scene wall width //********************************************************* if (ChartArea.Area3DStyle.Enable3D && !ChartArea.chartAreaIsCurcular && ChartArea.BackColor != Color.Transparent && ChartArea.Area3DStyle.WallWidth > 0) { SizeF areaWallSize = chartGraph.GetRelativeSize(new SizeF(ChartArea.Area3DStyle.WallWidth, ChartArea.Area3DStyle.WallWidth)); if (this.AxisPosition == AxisPosition.Bottom || this.AxisPosition == AxisPosition.Top) { this.markSize += areaWallSize.Height; } else { this.markSize += areaWallSize.Width; } // Ignore Max marks size for the 3D wall size. //this.markSize = Math.Min(this.markSize, (plotArea.Width / 100F) * (Axis.maxAxisMarkSize / axesNumber)); } //********************************************************* //** Adjust title size and mark size using arrow size //********************************************************* if (arrowSize > (this.markSize + this.scrollBarSize + this.titleSize)) { this.markSize = Math.Max(this.markSize, arrowSize - (this.markSize + this.scrollBarSize + this.titleSize)); this.markSize = Math.Min(this.markSize, (plotArea.Width / 100F) * (Axis.maxAxisMarkSize / axesNumber)); } //********************************************************* //** Calculate max label size //********************************************************* float maxLabelSize = 0; if (!autoPlotPosition) { if (this.GetIsMarksNextToAxis()) { if (this.AxisPosition == AxisPosition.Top) maxLabelSize = (float)GetAxisPosition() - ChartArea.Position.Y; else if (this.AxisPosition == AxisPosition.Bottom) maxLabelSize = ChartArea.Position.Bottom - (float)GetAxisPosition(); if (this.AxisPosition == AxisPosition.Left) maxLabelSize = (float)GetAxisPosition() - ChartArea.Position.X; else if (this.AxisPosition == AxisPosition.Right) maxLabelSize = ChartArea.Position.Right - (float)GetAxisPosition(); } else { if (this.AxisPosition == AxisPosition.Top) maxLabelSize = plotArea.Y - ChartArea.Position.Y; else if (this.AxisPosition == AxisPosition.Bottom) maxLabelSize = ChartArea.Position.Bottom - plotArea.Bottom; if (this.AxisPosition == AxisPosition.Left) maxLabelSize = plotArea.X - ChartArea.Position.X; else if (this.AxisPosition == AxisPosition.Right) maxLabelSize = ChartArea.Position.Right - plotArea.Right; } maxLabelSize *= 2F; } else { if (this.AxisPosition == AxisPosition.Bottom || this.AxisPosition == AxisPosition.Top) maxLabelSize = plotArea.Height * (_maximumAutoSize / 100f); else maxLabelSize = plotArea.Width * (_maximumAutoSize / 100f); } //****************************************************** //** First try to select the interval that will //** generate best fit labels. //****************************************************** // Make sure the variable interval mode is enabled and // no custom label interval used. if( this.Enabled != AxisEnabled.False && this.LabelStyle.Enabled && this.IsVariableLabelCountModeEnabled() ) { // Increase font by several points when height of the font is the most important // dimension. Use original size whenwidth is the most important size. float extraSize = 3f; if( (this.AxisPosition == AxisPosition.Left || this.AxisPosition == AxisPosition.Right) && (this.LabelStyle.Angle == 90 || this.LabelStyle.Angle == -90) ) { extraSize = 0f; } if( (this.AxisPosition == AxisPosition.Top || this.AxisPosition == AxisPosition.Bottom) && (this.LabelStyle.Angle == 180 || this.LabelStyle.Angle == 0) ) { extraSize = 0f; } // If 3D Common.Chart is used make the measurements with font several point larger if(ChartArea.Area3DStyle.Enable3D) { extraSize += 1f; } this.autoLabelFont = Common.Chart.chartPicture.FontCache.GetFont(this.LabelStyle.Font.FontFamily, this.LabelStyle.Font.Size + extraSize, this.LabelStyle.Font.Style, GraphicsUnit.Point); // Reset angle and stagged flag used in the auto-fitting algorithm this.autoLabelAngle = this.LabelStyle.Angle; this.autoLabelOffset = (this.LabelStyle.IsStaggered) ? 1 : 0; // Adjust interval this.AdjustIntervalToFitLabels(chartGraph, autoPlotPosition, false); } //****************************************************** //** Automatically calculate the best font size, angle //** and try to use offset labels. //****************************************************** // Reset all automatic label properties autoLabelFont = null; autoLabelAngle = -1000; autoLabelOffset = -1; // For circular Common.Chart area process auto-fitting for Y Axis only if (this.IsLabelAutoFit && this.LabelAutoFitStyle != LabelAutoFitStyles.None && !ChartArea.chartAreaIsCurcular) { bool fitDone = false; bool noWordWrap = false; // Set default font angle and labels offset flag autoLabelAngle = 0; autoLabelOffset = 0; // Original labels collection CustomLabelsCollection originalLabels = null; // Pick up maximum font size float size = 8f; size = (float)Math.Max(this.LabelAutoFitMaxFontSize, this.LabelAutoFitMinFontSize); _minLabelFontSize = Math.Min(this.LabelAutoFitMinFontSize, this.LabelAutoFitMaxFontSize); _aveLabelFontSize = _minLabelFontSize + Math.Abs(size - _minLabelFontSize)/2f; // Check if common font size should be used if (ChartArea.IsSameFontSizeForAllAxes) { size = (float)Math.Min(size, ChartArea.axesAutoFontSize); } //Set new font autoLabelFont = Common.Chart.chartPicture.FontCache.GetFont(this.LabelStyle.Font.FontFamily, size, this.LabelStyle.Font.Style, GraphicsUnit.Point ); // Check if we allowed to increase font size while auto-fitting if ((this.LabelAutoFitStyle & LabelAutoFitStyles.IncreaseFont) != LabelAutoFitStyles.IncreaseFont) { // Use axis labels font as starting point autoLabelFont = this.LabelStyle.Font; } // Loop while labels do not fit float spacer = 0f; while (!fitDone) { //****************************************************** //** Check if labels fit //****************************************************** // Check if grouping labels fit should be checked bool checkLabelsFirstRowOnly = true; if ((this.LabelAutoFitStyle & LabelAutoFitStyles.DecreaseFont) == LabelAutoFitStyles.DecreaseFont) { // Only check grouping labels if we can reduce fonts size checkLabelsFirstRowOnly = false; } // Check labels fit fitDone = CheckLabelsFit( chartGraph, this.markSize + this.scrollBarSize + this.titleSize + spacer, autoPlotPosition, checkLabelsFirstRowOnly, false); //****************************************************** //** Adjust labels text properties to fit //****************************************************** if (!fitDone) { // If font is bigger than average try to make it smaller if (autoLabelFont.SizeInPoints >= _aveLabelFontSize && (this.LabelAutoFitStyle & LabelAutoFitStyles.DecreaseFont) == LabelAutoFitStyles.DecreaseFont) { //Clean up the old font autoLabelFont = Common.Chart.chartPicture.FontCache.GetFont( autoLabelFont.FontFamily, autoLabelFont.SizeInPoints - 0.5f, autoLabelFont.Style, GraphicsUnit.Point); } // Try to use offset labels (2D charts and non-circular arae only!!!) else if (!ChartArea.Area3DStyle.Enable3D && !ChartArea.chartAreaIsCurcular && originalLabels == null && autoLabelAngle == 0 && autoLabelOffset == 0 && (this.LabelAutoFitStyle & LabelAutoFitStyles.StaggeredLabels) == LabelAutoFitStyles.StaggeredLabels) { autoLabelOffset = 1; } // Try to insert new line characters in labels text else if (!noWordWrap && (this.LabelAutoFitStyle & LabelAutoFitStyles.WordWrap) == LabelAutoFitStyles.WordWrap) { bool changed = false; autoLabelOffset = 0; // Check if backup copy of the original lables was made if (originalLabels == null) { // Copy current labels collection originalLabels = new CustomLabelsCollection(this); foreach (CustomLabel label in this.CustomLabels) { originalLabels.Add(label.Clone()); } } // Try to insert new line character into the longest label changed = WordWrapLongestLabel(this.CustomLabels); // Word wrapping do not solve the labels overlapping issue if (!changed) { noWordWrap = true; // Restore original labels if (originalLabels != null) { this.CustomLabels.Clear(); foreach (CustomLabel label in originalLabels) { this.CustomLabels.Add(label.Clone()); } originalLabels = null; } if (this.AxisPosition == AxisPosition.Bottom || this.AxisPosition == AxisPosition.Top) { if ((spacer == 0 || spacer == 30f || spacer == 20f) && ((this.LabelAutoFitStyle & LabelAutoFitStyles.LabelsAngleStep30) == LabelAutoFitStyles.LabelsAngleStep30 || (this.LabelAutoFitStyle & LabelAutoFitStyles.LabelsAngleStep45) == LabelAutoFitStyles.LabelsAngleStep45 || (this.LabelAutoFitStyle & LabelAutoFitStyles.LabelsAngleStep90) == LabelAutoFitStyles.LabelsAngleStep90)) { // Try to use 90 degrees angle autoLabelAngle = 90; noWordWrap = false; // Usually 55% of Common.Chart area size is allowed for labels // Reduce that space. if (spacer == 0f) { // 30 spacer = 30f; } else if (spacer == 30f) { // 20 spacer = 20f; } else if (spacer == 20f) { // 5 spacer = 5f; } else { autoLabelAngle = 0; noWordWrap = true; } } else { spacer = 0f; } } } } // Try to change font angle else if (autoLabelAngle != 90 && ((this.LabelAutoFitStyle & LabelAutoFitStyles.LabelsAngleStep30) == LabelAutoFitStyles.LabelsAngleStep30 || (this.LabelAutoFitStyle & LabelAutoFitStyles.LabelsAngleStep45) == LabelAutoFitStyles.LabelsAngleStep45 || (this.LabelAutoFitStyle & LabelAutoFitStyles.LabelsAngleStep90) == LabelAutoFitStyles.LabelsAngleStep90)) { spacer = 0f; autoLabelOffset = 0; if ((this.LabelAutoFitStyle & LabelAutoFitStyles.LabelsAngleStep30) == LabelAutoFitStyles.LabelsAngleStep30) { // Increase angle by 45 degrees in 2D and 45 in 3D autoLabelAngle += (ChartArea.Area3DStyle.Enable3D) ? 45 : 30; } else if ((this.LabelAutoFitStyle & LabelAutoFitStyles.LabelsAngleStep45) == LabelAutoFitStyles.LabelsAngleStep45) { // Increase angle by 45 degrees autoLabelAngle += 45; } else if ((this.LabelAutoFitStyle & LabelAutoFitStyles.LabelsAngleStep90) == LabelAutoFitStyles.LabelsAngleStep90) { // Increase angle by 90 degrees autoLabelAngle += 90; } } // Try to reduce font again else if (autoLabelFont.SizeInPoints > _minLabelFontSize && (this.LabelAutoFitStyle & LabelAutoFitStyles.DecreaseFont) == LabelAutoFitStyles.DecreaseFont) { //Clean up the old font autoLabelAngle = 0; autoLabelFont = Common.Chart.chartPicture.FontCache.GetFont( autoLabelFont.FontFamily, autoLabelFont.SizeInPoints - 0.5f, autoLabelFont.Style, GraphicsUnit.Point); } // Failed to fit else { // Use last font if ((this.LabelAutoFitStyle & LabelAutoFitStyles.LabelsAngleStep30) == LabelAutoFitStyles.LabelsAngleStep30 || (this.LabelAutoFitStyle & LabelAutoFitStyles.LabelsAngleStep45) == LabelAutoFitStyles.LabelsAngleStep45 || (this.LabelAutoFitStyle & LabelAutoFitStyles.LabelsAngleStep90) == LabelAutoFitStyles.LabelsAngleStep90) { // Reset angle if (this.AxisPosition == AxisPosition.Top || this.AxisPosition == AxisPosition.Bottom) { autoLabelAngle = 90; } else { autoLabelAngle = 0; } } if ((this.LabelAutoFitStyle & LabelAutoFitStyles.StaggeredLabels) == LabelAutoFitStyles.StaggeredLabels) { // Reset offset labels autoLabelOffset = 0; } fitDone = true; } } else if (ChartArea.Area3DStyle.Enable3D && !ChartArea.chartAreaIsCurcular && autoLabelFont.SizeInPoints > _minLabelFontSize) { // Reduce auto-fit font by 1 for the 3D charts autoLabelFont = Common.Chart.chartPicture.FontCache.GetFont( autoLabelFont.FontFamily, autoLabelFont.SizeInPoints - 0.5f, autoLabelFont.Style, GraphicsUnit.Point); } } // Change the auto-fit angle for top and bottom axes from 90 to -90 if(this.AxisPosition == AxisPosition.Bottom || this.AxisPosition == AxisPosition.Top) { if(autoLabelAngle == 90) { autoLabelAngle = -90; } } } //********************************************************* //** Calculate overall labels size //********************************************************* this.labelSize = 0; // if labels are not enabled their size needs to remain zero if (this.LabelStyle.Enabled) { //****************************************************** //** Calculate axis second labels row size //****************************************************** this.labelSize = (maxAxisElementsSize) - this.markSize - this.scrollBarSize - this.titleSize; if (this.labelSize > 0) { this.groupingLabelSizes = GetRequiredGroupLabelSize(chartGraph, (maxLabelSize / 100F) * maxAxisLabelRow2Size); this.totlaGroupingLabelsSize = GetGroupLablesToatalSize(); } //****************************************************** //** Calculate axis labels size //****************************************************** this.labelSize -= this.totlaGroupingLabelsSize; if (this.labelSize > 0) { // If axis is horizontal if (this.AxisPosition == AxisPosition.Bottom || this.AxisPosition == AxisPosition.Top) { this.labelSize = elementSpacing + GetRequiredLabelSize(chartGraph, (maxLabelSize / 100F) * (maxAxisElementsSize - this.markSize - this.scrollBarSize - this.titleSize), out this.unRotatedLabelSize); } // If axis is horizontal else { this.labelSize = elementSpacing + GetRequiredLabelSize(chartGraph, (maxLabelSize / 100F) * (maxAxisElementsSize - this.markSize - this.scrollBarSize - this.titleSize), out this.unRotatedLabelSize); } if (!this.LabelStyle.Enabled) { this.labelSize -= elementSpacing; } } else { this.labelSize = 0; } this.labelSize += this.totlaGroupingLabelsSize; } #if SUBAXES // Calculate offsets for all sub axes if(!ChartArea.Area3DStyle.Enable3D && !ChartArea.chartAreaIsCurcular) { float currentOffset = this.markSize + this.labelSize + this.titleSize + this.scrollBarSize; foreach(SubAxis subAxis in this.SubAxes) { if(subAxis.Enabled != AxisEnabled.False) { currentOffset += (float)subAxis.LocationOffset; subAxis.offsetFromParent = currentOffset; currentOffset += subAxis.markSize + subAxis.labelSize + subAxis.titleSize; } } } #endif // SUBAXES // Restore previous invalidation flag Common.Chart.disableInvalidates = oldDisableInvalidates; } /// /// Calculates axis interval so that labels will fit most efficiently. /// /// Chart graphics. /// True if plot position is auto calculated. /// True if interval should only be increased. private void AdjustIntervalToFitLabels(ChartGraphics chartGraph, bool autoPlotPosition, bool onlyIncreaseInterval) { // Calculates axis interval so that labels will fit most efficiently. if(this.ScaleSegments.Count == 0) { this.AdjustIntervalToFitLabels(chartGraph, autoPlotPosition, null, onlyIncreaseInterval); } else { // Allow values to go outside the segment boundary this.ScaleSegments.AllowOutOfScaleValues = true; // Adjust interval of each segment first foreach(AxisScaleSegment axisScaleSegment in this.ScaleSegments) { this.AdjustIntervalToFitLabels(chartGraph, autoPlotPosition, axisScaleSegment, onlyIncreaseInterval); } // Fill labels using new segment intervals bool removeLabels = true; int segmentIndex = 0; ArrayList removedLabels = new ArrayList(); ArrayList removedLabelsIndexes = new ArrayList(); foreach(AxisScaleSegment scaleSegment in this.ScaleSegments) { scaleSegment.SetTempAxisScaleAndInterval(); this.FillLabels(removeLabels); removeLabels = false; scaleSegment.RestoreAxisScaleAndInterval(); // Remove last label of all segmenst except of the last if(segmentIndex < this.ScaleSegments.Count - 1 && this.CustomLabels.Count > 0) { // Remove label and save it in the list removedLabels.Add(this.CustomLabels[this.CustomLabels.Count - 1]); removedLabelsIndexes.Add(this.CustomLabels.Count - 1); this.CustomLabels.RemoveAt(this.CustomLabels.Count - 1); } ++segmentIndex; } // Check all previously removed last labels of each segment if there // is enough space to fit them int reInsertedLabelsCount = 0; int labelIndex = 0; foreach(CustomLabel label in removedLabels) { // Re-insert the label int labelInsertIndex = (int)removedLabelsIndexes[labelIndex] + reInsertedLabelsCount; if(labelIndex < this.CustomLabels.Count) { this.CustomLabels.Insert(labelInsertIndex, label); } else { this.CustomLabels.Add(label); } // Check labels fit. Only horizontal or vertical fit is checked depending // on the axis orientation. ArrayList labelPositions = new ArrayList(); bool fitDone = CheckLabelsFit( chartGraph, this.markSize + this.scrollBarSize + this.titleSize, autoPlotPosition, true, false, (this.AxisPosition == AxisPosition.Left || this.AxisPosition == AxisPosition.Right) ? false : true, (this.AxisPosition == AxisPosition.Left || this.AxisPosition == AxisPosition.Right) ? true : false, labelPositions); // If labels fit check if any of the label positions overlap if(fitDone) { for(int index = 0; fitDone && index < labelPositions.Count; index++) { RectangleF rect1 = (RectangleF)labelPositions[index]; for(int index2 = index + 1; fitDone && index2 < labelPositions.Count; index2++) { RectangleF rect2 = (RectangleF)labelPositions[index2]; if(rect1.IntersectsWith(rect2)) { fitDone = false; } } } } // If labels do not fit or overlapp - remove completly if(!fitDone) { this.CustomLabels.RemoveAt(labelInsertIndex); } else { ++reInsertedLabelsCount; } ++labelIndex; } // Make sure now values are rounded on segment boundary this.ScaleSegments.AllowOutOfScaleValues = false; } } /// /// Checks if variable count labels mode is enabled. /// /// True if variable count labels mode is enabled. private bool IsVariableLabelCountModeEnabled() { // Make sure the variable interval mode is enabled and // no custom label interval used. if( (this.IntervalAutoMode == IntervalAutoMode.VariableCount || this.ScaleSegments.Count > 0) && !this.IsLogarithmic && (this.tempLabelInterval <= 0.0 || (double.IsNaN(this.tempLabelInterval) && this.Interval <= 0.0)) ) { // This feature is not supported for charts that do not // require X and Y axes (Pie, Radar, ...) if(!ChartArea.requireAxes) { return false; } // This feature is not supported if the axis doesn't have data range if (Double.IsNaN(this.minimum) || Double.IsNaN(this.maximum)) { return false; } // Check if custom labels are used in the first row bool customLabels = false; foreach(CustomLabel label in this.CustomLabels) { if(label.customLabel && label.RowIndex == 0) { customLabels = true; break; } } // Proceed only if no custom labels are used in the first row if(!customLabels) { return true; } } return false; } /// /// Calculates axis interval so that labels will fit most efficiently. /// /// Chart graphics. /// True if plot position is auto calculated. /// Axis scale segment to process. /// True if interval should only be increased. private void AdjustIntervalToFitLabels( ChartGraphics chartGraph, bool autoPlotPosition, AxisScaleSegment axisScaleSegment, bool onlyIncreaseInterval) { // Re-fill the labels just for the scale segment provided if(axisScaleSegment != null) { // Re-fill new axis labels if(this.tempLabels != null) { this.CustomLabels.Clear(); foreach( CustomLabel label in this.tempLabels ) { this.CustomLabels.Add(label.Clone()); } } // Fill labels just for the segment axisScaleSegment.SetTempAxisScaleAndInterval(); this.FillLabels( true ); axisScaleSegment.RestoreAxisScaleAndInterval(); } // Calculate minimum interval size double minIntervalSzie = double.NaN; ArrayList axisSeries = AxisScaleBreakStyle.GetAxisSeries(this); foreach(Series series in axisSeries) { if(this.axisType == AxisName.X || this.axisType == AxisName.X2) { if(ChartHelper.IndexedSeries(series)) { minIntervalSzie = 1.0; } else if(series.XValueType == ChartValueType.String || series.XValueType == ChartValueType.Int32 || series.XValueType == ChartValueType.UInt32 || series.XValueType == ChartValueType.UInt64 || series.XValueType == ChartValueType.Int64 ) { minIntervalSzie = 1.0; } } else { if(series.YValueType == ChartValueType.String || series.YValueType == ChartValueType.Int32 || series.YValueType == ChartValueType.UInt32 || series.YValueType == ChartValueType.UInt64 || series.YValueType == ChartValueType.Int64 ) { minIntervalSzie = 1.0; } } } // Iterate while interval is not found bool firstIteration = true; bool increaseNumberOfLabels = true; double currentInterval = (axisScaleSegment == null) ? this.labelStyle.GetInterval() : axisScaleSegment.Interval; DateTimeIntervalType currentIntervalType = (axisScaleSegment == null) ? this.labelStyle.GetIntervalType() : axisScaleSegment.IntervalType; DateTimeIntervalType lastFitIntervalType = currentIntervalType; double lastFitInterval = currentInterval; ArrayList lastFitLabels = new ArrayList(); bool intervalFound = false; int iterationNumber = 0; while(!intervalFound && iterationNumber <= 1000) { bool fillNewLabels = true; #if DEBUG if(iterationNumber >= 999) { throw (new InvalidOperationException(SR.ExceptionAxisDynamicIntervalCalculationFailed)); } #endif // DEBUG // Check labels fit. Only horizontal or vertical fit is checked depending // on the axis orientation. bool fitDone = CheckLabelsFit( chartGraph, this.markSize + this.scrollBarSize + this.titleSize, autoPlotPosition, true, false, (this.AxisPosition == AxisPosition.Left || this.AxisPosition == AxisPosition.Right) ? false : true, (this.AxisPosition == AxisPosition.Left || this.AxisPosition == AxisPosition.Right) ? true : false, null); // Check if we need to increase or reduce number of labels if(firstIteration) { firstIteration = false; increaseNumberOfLabels = (fitDone) ? true : false; // Check if we can decrease the interva; if(onlyIncreaseInterval && increaseNumberOfLabels) { intervalFound = true; continue; } } // Find new interval. Value 0.0 means that interval cannot be // reduced/increased any more and current interval should be used double newInterval = 0.0; DateTimeIntervalType newIntervalType = DateTimeIntervalType.Number; if(increaseNumberOfLabels) { if(fitDone) { // Make a copy of last interval and labels collection that previously fit lastFitInterval = currentInterval; lastFitIntervalType = currentIntervalType; lastFitLabels.Clear(); foreach(CustomLabel label in this.CustomLabels) { lastFitLabels.Add(label); } newIntervalType = currentIntervalType; newInterval = this.ReduceLabelInterval( currentInterval, minIntervalSzie, ref newIntervalType); } else { newInterval = lastFitInterval; newIntervalType = lastFitIntervalType; intervalFound = true; // Reuse previously saved labels fillNewLabels = false; this.CustomLabels.Clear(); foreach(CustomLabel label in lastFitLabels) { this.CustomLabels.Add(label); } } } else { if(!fitDone && this.CustomLabels.Count > 1) { newIntervalType = currentIntervalType; newInterval = this.IncreaseLabelInterval( currentInterval, ref newIntervalType); } else { intervalFound = true; } } // Set new interval if(newInterval != 0.0) { currentInterval = newInterval; currentIntervalType = newIntervalType; if(axisScaleSegment == null) { this.SetIntervalAndType(newInterval, newIntervalType); } else { axisScaleSegment.Interval = newInterval; axisScaleSegment.IntervalType = newIntervalType; } // Re-fill new axis labels if(fillNewLabels) { if(this.tempLabels != null) { this.CustomLabels.Clear(); foreach( CustomLabel label in this.tempLabels ) { CustomLabels.Add(label.Clone()); } } if(axisScaleSegment == null) { this.FillLabels(true); } else { axisScaleSegment.SetTempAxisScaleAndInterval(); this.FillLabels( true ); axisScaleSegment.RestoreAxisScaleAndInterval(); } } } else { intervalFound = true; } ++iterationNumber; } } /// /// Reduces current label interval, so that more labels can fit. /// /// An interval to reduce. /// Minimum interval size. /// Interval type. /// New interval or 0.0 if interval cannot be reduced. private double ReduceLabelInterval( double oldInterval, double minInterval, ref DateTimeIntervalType axisIntervalType) { double newInterval = oldInterval; // Calculate rounded interval value double range = this.maximum - this.minimum; int iterationIndex = 0; if( axisIntervalType == DateTimeIntervalType.Auto || axisIntervalType == DateTimeIntervalType.NotSet || axisIntervalType == DateTimeIntervalType.Number) { // Process numeric scale double devider = 2.0; do { #if DEBUG if(iterationIndex >= 99) { throw (new InvalidOperationException(SR.ExceptionAxisIntervalDecreasingFailed)); } #endif // DEBUG newInterval = CalcInterval( range / (range / (newInterval / devider)) ); if(newInterval == oldInterval) { devider *= 2.0; } ++iterationIndex; } while(newInterval == oldInterval && iterationIndex <= 100); } else { // Process date scale if(oldInterval > 1.0 || oldInterval < 1.0) { if( axisIntervalType == DateTimeIntervalType.Minutes || axisIntervalType == DateTimeIntervalType.Seconds) { if(oldInterval >= 60) { newInterval = Math.Round(oldInterval / 2.0); } else if(oldInterval >= 30.0) { newInterval = 15.0; } else if(oldInterval >= 15.0) { newInterval = 5.0; } else if(oldInterval >= 5.0) { newInterval = 1.0; } } else { newInterval = Math.Round(oldInterval / 2.0); } if(newInterval < 1.0) { newInterval = 1.0; } } if(oldInterval == 1.0) { if(axisIntervalType == DateTimeIntervalType.Years) { newInterval = 6.0; axisIntervalType = DateTimeIntervalType.Months; } else if(axisIntervalType == DateTimeIntervalType.Months) { newInterval = 2.0; axisIntervalType = DateTimeIntervalType.Weeks; } else if(axisIntervalType == DateTimeIntervalType.Weeks) { newInterval = 2.0; axisIntervalType = DateTimeIntervalType.Days; } else if(axisIntervalType == DateTimeIntervalType.Days) { newInterval = 12.0; axisIntervalType = DateTimeIntervalType.Hours; } else if(axisIntervalType == DateTimeIntervalType.Hours) { newInterval = 30.0; axisIntervalType = DateTimeIntervalType.Minutes; } else if(axisIntervalType == DateTimeIntervalType.Minutes) { newInterval = 30.0; axisIntervalType = DateTimeIntervalType.Seconds; } else if(axisIntervalType == DateTimeIntervalType.Seconds) { newInterval = 100.0; axisIntervalType = DateTimeIntervalType.Milliseconds; } } } // Make sure interal is not less than min interval specified if(!double.IsNaN(minInterval) && newInterval < minInterval) { newInterval = 0.0; } return newInterval; } /// /// Increases current label interval, so that less labels fit. /// /// An interval to increase. /// Interval type. /// New interval or 0.0 if interval cannot be increased. private double IncreaseLabelInterval( double oldInterval, ref DateTimeIntervalType axisIntervalType) { double newInterval = oldInterval; // Calculate rounded interval value double range = this.maximum - this.minimum; int iterationIndex = 0; if( axisIntervalType == DateTimeIntervalType.Auto || axisIntervalType == DateTimeIntervalType.NotSet || axisIntervalType == DateTimeIntervalType.Number) { // Process numeric scale double devider = 2.0; do { #if DEBUG if(iterationIndex >= 99) { throw (new InvalidOperationException(SR.ExceptionAxisIntervalIncreasingFailed)); } #endif // DEBUG newInterval = CalcInterval( range / (range / (newInterval * devider)) ); if(newInterval == oldInterval) { devider *= 2.0; } ++iterationIndex; } while(newInterval == oldInterval && iterationIndex <= 100); } else { // Process date scale newInterval = oldInterval * 2.0; if(axisIntervalType == DateTimeIntervalType.Years) { // Do nothing for years } else if(axisIntervalType == DateTimeIntervalType.Months) { if(newInterval >= 12.0) { newInterval = 1.0; axisIntervalType = DateTimeIntervalType.Years; } } else if(axisIntervalType == DateTimeIntervalType.Weeks) { if(newInterval >= 4.0) { newInterval = 1.0; axisIntervalType = DateTimeIntervalType.Months; } } else if(axisIntervalType == DateTimeIntervalType.Days) { if(newInterval >= 7.0) { newInterval = 1.0; axisIntervalType = DateTimeIntervalType.Weeks; } } else if(axisIntervalType == DateTimeIntervalType.Hours) { if(newInterval >= 60.0) { newInterval = 1.0; axisIntervalType = DateTimeIntervalType.Days; } } else if(axisIntervalType == DateTimeIntervalType.Minutes) { if(newInterval >= 60.0) { newInterval = 1.0; axisIntervalType = DateTimeIntervalType.Hours; } } else if(axisIntervalType == DateTimeIntervalType.Seconds) { if(newInterval >= 60.0) { newInterval = 1.0; axisIntervalType = DateTimeIntervalType.Minutes; } } else if(axisIntervalType == DateTimeIntervalType.Milliseconds) { if(newInterval >= 1000.0) { newInterval = 1.0; axisIntervalType = DateTimeIntervalType.Seconds; } } } return newInterval; } /// /// Finds the longest labels with the space and inserts the new line character. /// /// Labels collection. /// True if collection was modified. private bool WordWrapLongestLabel(CustomLabelsCollection labels) { bool changed = false; // Each label may contain several lines of text. // Create a list that contains an array of text for each label. ArrayList labelTextRows = new ArrayList(labels.Count); foreach (CustomLabel label in labels) { labelTextRows.Add(label.Text.Split('\n')); } // Find the longest label with a space int longestLabelSize = 5; int longestLabelIndex = -1; int longestLabelRowIndex = -1; int index = 0; foreach (string[] textRows in labelTextRows) { for (int rowIndex = 0; rowIndex < textRows.Length; rowIndex++) { if (textRows[rowIndex].Length > longestLabelSize && textRows[rowIndex].Trim().IndexOf(' ') > 0) { longestLabelSize = textRows[rowIndex].Length; longestLabelIndex = index; longestLabelRowIndex = rowIndex; } } ++index; } // Longest label with a space was found if (longestLabelIndex >= 0 && longestLabelRowIndex >= 0) { // Try to find a space and replace it with a new line string newText = ((string[])labelTextRows[longestLabelIndex])[longestLabelRowIndex]; for (index = 0; index < (newText.Length) / 2 - 1; index++) { if (newText[(newText.Length) / 2 - index] == ' ') { newText = newText.Substring(0, (newText.Length) / 2 - index) + "\n" + newText.Substring((newText.Length) / 2 - index + 1); changed = true; } else if (newText[(newText.Length) / 2 + index] == ' ') { newText = newText.Substring(0, (newText.Length) / 2 + index) + "\n" + newText.Substring((newText.Length) / 2 + index + 1); changed = true; } if (changed) { ((string[])labelTextRows[longestLabelIndex])[longestLabelRowIndex] = newText; break; } } // Update label text if (changed) { // Construct label text from multiple rows separated by "\n" CustomLabel label = labels[longestLabelIndex]; label.Text = string.Empty; for (int rowIndex = 0; rowIndex < ((string[])labelTextRows[longestLabelIndex]).Length; rowIndex++) { if (rowIndex > 0) { label.Text += "\n"; } label.Text += ((string[])labelTextRows[longestLabelIndex])[rowIndex]; } } } return changed; } /// /// Calculates the auto-fit font for the circular Common.Chart area axis labels. /// /// Chart graphics object. /// List of sector labels. /// Circular labels style. /// Plotting area position. /// Chart area position. /// Estimated size of labels. internal void GetCircularAxisLabelsAutoFitFont( ChartGraphics graph, ArrayList axisList, CircularAxisLabelsStyle labelsStyle, RectangleF plotAreaRectAbs, RectangleF areaRectAbs, float labelsSizeEstimate) { // X axis settings defines if auto-fit font should be calculated if (!this.IsLabelAutoFit || this.LabelAutoFitStyle == LabelAutoFitStyles.None || !this.LabelStyle.Enabled) { return; } // Set minimum font size _minLabelFontSize = Math.Min(this.LabelAutoFitMinFontSize, this.LabelAutoFitMaxFontSize); // Create new auto-fit font this.autoLabelFont = Common.Chart.chartPicture.FontCache.GetFont( this.LabelStyle.Font.FontFamily, Math.Max(this.LabelAutoFitMaxFontSize, this.LabelAutoFitMinFontSize), this.LabelStyle.Font.Style, GraphicsUnit.Point); // Check if we allowed to increase font size while auto-fitting if ((this.LabelAutoFitStyle & LabelAutoFitStyles.IncreaseFont) != LabelAutoFitStyles.IncreaseFont) { // Use axis labels font as starting point this.autoLabelFont = this.LabelStyle.Font; } // Loop while labels do not fit bool fitDone = false; while (!fitDone) { //****************************************************** //** Check if labels fit //****************************************************** fitDone = CheckCircularLabelsFit( graph, axisList, labelsStyle, plotAreaRectAbs, areaRectAbs, labelsSizeEstimate); //****************************************************** //** Adjust labels text properties to fit //****************************************************** if (!fitDone) { // Try to reduce font size if (autoLabelFont.SizeInPoints > _minLabelFontSize && (this.LabelAutoFitStyle & LabelAutoFitStyles.DecreaseFont) == LabelAutoFitStyles.DecreaseFont) { autoLabelFont = Common.Chart.chartPicture.FontCache.GetFont( autoLabelFont.FontFamily, autoLabelFont.SizeInPoints - 1, autoLabelFont.Style, GraphicsUnit.Point); } // Failed to fit else { // Use last font with no angles autoLabelAngle = 0; autoLabelOffset = 0; fitDone = true; } } } } /// /// Checks id circular axis labels fits using current auto-fit font. /// /// Chart graphics object. /// List of sector labels. /// Circular labels style. /// Plotting area position. /// Chart area position. /// Estimated size of labels. /// True if labels fit. internal bool CheckCircularLabelsFit( ChartGraphics graph, ArrayList axisList, CircularAxisLabelsStyle labelsStyle, RectangleF plotAreaRectAbs, RectangleF areaRectAbs, float labelsSizeEstimate) { bool labelsFit = true; // Get absolute center of the area PointF areaCenterAbs = graph.GetAbsolutePoint(ChartArea.circularCenter); // Get absolute markers size and spacing float spacing = graph.GetAbsolutePoint(new PointF(0, this.markSize + Axis.elementSpacing)).Y; //***************************************************************** //** Loop through all axis labels //***************************************************************** RectangleF prevLabelPosition = RectangleF.Empty; float prevLabelSideAngle = float.NaN; foreach (CircularChartAreaAxis axis in axisList) { //***************************************************************** //** Measure label text //***************************************************************** SizeF textSize = graph.MeasureString( axis.Title.Replace("\\n", "\n"), this.autoLabelFont); //***************************************************************** //** Get circular style label position. //***************************************************************** if (labelsStyle == CircularAxisLabelsStyle.Circular || labelsStyle == CircularAxisLabelsStyle.Radial) { // Swith text size for the radial style if (labelsStyle == CircularAxisLabelsStyle.Radial) { float tempValue = textSize.Width; textSize.Width = textSize.Height; textSize.Height = tempValue; } //***************************************************************** //** Check overlapping with previous label //***************************************************************** // Get radius of plot area float plotAreaRadius = areaCenterAbs.Y - plotAreaRectAbs.Y; plotAreaRadius -= labelsSizeEstimate; plotAreaRadius += spacing; // Calculate angle on the side of the label float leftSideAngle = (float)(Math.Atan((textSize.Width / 2f) / plotAreaRadius) * 180f / Math.PI); float rightSideAngle = axis.AxisPosition + leftSideAngle; leftSideAngle = axis.AxisPosition - leftSideAngle; // Check if label overlap the previous label if (!float.IsNaN(prevLabelSideAngle)) { if (prevLabelSideAngle > leftSideAngle) { // Labels overlap labelsFit = false; break; } } // Remember label side angle prevLabelSideAngle = rightSideAngle - 1; //***************************************************************** //** Check if label is inside the Common.Chart area //***************************************************************** // Find the most outside point of the label PointF outsidePoint = new PointF(areaCenterAbs.X, plotAreaRectAbs.Y); outsidePoint.Y += labelsSizeEstimate; outsidePoint.Y -= textSize.Height; outsidePoint.Y -= spacing; PointF[] rotatedPoint = new PointF[] { outsidePoint }; Matrix newMatrix = new Matrix(); newMatrix.RotateAt(axis.AxisPosition, areaCenterAbs); newMatrix.TransformPoints(rotatedPoint); // Check if rotated point is inside Common.Chart area if (!areaRectAbs.Contains(rotatedPoint[0])) { // Label is not inside Common.Chart area labelsFit = false; break; } } //***************************************************************** //** Get horizontal style label position. //***************************************************************** else if (labelsStyle == CircularAxisLabelsStyle.Horizontal) { // Get text angle float textAngle = axis.AxisPosition; if (textAngle > 180f) { textAngle -= 180f; } // Get label rotated position PointF[] labelPosition = new PointF[] { new PointF(areaCenterAbs.X, plotAreaRectAbs.Y) }; labelPosition[0].Y += labelsSizeEstimate; labelPosition[0].Y -= spacing; Matrix newMatrix = new Matrix(); newMatrix.RotateAt(textAngle, areaCenterAbs); newMatrix.TransformPoints(labelPosition); // Calculate label position RectangleF curLabelPosition = new RectangleF( labelPosition[0].X, labelPosition[0].Y - textSize.Height / 2f, textSize.Width, textSize.Height); if (textAngle < 5f) { curLabelPosition.X = labelPosition[0].X - textSize.Width / 2f; curLabelPosition.Y = labelPosition[0].Y - textSize.Height; } if (textAngle > 175f) { curLabelPosition.X = labelPosition[0].X - textSize.Width / 2f; curLabelPosition.Y = labelPosition[0].Y; } // Decrease label rectangle curLabelPosition.Inflate(0f, -curLabelPosition.Height * 0.15f); // Check if label position goes outside of the Common.Chart area. if (!areaRectAbs.Contains(curLabelPosition)) { // Label is not inside Common.Chart area labelsFit = false; break; } // Check if label position overlap previous label position. if (!prevLabelPosition.IsEmpty && curLabelPosition.IntersectsWith(prevLabelPosition)) { // Label intersects with previous label labelsFit = false; break; } // Set previous point position prevLabelPosition = curLabelPosition; } } return labelsFit; } #endregion #region Axis labels auto-fitting methods /// /// Adjust labels font size at second pass of auto fitting. /// /// Chart graphics object. /// Indicates that inner plot position is automatic. internal void AdjustLabelFontAtSecondPass(ChartGraphics chartGraph, bool autoPlotPosition) { #if SUBAXES // Process all sub-axis if(!ChartArea.Area3DStyle.Enable3D && !ChartArea.chartAreaIsCurcular) { foreach(SubAxis subAxis in this.SubAxes) { subAxis.AdjustLabelFontAtSecondPass(chartGraph, autoPlotPosition); } } #endif //SUBAXES //****************************************************** //** First try to select the interval that will //** generate best fit labels. //****************************************************** // Make sure the variable interval mode is enabled if( this.Enabled != AxisEnabled.False && this.LabelStyle.Enabled && this.IsVariableLabelCountModeEnabled() ) { // Set font for labels fitting if(this.autoLabelFont == null) { this.autoLabelFont = this.LabelStyle.Font; } // Reset angle and stagged flag used in the auto-fitting algorithm if(this.autoLabelAngle < 0) { this.autoLabelAngle = this.LabelStyle.Angle; } if(this.autoLabelOffset < 0) { this.autoLabelOffset = (this.LabelStyle.IsStaggered) ? 1 : 0; } // Check labels fit bool fitDone = CheckLabelsFit( chartGraph, this.markSize + this.scrollBarSize + this.titleSize, autoPlotPosition, true, true, (this.AxisPosition == AxisPosition.Left || this.AxisPosition == AxisPosition.Right) ? false : true, (this.AxisPosition == AxisPosition.Left || this.AxisPosition == AxisPosition.Right) ? true : false, null); // If there is a problem fitting labels try to reduce number of labels by // increasing of the interval. if(!fitDone) { // Adjust interval this.AdjustIntervalToFitLabels(chartGraph, autoPlotPosition, true); } } //****************************************************** //** If labels auto-fit is on try reducing font size. //****************************************************** totlaGroupingLabelsSizeAdjustment = 0f; if (this.IsLabelAutoFit && this.LabelAutoFitStyle != LabelAutoFitStyles.None && this.Enabled != AxisEnabled.False) { bool fitDone = false; if (autoLabelFont == null) { autoLabelFont = this.LabelStyle.Font; } // Loop while labels do not fit float oldLabelSecondRowSize = totlaGroupingLabelsSize; while (!fitDone) { //****************************************************** //** Check if labels fit //****************************************************** fitDone = CheckLabelsFit( chartGraph, this.markSize + this.scrollBarSize + this.titleSize, autoPlotPosition, true, true); //****************************************************** //** Adjust labels text properties to fit //****************************************************** if (!fitDone) { // Try to reduce font if (autoLabelFont.SizeInPoints > _minLabelFontSize) { // Reduce auto fit font if (ChartArea != null && ChartArea.IsSameFontSizeForAllAxes) { // Same font for all axes foreach (Axis currentAxis in ChartArea.Axes) { if (currentAxis.enabled && currentAxis.IsLabelAutoFit && currentAxis.autoLabelFont != null) { currentAxis.autoLabelFont = Common.Chart.chartPicture.FontCache.GetFont( currentAxis.autoLabelFont.FontFamily, autoLabelFont.SizeInPoints - 1, currentAxis.autoLabelFont.Style, GraphicsUnit.Point); } } } else if ((this.LabelAutoFitStyle & LabelAutoFitStyles.DecreaseFont) == LabelAutoFitStyles.DecreaseFont) { autoLabelFont = Common.Chart.chartPicture.FontCache.GetFont( autoLabelFont.FontFamily, autoLabelFont.SizeInPoints - 1, autoLabelFont.Style, GraphicsUnit.Point); } else { // Failed to fit fitDone = true; } } else { // Failed to fit fitDone = true; } } } this.totlaGroupingLabelsSizeAdjustment = oldLabelSecondRowSize - totlaGroupingLabelsSize; } } /// /// Check if axis is logarithmic /// /// Y value from data /// Corected Y value if axis is logarithmic internal double GetLogValue(double yValue) { // Check if axis is logarithmic if (this.IsLogarithmic) { yValue = Math.Log(yValue, this.logarithmBase); } return yValue; } /// /// Checks if labels fit using current auto fit properties /// /// Chart graphics object. /// Axis title and marks size. /// Indicates auto calculation of plotting area. /// Labels fit is checked during the second pass. /// Indicates second pass of labels fitting. /// True if labels fit. private bool CheckLabelsFit( ChartGraphics chartGraph, float otherElementsSize, bool autoPlotPosition, bool checkLabelsFirstRowOnly, bool secondPass) { return this.CheckLabelsFit( chartGraph, otherElementsSize, autoPlotPosition, checkLabelsFirstRowOnly, secondPass, true, true, null); } /// /// Checks if labels fit using current auto fit properties /// /// Chart graphics object. /// Axis title and marks size. /// Indicates auto calculation of plotting area. /// Labels fit is checked during the second pass. /// Indicates second pass of labels fitting. /// True if width should be checked. /// True if height should be checked. /// Returns an array of label positions. /// True if labels fit. private bool CheckLabelsFit( ChartGraphics chartGraph, float otherElementsSize, bool autoPlotPosition, bool checkLabelsFirstRowOnly, bool secondPass, bool checkWidth, bool checkHeight, ArrayList labelPositions) { // Reset list of label positions if (labelPositions != null) { labelPositions.Clear(); } // Label string drawing format using (StringFormat format = new StringFormat()) { format.FormatFlags |= StringFormatFlags.LineLimit; format.Trimming = StringTrimming.EllipsisCharacter; // Initialize all labels position rectangle RectangleF rect = RectangleF.Empty; // Calculate max label size float maxLabelSize = 0; if (!autoPlotPosition) { if (this.GetIsMarksNextToAxis()) { if (this.AxisPosition == AxisPosition.Top) maxLabelSize = (float)GetAxisPosition() - ChartArea.Position.Y; else if (this.AxisPosition == AxisPosition.Bottom) maxLabelSize = ChartArea.Position.Bottom - (float)GetAxisPosition(); if (this.AxisPosition == AxisPosition.Left) maxLabelSize = (float)GetAxisPosition() - ChartArea.Position.X; else if (this.AxisPosition == AxisPosition.Right) maxLabelSize = ChartArea.Position.Right - (float)GetAxisPosition(); } else { if (this.AxisPosition == AxisPosition.Top) maxLabelSize = this.PlotAreaPosition.Y - ChartArea.Position.Y; else if (this.AxisPosition == AxisPosition.Bottom) maxLabelSize = ChartArea.Position.Bottom - this.PlotAreaPosition.Bottom; if (this.AxisPosition == AxisPosition.Left) maxLabelSize = this.PlotAreaPosition.X - ChartArea.Position.X; else if (this.AxisPosition == AxisPosition.Right) maxLabelSize = ChartArea.Position.Right - this.PlotAreaPosition.Right; } maxLabelSize *= 2F; } else { if (this.AxisPosition == AxisPosition.Bottom || this.AxisPosition == AxisPosition.Top) maxLabelSize = ChartArea.Position.Height; else maxLabelSize = ChartArea.Position.Width; } // Loop through all grouping labels (all except first row) this.totlaGroupingLabelsSize = 0; // Get number of groups int groupLabelLevelCount = GetGroupLabelLevelCount(); // Check ig grouping labels exist if (groupLabelLevelCount > 0) { groupingLabelSizes = new float[groupLabelLevelCount]; // Loop through each level of grouping labels bool fitResult = true; for (int groupLevelIndex = 1; groupLevelIndex <= groupLabelLevelCount; groupLevelIndex++) { groupingLabelSizes[groupLevelIndex - 1] = 0f; // Loop through all labels in the level foreach (CustomLabel label in this.CustomLabels) { // Skip if label middle point is outside current scaleView if (label.RowIndex == 0) { double middlePoint = (label.FromPosition + label.ToPosition) / 2.0; if (middlePoint < this.ViewMinimum || middlePoint > this.ViewMaximum) { continue; } } if (label.RowIndex == groupLevelIndex) { // Calculate label rect double fromPosition = this.GetLinearPosition(label.FromPosition); double toPosition = this.GetLinearPosition(label.ToPosition); if (this.AxisPosition == AxisPosition.Bottom || this.AxisPosition == AxisPosition.Top) { rect.Height = (maxLabelSize / 100F) * maxAxisLabelRow2Size / groupLabelLevelCount; rect.X = (float)Math.Min(fromPosition, toPosition); rect.Width = (float)Math.Max(fromPosition, toPosition) - rect.X; } else { rect.Width = (maxLabelSize / 100F) * maxAxisLabelRow2Size / groupLabelLevelCount; rect.Y = (float)Math.Min(fromPosition, toPosition); rect.Height = (float)Math.Max(fromPosition, toPosition) - rect.Y; } // Measure string SizeF axisLabelSize = chartGraph.MeasureStringRel(label.Text.Replace("\\n", "\n"), autoLabelFont); // Add image size if (label.Image.Length > 0) { SizeF imageAbsSize = new SizeF(); if (this.Common.ImageLoader.GetAdjustedImageSize(label.Image, chartGraph.Graphics, ref imageAbsSize)) { SizeF imageRelSize = chartGraph.GetRelativeSize(imageAbsSize); axisLabelSize.Width += imageRelSize.Width; axisLabelSize.Height = Math.Max(axisLabelSize.Height, imageRelSize.Height); } } // Add extra spacing for the box marking of the label if (label.LabelMark == LabelMarkStyle.Box) { // Get relative size from pixels and add it to the label size SizeF spacerSize = chartGraph.GetRelativeSize(new SizeF(4, 4)); axisLabelSize.Width += spacerSize.Width; axisLabelSize.Height += spacerSize.Height; } // Calculate max height of the second row of labels if (this.AxisPosition == AxisPosition.Bottom || this.AxisPosition == AxisPosition.Top) { groupingLabelSizes[groupLevelIndex - 1] = (float)Math.Max(groupingLabelSizes[groupLevelIndex - 1], axisLabelSize.Height); } else { axisLabelSize.Width = chartGraph.GetAbsoluteSize(new SizeF(axisLabelSize.Height, axisLabelSize.Height)).Height; axisLabelSize.Width = chartGraph.GetRelativeSize(new SizeF(axisLabelSize.Width, axisLabelSize.Width)).Width; groupingLabelSizes[groupLevelIndex - 1] = (float)Math.Max(groupingLabelSizes[groupLevelIndex - 1], axisLabelSize.Width); } // Check if string fits if (Math.Round(axisLabelSize.Width) >= Math.Round(rect.Width) && checkWidth) { fitResult = false; } if (Math.Round(axisLabelSize.Height) >= Math.Round(rect.Height) && checkHeight) { fitResult = false; } } } } this.totlaGroupingLabelsSize = this.GetGroupLablesToatalSize(); if (!fitResult && !checkLabelsFirstRowOnly) { return false; } } // Loop through all labels in the first row float angle = autoLabelAngle; int labelIndex = 0; foreach (CustomLabel label in this.CustomLabels) { // Skip if label middle point is outside current scaleView if (label.RowIndex == 0) { double middlePoint = (label.FromPosition + label.ToPosition) / 2.0; if (middlePoint < this.ViewMinimum || middlePoint > this.ViewMaximum) { continue; } } if (label.RowIndex == 0) { // Force which scale segment to use when calculating label position if (labelPositions != null) { this.ScaleSegments.EnforceSegment(this.ScaleSegments.FindScaleSegmentForAxisValue((label.FromPosition + label.ToPosition) / 2.0)); } // Set label From and To coordinates double fromPosition = this.GetLinearPosition(label.FromPosition); double toPosition = this.GetLinearPosition(label.ToPosition); // Reset scale segment to use when calculating label position if (labelPositions != null) { this.ScaleSegments.EnforceSegment(null); } // Calculate single label position rect.X = this.PlotAreaPosition.X; rect.Y = (float)Math.Min(fromPosition, toPosition); rect.Height = (float)Math.Max(fromPosition, toPosition) - rect.Y; float maxElementSize = maxAxisElementsSize; if (maxAxisElementsSize - this.totlaGroupingLabelsSize > 55) { maxElementSize = 55 + this.totlaGroupingLabelsSize; } if (this.AxisPosition == AxisPosition.Bottom || this.AxisPosition == AxisPosition.Top) { rect.Width = (maxLabelSize / 100F) * (maxElementSize - this.totlaGroupingLabelsSize - otherElementsSize - elementSpacing); } else { rect.Width = (maxLabelSize / 100F) * (maxElementSize - this.totlaGroupingLabelsSize - otherElementsSize - elementSpacing); } // Adjust label From/To position if labels are displayed with offset if (autoLabelOffset == 1) { rect.Y -= rect.Height / 2F; rect.Height *= 2F; rect.Width /= 2F; } // If horizontal axis if (this.AxisPosition == AxisPosition.Bottom || this.AxisPosition == AxisPosition.Top) { // Switch rectangle sizes float val = rect.Height; rect.Height = rect.Width; rect.Width = val; // Set vertical font for measuring if (angle != 0) { format.FormatFlags |= StringFormatFlags.DirectionVertical; } } else { // Set vertical font for measuring if (angle == 90 || angle == -90) { angle = 0; format.FormatFlags |= StringFormatFlags.DirectionVertical; } } // Measure label text size. Add the 'I' character to allow a little bit of spacing between labels. SizeF axisLabelSize = chartGraph.MeasureStringRel( label.Text.Replace("\\n", "\n") + "W", autoLabelFont, (secondPass) ? rect.Size : ChartArea.Position.ToRectangleF().Size, format); // Width and height maybe zeros if rect is too small to fit the text and // the LineLimit format flag is set. if (label.Text.Length > 0 && (axisLabelSize.Width == 0f || axisLabelSize.Height == 0f)) { // Measure string without the LineLimit flag format.FormatFlags ^= StringFormatFlags.LineLimit; axisLabelSize = chartGraph.MeasureStringRel( label.Text.Replace("\\n", "\n"), autoLabelFont, (secondPass) ? rect.Size : ChartArea.Position.ToRectangleF().Size, format); format.FormatFlags |= StringFormatFlags.LineLimit; } // Add image size if (label.Image.Length > 0) { SizeF imageAbsSize = new SizeF(); if(this.Common.ImageLoader.GetAdjustedImageSize(label.Image, chartGraph.Graphics, ref imageAbsSize)) { SizeF imageRelSize = chartGraph.GetRelativeSize(imageAbsSize); if ((format.FormatFlags & StringFormatFlags.DirectionVertical) == StringFormatFlags.DirectionVertical) { axisLabelSize.Height += imageRelSize.Height; axisLabelSize.Width = Math.Max(axisLabelSize.Width, imageRelSize.Width); } else { axisLabelSize.Width += imageRelSize.Width; axisLabelSize.Height = Math.Max(axisLabelSize.Height, imageRelSize.Height); } } } // Add extra spacing for the box marking of the label if (label.LabelMark == LabelMarkStyle.Box) { // Get relative size from pixels and add it to the label size SizeF spacerSize = chartGraph.GetRelativeSize(new SizeF(4, 4)); axisLabelSize.Width += spacerSize.Width; axisLabelSize.Height += spacerSize.Height; } // Calculate size using label angle float width = axisLabelSize.Width; float height = axisLabelSize.Height; if (angle != 0) { // Decrease label rectangle width by 3% rect.Width *= 0.97f; if (this.AxisPosition == AxisPosition.Bottom || this.AxisPosition == AxisPosition.Top) { width = (float)Math.Cos((Math.Abs(angle)) / 180F * Math.PI) * axisLabelSize.Height; width += (float)Math.Sin((Math.Abs(angle)) / 180F * Math.PI) * axisLabelSize.Width; height = (float)Math.Sin((Math.Abs(angle)) / 180F * Math.PI) * axisLabelSize.Height; height += (float)Math.Cos((Math.Abs(angle)) / 180F * Math.PI) * axisLabelSize.Width; } else { width = (float)Math.Cos((Math.Abs(angle)) / 180F * Math.PI) * axisLabelSize.Width; width += (float)Math.Sin((Math.Abs(angle)) / 180F * Math.PI) * axisLabelSize.Height; height = (float)Math.Sin((Math.Abs(angle)) / 180F * Math.PI) * axisLabelSize.Width; height += (float)Math.Cos((Math.Abs(angle)) / 180F * Math.PI) * axisLabelSize.Height; } } // Save label position if (labelPositions != null) { RectangleF labelPosition = rect; if (angle == 0F || angle == 90F || angle == -90F) { if (this.AxisPosition == AxisPosition.Bottom || this.AxisPosition == AxisPosition.Top) { labelPosition.X = labelPosition.X + labelPosition.Width / 2f - width / 2f; labelPosition.Width = width; } else { labelPosition.Y = labelPosition.Y + labelPosition.Height / 2f - height / 2f; labelPosition.Height = height; } } labelPositions.Add(labelPosition); } // Check if string fits if (angle == 0F) { if (width >= rect.Width && checkWidth) { return false; } if (height >= rect.Height && checkHeight) { return false; } } if (angle == 90F || angle == -90F) { if (width >= rect.Width && checkWidth) { return false; } if (height >= rect.Height && checkHeight) { return false; } } else { if (this.AxisPosition == AxisPosition.Bottom || this.AxisPosition == AxisPosition.Top) { if (width >= rect.Width * 2F && checkWidth) { return false; } if (height >= rect.Height * 2F && checkHeight) { return false; } } else { if (width >= rect.Width * 2F && checkWidth) { return false; } if (height >= rect.Height * 2F && checkHeight) { return false; } } } ++labelIndex; } } } return true; } /// /// Calculates the best size for labels area. /// /// Chart graphics object. /// Maximum labels area size. /// Label size without angle = 0. private float GetRequiredLabelSize(ChartGraphics chartGraph, float maxLabelSize, out float resultSize) { float resultRotatedSize = 0F; resultSize = 0F; float angle = (autoLabelAngle < -90) ? this.LabelStyle.Angle : autoLabelAngle; labelNearOffset = float.MaxValue; labelFarOffset = float.MinValue; // Label string drawing format using (StringFormat format = new StringFormat()) { format.FormatFlags |= StringFormatFlags.LineLimit; format.Trimming = StringTrimming.EllipsisCharacter; // Initialize all labels position rectangle RectangleF rectLabels = ChartArea.Position.ToRectangleF(); // Loop through all labels in the first row foreach (CustomLabel label in this.CustomLabels) { // Skip if label middle point is outside current scaleView if (label.RowIndex == 0) { decimal middlePoint = (decimal)(label.FromPosition + label.ToPosition) / (decimal)2.0; if (middlePoint < (decimal)this.ViewMinimum || middlePoint > (decimal)this.ViewMaximum) { continue; } } if (label.RowIndex == 0) { // Calculate single label position RectangleF rect = rectLabels; rect.Width = maxLabelSize; // Set label From and To coordinates double fromPosition = this.GetLinearPosition(label.FromPosition); double toPosition = this.GetLinearPosition(label.ToPosition); rect.Y = (float)Math.Min(fromPosition, toPosition); rect.Height = (float)Math.Max(fromPosition, toPosition) - rect.Y; // Adjust label From/To position if labels are displayed with offset if ((autoLabelOffset == -1) ? this.LabelStyle.IsStaggered : (autoLabelOffset == 1)) { rect.Y -= rect.Height / 2F; rect.Height *= 2F; } // If horizontal axis if (this.AxisPosition == AxisPosition.Bottom || this.AxisPosition == AxisPosition.Top) { // Switch rectangle sizes float val = rect.Height; rect.Height = rect.Width; rect.Width = val; // Set vertical font for measuring if (angle != 0) { format.FormatFlags |= StringFormatFlags.DirectionVertical; } } else { // Set vertical font for measuring if (angle == 90 || angle == -90) { angle = 0; format.FormatFlags |= StringFormatFlags.DirectionVertical; } } // Measure label text size rect.Width = (float)Math.Ceiling(rect.Width); rect.Height = (float)Math.Ceiling(rect.Height); SizeF axisLabelSize = chartGraph.MeasureStringRel(label.Text.Replace("\\n", "\n"), (autoLabelFont != null) ? autoLabelFont : this.LabelStyle.Font, rect.Size, format); // Width and height maybe zeros if rect is too small to fit the text and // the LineLimit format flag is set. if (axisLabelSize.Width == 0f || axisLabelSize.Height == 0f) { // Measure string without the LineLimit flag format.FormatFlags ^= StringFormatFlags.LineLimit; axisLabelSize = chartGraph.MeasureStringRel(label.Text.Replace("\\n", "\n"), (autoLabelFont != null) ? autoLabelFont : this.LabelStyle.Font, rect.Size, format); format.FormatFlags |= StringFormatFlags.LineLimit; } // Add image size if (label.Image.Length > 0) { SizeF imageAbsSize = new SizeF(); if (this.Common.ImageLoader.GetAdjustedImageSize(label.Image, chartGraph.Graphics, ref imageAbsSize)) { SizeF imageRelSize = chartGraph.GetRelativeSize(imageAbsSize); if ((format.FormatFlags & StringFormatFlags.DirectionVertical) == StringFormatFlags.DirectionVertical) { axisLabelSize.Height += imageRelSize.Height; axisLabelSize.Width = Math.Max(axisLabelSize.Width, imageRelSize.Width); } else { axisLabelSize.Width += imageRelSize.Width; axisLabelSize.Height = Math.Max(axisLabelSize.Height, imageRelSize.Height); } } } // Add extra spacing for the box marking of the label if (label.LabelMark == LabelMarkStyle.Box) { // Get relative size from pixels and add it to the label size SizeF spacerSize = chartGraph.GetRelativeSize(new SizeF(4, 4)); axisLabelSize.Width += spacerSize.Width; axisLabelSize.Height += spacerSize.Height; } // Calculate size using label angle float width = axisLabelSize.Width; float height = axisLabelSize.Height; if (angle != 0) { width = (float)Math.Cos((90 - Math.Abs(angle)) / 180F * Math.PI) * axisLabelSize.Width; width += (float)Math.Cos((Math.Abs(angle)) / 180F * Math.PI) * axisLabelSize.Height; height = (float)Math.Sin((Math.Abs(angle)) / 180F * Math.PI) * axisLabelSize.Height; height += (float)Math.Sin((90 - Math.Abs(angle)) / 180F * Math.PI) * axisLabelSize.Width; } width = (float)Math.Ceiling(width) * 1.05f; height = (float)Math.Ceiling(height) * 1.05f; axisLabelSize.Width = (float)Math.Ceiling(axisLabelSize.Width) * 1.05f; axisLabelSize.Height = (float)Math.Ceiling(axisLabelSize.Height) * 1.05f; // If axis is horizontal if (this.AxisPosition == AxisPosition.Bottom || this.AxisPosition == AxisPosition.Top) { if (angle == 90 || angle == -90 || angle == 0) { resultSize = Math.Max(resultSize, axisLabelSize.Height); resultRotatedSize = Math.Max(resultRotatedSize, axisLabelSize.Height); // Calculate the overhang of labels on the side labelNearOffset = (float)Math.Min(labelNearOffset, (fromPosition + toPosition) / 2f - axisLabelSize.Width / 2f); labelFarOffset = (float)Math.Max(labelFarOffset, (fromPosition + toPosition) / 2f + axisLabelSize.Width / 2f); } else { resultSize = Math.Max(resultSize, axisLabelSize.Height); resultRotatedSize = Math.Max(resultRotatedSize, height); // Calculate the overhang of labels on the side if (angle > 0) { labelFarOffset = (float)Math.Max(labelFarOffset, (fromPosition + toPosition) / 2f + width * 1.1f); } else { labelNearOffset = (float)Math.Min(labelNearOffset, (fromPosition + toPosition) / 2f - width * 1.1f); } } } // If axis is vertical else { if (angle == 90 || angle == -90 || angle == 0) { resultSize = Math.Max(resultSize, axisLabelSize.Width); resultRotatedSize = Math.Max(resultRotatedSize, axisLabelSize.Width); // Calculate the overhang of labels on the side labelNearOffset = (float)Math.Min(labelNearOffset, (fromPosition + toPosition) / 2f - axisLabelSize.Height / 2f); labelFarOffset = (float)Math.Max(labelFarOffset, (fromPosition + toPosition) / 2f + axisLabelSize.Height / 2f); } else { resultSize = Math.Max(resultSize, axisLabelSize.Width); resultRotatedSize = Math.Max(resultRotatedSize, width); // Calculate the overhang of labels on the side if (angle > 0) { labelFarOffset = (float)Math.Max(labelFarOffset, (fromPosition + toPosition) / 2f + height * 1.1f); } else { labelNearOffset = (float)Math.Min(labelNearOffset, (fromPosition + toPosition) / 2f - height * 1.1f); } } } // Check if we exceed the maximum value if (resultSize > maxLabelSize) { resultSize = maxLabelSize; } } } } // Adjust results if labels are displayed with offset if ((autoLabelOffset == -1) ? this.LabelStyle.IsStaggered : (autoLabelOffset == 1)) { resultSize *= 2F; resultRotatedSize *= 2F; // Check if we exceed the maximum value if (resultSize > maxLabelSize) { resultSize = maxLabelSize; resultRotatedSize = maxLabelSize; } } // Adjust labels size for the 3D Common.Chart if (ChartArea.Area3DStyle.Enable3D && !ChartArea.chartAreaIsCurcular) { // Increase labels size resultSize *= 1.1f; resultRotatedSize *= 1.1f; } return resultRotatedSize; } /// /// Gets total size of all grouping labels. /// /// Total size of all grouping labels. internal float GetGroupLablesToatalSize() { float size = 0f; if (this.groupingLabelSizes != null && this.groupingLabelSizes.Length > 0) { foreach (float val in this.groupingLabelSizes) { size += val; } } return size; } /// /// Gets number of levels of the grouping labels. /// /// Number of levels of the grouping labels. internal int GetGroupLabelLevelCount() { int groupLabelLevel = 0; foreach (CustomLabel label in this.CustomLabels) { if (label.RowIndex > 0) { groupLabelLevel = Math.Max(groupLabelLevel, label.RowIndex); } } return groupLabelLevel; } /// /// Calculates the best size for axis labels for all rows except first one (grouping labels). /// /// Chart graphics object. /// Maximum labels area size. /// Array of grouping label sizes for each level. private float[] GetRequiredGroupLabelSize(ChartGraphics chartGraph, float maxLabelSize) { float[] resultSize = null; // Get number of groups int groupLabelLevelCount = GetGroupLabelLevelCount(); // Check ig grouping labels exist if (groupLabelLevelCount > 0) { // Create result array resultSize = new float[groupLabelLevelCount]; // Loop through each level of grouping labels for (int groupLevelIndex = 1; groupLevelIndex <= groupLabelLevelCount; groupLevelIndex++) { resultSize[groupLevelIndex - 1] = 0f; // Loop through all labels in the level foreach (CustomLabel label in this.CustomLabels) { // Skip if label middle point is outside current scaleView if (label.RowIndex == 0) { double middlePoint = (label.FromPosition + label.ToPosition) / 2.0; if (middlePoint < this.ViewMinimum || middlePoint > this.ViewMaximum) { continue; } } if (label.RowIndex == groupLevelIndex) { // Measure label text size SizeF axisLabelSize = chartGraph.MeasureStringRel(label.Text.Replace("\\n", "\n"), (autoLabelFont != null) ? autoLabelFont : this.LabelStyle.Font); axisLabelSize.Width = (float)Math.Ceiling(axisLabelSize.Width); axisLabelSize.Height = (float)Math.Ceiling(axisLabelSize.Height); // Add image size if(label.Image.Length > 0) { SizeF imageAbsSize = new SizeF(); if(this.Common.ImageLoader.GetAdjustedImageSize(label.Image, chartGraph.Graphics, ref imageAbsSize)) { SizeF imageRelSize = chartGraph.GetRelativeSize(imageAbsSize); axisLabelSize.Width += imageRelSize.Width; axisLabelSize.Height = Math.Max(axisLabelSize.Height, imageRelSize.Height); } } // Add extra spacing for the box marking of the label if(label.LabelMark == LabelMarkStyle.Box) { // Get relative size from pixels and add it to the label size SizeF spacerSize = chartGraph.GetRelativeSize(new SizeF(4, 4)); axisLabelSize.Width += spacerSize.Width; axisLabelSize.Height += spacerSize.Height; } // If axis is horizontal if (this.AxisPosition == AxisPosition.Bottom || this.AxisPosition == AxisPosition.Top) { resultSize[groupLevelIndex - 1] = Math.Max(resultSize[groupLevelIndex - 1], axisLabelSize.Height); } // If axis is vertical else { axisLabelSize.Width = chartGraph.GetAbsoluteSize(new SizeF(axisLabelSize.Height, axisLabelSize.Height)).Height; axisLabelSize.Width = chartGraph.GetRelativeSize(new SizeF(axisLabelSize.Width, axisLabelSize.Width)).Width; resultSize[groupLevelIndex - 1] = Math.Max(resultSize[groupLevelIndex - 1], axisLabelSize.Width); } // Check if we exceed the maximum value if (resultSize[groupLevelIndex - 1] > maxLabelSize / groupLabelLevelCount) { // NOTE: Group Labels size limitations are removed !!! // resultSize[groupLevelIndex - 1] = maxLabelSize / groupLabelLevelCount; // break; } } } } } return resultSize; } #endregion #region Axis helper methods /// /// Gets main or sub axis associated with this axis. /// /// Sub axis name or empty string to get the main axis. /// Main or sub axis of the main axis. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "subAxisName")] internal Axis GetSubAxis(string subAxisName) { #if SUBAXES if(!this.IsSubAxis && subAxisName.Length > 0) { SubAxis subAxis = this.SubAxes.FindByName(subAxisName); if(subAxis == null) { throw(new InvalidOperationException( SR.ExceptionSubAxisNameNotFoundShort( subAxisName ))); } return subAxis; } #endif // SUBAXES return this; } /// /// Checks if axis marks should be next to the axis /// /// true if marks are next to axis. internal bool GetIsMarksNextToAxis() { if (ChartArea != null && ChartArea.chartAreaIsCurcular) { return true; } return this.IsMarksNextToAxis; } /// /// Gets axis auto interval type. /// /// Axis interval type. internal DateTimeIntervalType GetAxisIntervalType() { if(InternalIntervalType == DateTimeIntervalType.Auto) { if(GetAxisValuesType() == ChartValueType.DateTime || GetAxisValuesType() == ChartValueType.Date || GetAxisValuesType() == ChartValueType.Time || GetAxisValuesType() == ChartValueType.DateTimeOffset) { return DateTimeIntervalType.Years; } return DateTimeIntervalType.Number; } return InternalIntervalType; } /// /// Gets axis values type depending on the series attached /// /// Axis values type. internal ChartValueType GetAxisValuesType() { ChartValueType type = ChartValueType.Double; // Check all series in this Common.Chart area attached to this axis if (this.Common != null && this.Common.DataManager.Series != null && ChartArea != null) { foreach (Series series in this.Common.DataManager.Series) { bool seriesAttached = false; // Check series name if (series.ChartArea == ChartArea.Name && series.IsVisible()) { // Check if axis type of series match if (this.axisType == AxisName.X && series.XAxisType == AxisType.Primary) { #if SUBAXES if(((Axis)this).SubAxisName == series.XSubAxisName) #endif // SUBAXES { seriesAttached = true; } } else if (this.axisType == AxisName.X2 && series.XAxisType == AxisType.Secondary) { #if SUBAXES if(((Axis)this).SubAxisName == series.XSubAxisName) #endif // SUBAXES { seriesAttached = true; } } else if (this.axisType == AxisName.Y && series.YAxisType == AxisType.Primary) { #if SUBAXES if(((Axis)this).SubAxisName == series.YSubAxisName) #endif // SUBAXES { seriesAttached = true; } } else if (this.axisType == AxisName.Y2 && series.YAxisType == AxisType.Secondary) { #if SUBAXES if(((Axis)this).SubAxisName == series.YSubAxisName) #endif // SUBAXES { seriesAttached = true; } } } // If series attached to this axes if (seriesAttached) { if (this.axisType == AxisName.X || this.axisType == AxisName.X2) { type = series.XValueType; } else if (this.axisType == AxisName.Y || this.axisType == AxisName.Y2) { type = series.YValueType; } break; } } } return type; } /// /// Returns Arrow size /// /// Return arrow orientation. /// Size of arrow internal SizeF GetArrowSize(out ArrowOrientation arrowOrientation) { Axis opositeAxis; double size; double sizeOpposite; arrowOrientation = ArrowOrientation.Top; // Set the position of axis switch (AxisPosition) { case AxisPosition.Left: if (isReversed) arrowOrientation = ArrowOrientation.Bottom; else arrowOrientation = ArrowOrientation.Top; break; case AxisPosition.Right: if (isReversed) arrowOrientation = ArrowOrientation.Bottom; else arrowOrientation = ArrowOrientation.Top; break; case AxisPosition.Bottom: if (isReversed) arrowOrientation = ArrowOrientation.Left; else arrowOrientation = ArrowOrientation.Right; break; case AxisPosition.Top: if (isReversed) arrowOrientation = ArrowOrientation.Left; else arrowOrientation = ArrowOrientation.Right; break; } // Opposite axis. Arrow uses this axis to find // a shift from Common.Chart area border. This shift // depend on Tick mark size. switch (arrowOrientation) { case ArrowOrientation.Left: opositeAxis = ChartArea.AxisX; break; case ArrowOrientation.Right: opositeAxis = ChartArea.AxisX2; break; case ArrowOrientation.Top: opositeAxis = ChartArea.AxisY2; break; case ArrowOrientation.Bottom: opositeAxis = ChartArea.AxisY; break; default: opositeAxis = ChartArea.AxisX; break; } // Arrow size has to have the same shape when width and height // are changed. When the picture is resized, width of the Common.Chart // picture is used only for arrow size. if (arrowOrientation == ArrowOrientation.Top || arrowOrientation == ArrowOrientation.Bottom) { size = _lineWidth; sizeOpposite = (double)(_lineWidth) * Common.Width / Common.Height; } else { size = (double)(_lineWidth) * Common.Width / Common.Height; sizeOpposite = _lineWidth; } // Arrow is sharp triangle if (_arrowStyle == AxisArrowStyle.SharpTriangle) { // Arrow direction is vertical if (arrowOrientation == ArrowOrientation.Top || arrowOrientation == ArrowOrientation.Bottom) return new SizeF((float)(size * 2), (float)(opositeAxis.MajorTickMark.Size + sizeOpposite * 4)); else // Arrow direction is horizontal return new SizeF((float)(opositeAxis.MajorTickMark.Size + sizeOpposite * 4), (float)(size * 2)); } // There is no arrow else if (_arrowStyle == AxisArrowStyle.None) return new SizeF(0, 0); else// Arrow is triangle or line type { // Arrow direction is vertical if (arrowOrientation == ArrowOrientation.Top || arrowOrientation == ArrowOrientation.Bottom) return new SizeF((float)(size * 2), (float)(opositeAxis.MajorTickMark.Size + sizeOpposite * 2)); else // Arrow direction is horizontal return new SizeF((float)(opositeAxis.MajorTickMark.Size + sizeOpposite * 2), (float)(size * 2)); } } /// /// Checks if arrow with specified orientation will require space /// in axis with specified position /// /// Arrow orientation. /// Axis position. /// True if arrow will be drawn in axis space private bool IsArrowInAxis(ArrowOrientation arrowOrientation, AxisPosition axisPosition) { if (axisPosition == AxisPosition.Top && arrowOrientation == ArrowOrientation.Top) return true; else if (axisPosition == AxisPosition.Bottom && arrowOrientation == ArrowOrientation.Bottom) return true; if (axisPosition == AxisPosition.Left && arrowOrientation == ArrowOrientation.Left) return true; else if (axisPosition == AxisPosition.Right && arrowOrientation == ArrowOrientation.Right) return true; return false; } /// /// This function converts real Interval to /// absolute Interval /// /// A interval represented as double value /// A interval represented in pixels internal float GetPixelInterval(double realInterval) { double chartAreaSize; // The Chart area pixel size as double if (AxisPosition == AxisPosition.Top || AxisPosition == AxisPosition.Bottom) { chartAreaSize = PlotAreaPosition.Right - PlotAreaPosition.X; } else { chartAreaSize = PlotAreaPosition.Bottom - PlotAreaPosition.Y; } // Avoid division by zero. if (ViewMaximum - ViewMinimum == 0) { return (float)(chartAreaSize / realInterval); } // The interval integer return (float)(chartAreaSize / (ViewMaximum - ViewMinimum) * realInterval); } /// /// Find if axis is on the edge of the Common.Chart plot area /// internal bool IsAxisOnAreaEdge { get { double edgePosition = 0; if (this.AxisPosition == AxisPosition.Bottom) { edgePosition = PlotAreaPosition.Bottom; } else if (this.AxisPosition == AxisPosition.Left) { edgePosition = PlotAreaPosition.X; } else if (this.AxisPosition == AxisPosition.Right) { edgePosition = PlotAreaPosition.Right; } else if (this.AxisPosition == AxisPosition.Top) { edgePosition = PlotAreaPosition.Y; } // DT Fix : problems with values on edge ~0.0005 if (Math.Abs(GetAxisPosition() - edgePosition) < 0.0015) { return true; } return false; } } /// /// Find axis position using crossing value. /// /// Relative position internal double GetAxisPosition() { return GetAxisPosition(false); } /// /// Find axis position using crossing value. /// /// Axis crossing should be ignored. /// Relative position virtual internal double GetAxisPosition(bool ignoreCrossing) { Axis axisOpposite = GetOppositeAxis(); // Get axis position for circular Common.Chart area if (ChartArea != null && ChartArea.chartAreaIsCurcular) { return PlotAreaPosition.X + PlotAreaPosition.Width / 2f; } // Axis is not connected with any series. There is no maximum and minimum if (axisOpposite.maximum == axisOpposite.minimum || double.IsNaN(axisOpposite.maximum) || double.IsNaN(axisOpposite.minimum) || maximum == minimum || double.IsNaN(maximum) || double.IsNaN(minimum)) { switch (AxisPosition) { case AxisPosition.Top: return PlotAreaPosition.Y; case AxisPosition.Bottom: return PlotAreaPosition.Bottom; case AxisPosition.Right: return PlotAreaPosition.Right; case AxisPosition.Left: return PlotAreaPosition.X; } } // Auto crossing enabled if (Double.IsNaN(axisOpposite.crossing) || ignoreCrossing) { // Primary if (axisType == AxisName.X || axisType == AxisName.Y) return axisOpposite.GetLinearPosition(axisOpposite.ViewMinimum); else // Secondary return axisOpposite.GetLinearPosition(axisOpposite.ViewMaximum); } else // Auto crossing disabled { axisOpposite.crossing = axisOpposite.tempCrossing; if (axisOpposite.crossing < axisOpposite.ViewMinimum) { axisOpposite.crossing = axisOpposite.ViewMinimum; } else if (axisOpposite.crossing > axisOpposite.ViewMaximum) { axisOpposite.crossing = axisOpposite.ViewMaximum; } } return axisOpposite.GetLinearPosition(axisOpposite.crossing); } #endregion #region Axis 3D helper methods /// /// Returns angle between 2D axis line and it's 3D transformed projection. /// /// Axis projection angle. internal double GetAxisProjectionAngle() { // Get Z position bool axisOnEdge; float zPosition = GetMarksZPosition(out axisOnEdge); // Get axis position float axisPosition = (float)GetAxisPosition(); // Create two points on the sides of the axis Point3D[] axisPoints = new Point3D[2]; if (this.AxisPosition == AxisPosition.Top || this.AxisPosition == AxisPosition.Bottom) { axisPoints[0] = new Point3D(0f, axisPosition, zPosition); axisPoints[1] = new Point3D(100f, axisPosition, zPosition); } else { axisPoints[0] = new Point3D(axisPosition, 0f, zPosition); axisPoints[1] = new Point3D(axisPosition, 100f, zPosition); } // Transform coordinates ChartArea.matrix3D.TransformPoints(axisPoints); // Round result axisPoints[0].X = (float)Math.Round(axisPoints[0].X, 4); axisPoints[0].Y = (float)Math.Round(axisPoints[0].Y, 4); axisPoints[1].X = (float)Math.Round(axisPoints[1].X, 4); axisPoints[1].Y = (float)Math.Round(axisPoints[1].Y, 4); // Calculate angle double angle = 0.0; if (this.AxisPosition == AxisPosition.Top || this.AxisPosition == AxisPosition.Bottom) { angle = Math.Atan((axisPoints[1].Y - axisPoints[0].Y) / (axisPoints[1].X - axisPoints[0].X)); } else { angle = Math.Atan((axisPoints[1].X - axisPoints[0].X) / (axisPoints[1].Y - axisPoints[0].Y)); } // Conver to degrees return (angle * 180.0) / Math.PI; } #endregion #region IDisposable Members /// /// Releases unmanaged and - optionally - managed resources /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected override void Dispose(bool disposing) { if (disposing) { if (_fontCache != null) { _fontCache.Dispose(); _fontCache = null; } if (labelStyle != null) { labelStyle.Dispose(); labelStyle = null; } if (_stripLines != null) { _stripLines.Dispose(); _stripLines = null; } if (_customLabels != null) { _customLabels.Dispose(); _customLabels = null; } if (tempLabels != null) { tempLabels.Dispose(); tempLabels = null; } if (this.scrollBar != null) { this.scrollBar.Dispose(); this.scrollBar = null; } } base.Dispose(disposing); } #endregion } }