// 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: LabelStyle and CustomLabel classes are used to determine // chart axis labels. Labels can be automatically // generated based on the series data or be “manually” // set by the user. // using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Drawing.Design; using System.Drawing.Drawing2D; using FastReport.DataVisualization.Charting.ChartTypes; using FastReport.DataVisualization.Charting.Utilities; namespace FastReport.DataVisualization.Charting { #region Labels enumerations /// /// An enumeration that specifies a mark for custom labels. /// public enum LabelMarkStyle { /// /// No label marks are used. /// None, /// /// Labels use side marks. /// SideMark, /// /// Labels use line and side marks. /// LineSideMark, /// /// Draws a box around the label. The box always starts at the axis position. /// Box }; /// /// An enumeration of custom grid lines and tick marks flags used in the custom labels. /// [Flags] public enum GridTickTypes { /// /// No tick mark or grid line are shown. /// None = 0, /// /// Tick mark is shown. /// TickMark = 1, /// /// Grid line is shown. /// Gridline = 2, /// /// Tick mark and grid line are shown. /// All = TickMark | Gridline } /// /// An enumeration of label styles for circular chart area axis. /// internal enum CircularAxisLabelsStyle { /// /// Style depends on number of labels. /// Auto, /// /// Label text positions around the circular area. /// Circular, /// /// Label text is always horizontal. /// Horizontal, /// /// Label text has the same angle as circular axis. /// Radial } #endregion /// /// The CustomLabelsCollection class is a strongly typed collection of /// custom axis labels. /// [ SRDescription("DescriptionAttributeCustomLabelsCollection_CustomLabelsCollection"), ] public class CustomLabelsCollection : ChartElementCollection { #region Constructors /// /// Custom labels collection object constructor /// /// Reference to the axis object. internal CustomLabelsCollection(Axis axis) : base(axis) { } #endregion #region Properties internal Axis Axis { get { return Parent as Axis; } } #endregion #region Labels adding methods /// /// Adds a custom label into the collection. /// /// Label left position. /// Label right position. /// Label text. /// Newly added item. public CustomLabel Add(double fromPosition, double toPosition, string text) { CustomLabel label = new CustomLabel(fromPosition, toPosition, text, 0, LabelMarkStyle.None); Add(label); return label; } /// /// Adds one custom label into the collection. Custom label flag may be specified. /// /// Label left position. /// Label right position. /// Label text. /// Indicates if label is custom (created by user). /// Newly added item. internal CustomLabel Add(double fromPosition, double toPosition, string text, bool customLabel) { CustomLabel label = new CustomLabel(fromPosition, toPosition, text, 0, LabelMarkStyle.None); label.customLabel = customLabel; Add(label); return label; } /// /// Adds a custom label into the collection. /// /// Label left position. /// Label right position. /// Label text. /// Label row index. /// Label marking style. /// Newly added item. public CustomLabel Add(double fromPosition, double toPosition, string text, int rowIndex, LabelMarkStyle markStyle) { CustomLabel label = new CustomLabel(fromPosition, toPosition, text, rowIndex, markStyle); Add(label); return label; } /// /// Adds a custom label into the collection. /// /// Label left position. /// Label right position. /// Label text. /// Label row index. /// Label marking style. /// Index of newly added item. /// Custom grid line and tick mark flag. public CustomLabel Add(double fromPosition, double toPosition, string text, int rowIndex, LabelMarkStyle markStyle, GridTickTypes gridTick) { CustomLabel label = new CustomLabel(fromPosition, toPosition, text, rowIndex, markStyle, gridTick); Add(label); return label; } /// /// Adds multiple custom labels to the collection. /// The labels will be DateTime labels with the specified interval type, /// and will be generated for the axis range that is determined by the minimum and maximum arguments. /// /// The label step determines how often the custom labels will be drawn. /// Unit of measurement of the label step. /// Minimum value.. /// Maximum value.. /// Label text format. /// Label row index. /// Label marking style. public void Add(double labelsStep, DateTimeIntervalType intervalType, double min, double max, string format, int rowIndex, LabelMarkStyle markStyle) { // Find labels range min/max values if(min == 0.0 && max == 0.0 && this.Axis != null && !double.IsNaN(this.Axis.Minimum) && !double.IsNaN(this.Axis.Maximum)) { min = this.Axis.Minimum; max = this.Axis.Maximum; } double fromX = Math.Min(min, max); double toX = Math.Max(min, max); this.SuspendUpdates(); try { // Loop through all label points double labelStart = fromX; double labelEnd = 0; while (labelStart < toX) { // Determine label end location if (intervalType == DateTimeIntervalType.Number) { labelEnd = labelStart + labelsStep; } else if (intervalType == DateTimeIntervalType.Milliseconds) { labelEnd = DateTime.FromOADate(labelStart).AddMilliseconds(labelsStep).ToOADate(); } else if (intervalType == DateTimeIntervalType.Seconds) { labelEnd = DateTime.FromOADate(labelStart).AddSeconds(labelsStep).ToOADate(); } else if (intervalType == DateTimeIntervalType.Minutes) { labelEnd = DateTime.FromOADate(labelStart).AddMinutes(labelsStep).ToOADate(); } else if (intervalType == DateTimeIntervalType.Hours) { labelEnd = DateTime.FromOADate(labelStart).AddHours(labelsStep).ToOADate(); } else if (intervalType == DateTimeIntervalType.Days) { labelEnd = DateTime.FromOADate(labelStart).AddDays(labelsStep).ToOADate(); } else if (intervalType == DateTimeIntervalType.Weeks) { labelEnd = DateTime.FromOADate(labelStart).AddDays(7 * labelsStep).ToOADate(); } else if (intervalType == DateTimeIntervalType.Months) { labelEnd = DateTime.FromOADate(labelStart).AddMonths((int)labelsStep).ToOADate(); } else if (intervalType == DateTimeIntervalType.Years) { labelEnd = DateTime.FromOADate(labelStart).AddYears((int)labelsStep).ToOADate(); } else { // Unsupported step type throw (new ArgumentException(SR.ExceptionAxisLabelsIntervalTypeUnsupported(intervalType.ToString()))); } if (labelEnd > toX) { labelEnd = toX; } // Generate label text ChartValueType valueType = ChartValueType.Double; if (intervalType != DateTimeIntervalType.Number) { if (this.Axis.GetAxisValuesType() == ChartValueType.DateTimeOffset) valueType = ChartValueType.DateTimeOffset; else valueType = ChartValueType.DateTime; } string text = ValueConverter.FormatValue( this.Common.Chart, this.Axis, null, labelStart + (labelEnd - labelStart) / 2, format, valueType, ChartElementType.AxisLabels); // Add label CustomLabel label = new CustomLabel(labelStart, labelEnd, text, rowIndex, markStyle); this.Add(label); labelStart = labelEnd; } } finally { this.ResumeUpdates(); } } /// /// Adds multiple custom labels to the collection. /// The labels will be DateTime labels with the specified interval type, /// and will be generated for the axis range that is determined by the minimum and maximum arguments. /// /// The label step determines how often the custom labels will be drawn. /// Unit of measurement of the label step. public void Add(double labelsStep, DateTimeIntervalType intervalType) { Add(labelsStep, intervalType, 0, 0, "", 0, LabelMarkStyle.None); } /// /// Adds multiple custom labels to the collection. /// The labels will be DateTime labels with the specified interval type, /// and will be generated for the axis range that is determined by the minimum and maximum arguments. /// /// The label step determines how often the custom labels will be drawn. /// Unit of measurement of the label step. /// Label text format. public void Add(double labelsStep, DateTimeIntervalType intervalType, string format) { Add(labelsStep, intervalType, 0, 0, format, 0, LabelMarkStyle.None); } /// /// Adds multiple custom labels to the collection. /// The labels will be DateTime labels with the specified interval type, /// and will be generated for the axis range that is determined by the minimum and maximum arguments. /// /// The label step determines how often the custom labels will be drawn. /// Unit of measurement of the label step. /// Label text format. /// Label row index. /// Label marking style. public void Add(double labelsStep, DateTimeIntervalType intervalType, string format, int rowIndex, LabelMarkStyle markStyle) { Add(labelsStep, intervalType, 0, 0, format, rowIndex, markStyle); } #endregion } /// /// The CustomLabel class represents a single custom axis label. Text and /// position along the axis is provided by the caller. /// [ SRDescription("DescriptionAttributeCustomLabel_CustomLabel"), DefaultProperty("Text"), ] public class CustomLabel : ChartNamedElement { #region Fields and Constructors // Private data members, which store properties values private double _fromPosition = 0; private double _toPosition = 0; private string _text = ""; private LabelMarkStyle _labelMark = LabelMarkStyle.None; private Color _foreColor = Color.Empty; private Color _markColor = Color.Empty; private int _labelRowIndex = 0; // Custom grid lines and tick marks flags private GridTickTypes _gridTick = GridTickTypes.None; // Indicates if label was automatically created or cpecified by user (custom) internal bool customLabel = true; // Image associated with the label private string _image = string.Empty; // Image transparent color private Color _imageTransparentColor = Color.Empty; // Label tooltip private string _tooltip = string.Empty; private Axis _axis = null; #endregion #region Constructors /// /// Default constructor /// public CustomLabel() { } /// /// CustomLabel constructor /// /// From position. /// To position. /// Label text. /// Label row index. /// Label mark style. public CustomLabel(double fromPosition, double toPosition, string text, int labelRow, LabelMarkStyle markStyle) { this._fromPosition = fromPosition; this._toPosition = toPosition; this._text = text; this._labelRowIndex = labelRow; this._labelMark = markStyle; this._gridTick = GridTickTypes.None; } /// /// CustomLabel constructor /// /// From position. /// To position. /// Label text. /// Label row index. /// Label mark style. /// Custom grid line and tick marks flag. public CustomLabel(double fromPosition, double toPosition, string text, int labelRow, LabelMarkStyle markStyle, GridTickTypes gridTick) { this._fromPosition = fromPosition; this._toPosition = toPosition; this._text = text; this._labelRowIndex = labelRow; this._labelMark = markStyle; this._gridTick = gridTick; } #endregion #region Helper methods /// /// Returns a cloned label object. /// /// Copy of current custom label. public CustomLabel Clone() { CustomLabel newLabel = new CustomLabel(); newLabel.FromPosition = this.FromPosition; newLabel.ToPosition = this.ToPosition; newLabel.Text = this.Text; newLabel.ForeColor = this.ForeColor; newLabel.MarkColor = this.MarkColor; newLabel.RowIndex = this.RowIndex; newLabel.LabelMark = this.LabelMark; newLabel.GridTicks = this.GridTicks; newLabel.ToolTip = this.ToolTip; newLabel.Tag = this.Tag; newLabel.Image = this.Image; newLabel.ImageTransparentColor = this.ImageTransparentColor; return newLabel; } internal override IChartElement Parent { get { return base.Parent; } set { base.Parent = value; if (value != null) { _axis = Parent.Parent as Axis; } } } /// /// Gets the axis to which this object is attached to. /// /// Axis. [ Browsable(false), DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden), SerializationVisibilityAttribute(SerializationVisibility.Hidden), ] public Axis Axis { get { return _axis; } } #endregion #region CustomLabel properties /// /// Gets or sets the tooltip of the custom label. /// [ SRCategory("CategoryAttributeMapArea"), Bindable(true), SRDescription("DescriptionAttributeToolTip"), DefaultValue("") ] public string ToolTip { set { this._tooltip = value; } get { return this._tooltip; CallOnModifing(); } } /// /// Gets or sets the label image. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(""), SRDescription("DescriptionAttributeCustomLabel_Image"), #if DESIGNER Editor(typeof(ImageValueEditor), typeof(UITypeEditor)), #endif NotifyParentPropertyAttribute(true) ] public string Image { get { return _image; } set { _image = value; Invalidate(); CallOnModifing(); } } /// /// Gets or sets a color which will be replaced with a transparent color while drawing the image. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(typeof(Color), ""), NotifyParentPropertyAttribute(true), SRDescription("DescriptionAttributeImageTransparentColor"), TypeConverter(typeof(ColorConverter)), #if DESIGNER Editor(typeof(ChartColorEditor), typeof(UITypeEditor)) #endif ] public Color ImageTransparentColor { get { return _imageTransparentColor; } set { _imageTransparentColor = value; this.Invalidate(); CallOnModifing(); } } /// /// Custom label name. This property is for internal use only. /// [ SRCategory("CategoryAttributeAppearance"), SRDescription("DescriptionAttributeCustomLabel_Name"), DefaultValue("Custom LabelStyle"), DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden), DesignOnlyAttribute(true), SerializationVisibilityAttribute(SerializationVisibility.Hidden) ] public override string Name { get { return base.Name; } set { base.Name = value; CallOnModifing(); } } /// /// Gets or sets a property which specifies whether /// custom tick marks and grid lines will be drawn in the center of the label. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(GridTickTypes.None), SRDescription("DescriptionAttributeCustomLabel_GridTicks"), #if DESIGNER Editor(typeof(FlagsEnumUITypeEditor), typeof(UITypeEditor)) #endif ] public GridTickTypes GridTicks { get { return _gridTick; } set { _gridTick = value; this.Invalidate(); CallOnModifing(); } } /// /// Gets or sets the end position of the custom label in axis coordinates. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(0.0), SRDescription("DescriptionAttributeCustomLabel_From"), TypeConverter(typeof(AxisLabelDateValueConverter)) ] public double FromPosition { get { return _fromPosition; } set { _fromPosition = value; this.Invalidate(); CallOnModifing(); } } /// /// Gets or sets the starting position of the custom label in axis coordinates. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(0.0), SRDescription("DescriptionAttributeCustomLabel_To"), TypeConverter(typeof(AxisLabelDateValueConverter)) ] public double ToPosition { get { return _toPosition; } set { _toPosition = value; this.Invalidate(); CallOnModifing(); } } /// /// Gets or sets the text of the custom label. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(""), SRDescription("DescriptionAttributeCustomLabel_Text") ] public string Text { get { return _text; } set { _text = value; this.Invalidate(); CallOnModifing(); } } /// /// Gets or sets the text color of the custom label. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(typeof(Color), ""), SRDescription("DescriptionAttributeForeColor"), NotifyParentPropertyAttribute(true), TypeConverter(typeof(ColorConverter)), #if DESIGNER Editor(typeof(ChartColorEditor), typeof(UITypeEditor)) #endif ] public Color ForeColor { get { return _foreColor; } set { _foreColor = value; this.Invalidate(); CallOnModifing(); } } /// /// Gets or sets the color of the label mark line of the custom label. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(typeof(Color), ""), SRDescription("DescriptionAttributeCustomLabel_MarkColor"), NotifyParentPropertyAttribute(true), TypeConverter(typeof(ColorConverter)), #if DESIGNER Editor(typeof(ChartColorEditor), typeof(UITypeEditor)) #endif ] public Color MarkColor { get { return _markColor; } set { _markColor = value; this.Invalidate(); CallOnModifing(); } } /// /// Gets or sets the row index of the custom label. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(0), SRDescription("DescriptionAttributeCustomLabel_RowIndex") ] public int RowIndex { get { return this._labelRowIndex; } set { if(value < 0) { throw (new InvalidOperationException(SR.ExceptionAxisLabelRowIndexIsNegative)); } this._labelRowIndex = value; this.Invalidate(); CallOnModifing(); } } /// /// Gets or sets a property which define the marks for the labels in the second row. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(LabelMarkStyle.None), SRDescription("DescriptionAttributeCustomLabel_LabelMark") ] public LabelMarkStyle LabelMark { get { return _labelMark; } set { _labelMark = value; this.Invalidate(); CallOnModifing(); } } #endregion } /// /// The LabelStyle class contains properties which define the visual appearance of /// the axis labels, their interval and position. This class is also /// responsible for calculating the position of all the labels and /// drawing them. /// [ SRDescription("DescriptionAttributeLabel_Label"), DefaultProperty("Enabled"), ] public class LabelStyle : ChartElement { #region Fields // Reference to the Axis private Axis _axis = null; // Private data members, which store properties values private bool _enabled = true; internal double intervalOffset = double.NaN; internal double interval = double.NaN; internal DateTimeIntervalType intervalType = DateTimeIntervalType.NotSet; internal DateTimeIntervalType intervalOffsetType = DateTimeIntervalType.NotSet; private FontCache _fontCache = new FontCache(); private Font _font; private Color _foreColor = Color.Black; internal int angle = 0; internal bool isStaggered = false; private bool _isEndLabelVisible = true; private bool _truncatedLabels = false; private string _format = ""; #endregion #region Constructors /// /// Public default constructor. /// public LabelStyle() { _font = _fontCache.DefaultFont; } /// /// Public constructor. /// /// Axis which owns the grid. internal LabelStyle(Axis axis) : this() { _axis = axis; } #endregion #region Axis labels drawing methods /// /// Draws axis labels on the circular chart area. /// /// Reference to the Chart Graphics object. internal void PaintCircular( ChartGraphics graph ) { // Label string drawing format using (StringFormat format = new StringFormat()) { format.FormatFlags |= StringFormatFlags.LineLimit; format.Trimming = StringTrimming.EllipsisCharacter; // Labels are disabled for this axis if (!_axis.LabelStyle.Enabled) return; // Draw text with anti-aliasing /* if( (graph.AntiAliasing & AntiAliasing.Text) == AntiAliasing.Text ) { graph.TextRenderingHint = TextRenderingHint.AntiAlias; } else { graph.TextRenderingHint = TextRenderingHint.SingleBitPerPixelGridFit; } */ // Gets axis labels style CircularAxisLabelsStyle labelsStyle = this._axis.ChartArea.GetCircularAxisLabelsStyle(); // Get list of circular axes with labels ArrayList circularAxes = this._axis.ChartArea.GetCircularAxisList(); // Draw each axis label int index = 0; foreach (CircularChartAreaAxis circAxis in circularAxes) { if (circAxis.Title.Length > 0) { //****************************************************************** //** Calculate label position corner position //****************************************************************** PointF labelRelativePosition = new PointF( this._axis.ChartArea.circularCenter.X, this._axis.ChartArea.PlotAreaPosition.Y); // Adjust labels Y position labelRelativePosition.Y -= _axis.markSize + Axis.elementSpacing; // Convert to absolute PointF[] labelPosition = new PointF[] { graph.GetAbsolutePoint(labelRelativePosition) }; // Get label rotation angle float labelAngle = circAxis.AxisPosition; ICircularChartType chartType = this._axis.ChartArea.GetCircularChartType(); if (chartType != null && chartType.XAxisCrossingSupported()) { if (!double.IsNaN(this._axis.ChartArea.AxisX.Crossing)) { labelAngle += (float)this._axis.ChartArea.AxisX.Crossing; } } // Make sure angle is presented as a positive number while (labelAngle < 0) { labelAngle = 360f + labelAngle; } // Set graphics rotation matrix Matrix newMatrix = new Matrix(); newMatrix.RotateAt(labelAngle, graph.GetAbsolutePoint(this._axis.ChartArea.circularCenter)); newMatrix.TransformPoints(labelPosition); // Set text alignment format.LineAlignment = StringAlignment.Center; format.Alignment = StringAlignment.Near; if (labelsStyle != CircularAxisLabelsStyle.Radial) { if (labelAngle < 5f || labelAngle > 355f) { format.Alignment = StringAlignment.Center; format.LineAlignment = StringAlignment.Far; } if (labelAngle < 185f && labelAngle > 175f) { format.Alignment = StringAlignment.Center; format.LineAlignment = StringAlignment.Near; } if (labelAngle > 185f && labelAngle < 355f) { format.Alignment = StringAlignment.Far; } } else { if (labelAngle > 180f) { format.Alignment = StringAlignment.Far; } } // Set text rotation angle float textAngle = labelAngle; if (labelsStyle == CircularAxisLabelsStyle.Radial) { if (labelAngle > 180) { textAngle += 90f; } else { textAngle -= 90f; } } else if (labelsStyle == CircularAxisLabelsStyle.Circular) { format.Alignment = StringAlignment.Center; format.LineAlignment = StringAlignment.Far; } // Set text rotation matrix Matrix oldMatrix = graph.Transform; if (labelsStyle == CircularAxisLabelsStyle.Radial || labelsStyle == CircularAxisLabelsStyle.Circular) { Matrix textRotationMatrix = oldMatrix.Clone(); textRotationMatrix.RotateAt(textAngle, labelPosition[0]); graph.Transform = textRotationMatrix; } // Get axis titl (label) color Color labelColor = _foreColor; if (!circAxis.TitleForeColor.IsEmpty) { labelColor = circAxis.TitleForeColor; } // Draw label using (Brush brush = new SolidBrush(labelColor)) { graph.DrawString( circAxis.Title.Replace("\\n", "\n"), (_axis.autoLabelFont == null) ? _font : _axis.autoLabelFont, brush, labelPosition[0], format); } // Process selection region if (this._axis.Common.ProcessModeRegions) { SizeF size = graph.MeasureString(circAxis.Title.Replace("\\n", "\n"), (_axis.autoLabelFont == null) ? _font : _axis.autoLabelFont); RectangleF labelRect = GetLabelPosition( labelPosition[0], size, format); PointF[] points = new PointF[] { labelRect.Location, new PointF(labelRect.Right, labelRect.Y), new PointF(labelRect.Right, labelRect.Bottom), new PointF(labelRect.X, labelRect.Bottom) }; using (GraphicsPath path = new GraphicsPath()) { path.AddPolygon(points); path.CloseAllFigures(); path.Transform(graph.Transform); this._axis.Common.HotRegionsList.AddHotRegion( path, false, ChartElementType.AxisLabels, circAxis.Title); } } // Restore graphics if(labelsStyle == CircularAxisLabelsStyle.Radial || labelsStyle == CircularAxisLabelsStyle.Circular) { graph.Transform = oldMatrix; } } ++index; } } } /// /// Gets rectangle position of the label. /// /// Original label position. /// Label text size. /// Label string format. /// Label rectangle position. internal static RectangleF GetLabelPosition( PointF position, SizeF size, StringFormat format) { // Calculate label position rectangle RectangleF labelPosition = RectangleF.Empty; labelPosition.Width = size.Width; labelPosition.Height = size.Height; if(format.Alignment == StringAlignment.Far) { labelPosition.X = position.X - size.Width; } else if(format.Alignment == StringAlignment.Near) { labelPosition.X = position.X; } else if(format.Alignment == StringAlignment.Center) { labelPosition.X = position.X - size.Width/2F; } if(format.LineAlignment == StringAlignment.Far) { labelPosition.Y = position.Y - size.Height; } else if(format.LineAlignment == StringAlignment.Near) { labelPosition.Y = position.Y; } else if(format.LineAlignment == StringAlignment.Center) { labelPosition.Y = position.Y - size.Height/2F; } return labelPosition; } /// /// Draws axis labels. /// /// Reference to the Chart Graphics object. /// Back elements of the axis should be drawn in 3D scene. internal void Paint( ChartGraphics graph, bool backElements ) { // Label string drawing format using (StringFormat format = new StringFormat()) { format.FormatFlags |= StringFormatFlags.LineLimit; format.Trimming = StringTrimming.EllipsisCharacter; // Labels are disabled for this axis if (!_axis.LabelStyle.Enabled) return; // deliant fix-> VSTS #157848, #143286 - drawing custom label in empty axis if (Double.IsNaN(_axis.ViewMinimum) || Double.IsNaN(_axis.ViewMaximum)) return; // Draw labels in 3D space if (this._axis.ChartArea.Area3DStyle.Enable3D && !this._axis.ChartArea.chartAreaIsCurcular) { this.Paint3D(graph, backElements); return; } // Initialize all labels position rectangle RectangleF rectLabels = _axis.ChartArea.Position.ToRectangleF(); float labelSize = _axis.labelSize; if (_axis.AxisPosition == AxisPosition.Left) { rectLabels.Width = labelSize; if (_axis.GetIsMarksNextToAxis()) rectLabels.X = (float)_axis.GetAxisPosition(); else rectLabels.X = _axis.PlotAreaPosition.X; rectLabels.X -= labelSize + _axis.markSize; // Set label text alignment format.Alignment = StringAlignment.Far; format.LineAlignment = StringAlignment.Center; } else if (_axis.AxisPosition == AxisPosition.Right) { rectLabels.Width = labelSize; if (_axis.GetIsMarksNextToAxis()) rectLabels.X = (float)_axis.GetAxisPosition(); else rectLabels.X = _axis.PlotAreaPosition.Right; rectLabels.X += _axis.markSize; // Set label text alignment format.Alignment = StringAlignment.Near; format.LineAlignment = StringAlignment.Center; } else if (_axis.AxisPosition == AxisPosition.Top) { rectLabels.Height = labelSize; if (_axis.GetIsMarksNextToAxis()) rectLabels.Y = (float)_axis.GetAxisPosition(); else rectLabels.Y = _axis.PlotAreaPosition.Y; rectLabels.Y -= labelSize + _axis.markSize; // Set label text alignment format.Alignment = StringAlignment.Center; format.LineAlignment = StringAlignment.Far; } else if (_axis.AxisPosition == AxisPosition.Bottom) { rectLabels.Height = labelSize; if (_axis.GetIsMarksNextToAxis()) rectLabels.Y = (float)_axis.GetAxisPosition(); else rectLabels.Y = _axis.PlotAreaPosition.Bottom; rectLabels.Y += _axis.markSize; // Set label text alignment format.Alignment = StringAlignment.Center; format.LineAlignment = StringAlignment.Near; } // Calculate bounding rectangle RectangleF boundaryRect = rectLabels; if (boundaryRect != RectangleF.Empty && _axis.totlaGroupingLabelsSize > 0) { if (_axis.AxisPosition == AxisPosition.Left) { boundaryRect.X += _axis.totlaGroupingLabelsSize; boundaryRect.Width -= _axis.totlaGroupingLabelsSize; } else if (_axis.AxisPosition == AxisPosition.Right) { boundaryRect.Width -= _axis.totlaGroupingLabelsSize; } else if (_axis.AxisPosition == AxisPosition.Top) { boundaryRect.Y += _axis.totlaGroupingLabelsSize; boundaryRect.Height -= _axis.totlaGroupingLabelsSize; } else if (_axis.AxisPosition == AxisPosition.Bottom) { boundaryRect.Height -= _axis.totlaGroupingLabelsSize; } } // Check if the AJAX zooming and scrolling mode is enabled. // Labels are drawn slightly different in this case. bool ajaxScrollingEnabled = false; bool firstFrame = true; bool lastFrame = true; // Draw all labels from the collection int labelIndex = 0; foreach (CustomLabel label in this._axis.CustomLabels) { bool truncatedLeft = false; bool truncatedRight = false; double labelFrom = label.FromPosition; double labelTo = label.ToPosition; bool useRelativeCoordiantes = false; double labelFromRelative = double.NaN; double labelToRelative = double.NaN; // Skip if label middle point is outside current scaleView if (label.RowIndex == 0) { double middlePoint = (label.FromPosition + label.ToPosition) / 2.0; decimal viewMin = (decimal)_axis.ViewMinimum; decimal viewMax = (decimal)_axis.ViewMaximum; if (ajaxScrollingEnabled) { // Skip very first and last labels if they are partialy outside the scaleView if (firstFrame) { if ((decimal)label.FromPosition < (decimal)_axis.Minimum) { continue; } } if (lastFrame) { if ((decimal)label.ToPosition > (decimal)_axis.Maximum) { continue; } } // Skip label only if it is compleltly out of the scaleView if ((decimal)label.ToPosition < viewMin || (decimal)label.FromPosition > viewMax) { continue; } // RecalculateAxesScale label index starting from the first frame. // Index is used to determine position of the offset labels if ((this._axis.autoLabelOffset == -1) ? this.IsStaggered : (this._axis.autoLabelOffset == 1)) { // Reset index labelIndex = 0; // Get first series attached to this axis Series axisSeries = null; if (_axis.axisType == AxisName.X || _axis.axisType == AxisName.X2) { List seriesArray = _axis.ChartArea.GetXAxesSeries((_axis.axisType == AxisName.X) ? AxisType.Primary : AxisType.Secondary, _axis.SubAxisName); if (seriesArray.Count > 0) { axisSeries = _axis.Common.DataManager.Series[seriesArray[0]]; if (axisSeries != null && !axisSeries.IsXValueIndexed) { axisSeries = null; } } } // Set start position and iterate through label positions // NOTE: Labels offset should not be taken in the account double currentPosition = _axis.Minimum; while (currentPosition < _axis.Maximum) { if (currentPosition >= middlePoint) { break; } currentPosition += ChartHelper.GetIntervalSize(currentPosition, _axis.LabelStyle.GetInterval(), _axis.LabelStyle.GetIntervalType(), axisSeries, 0.0, DateTimeIntervalType.Number, true); ++labelIndex; } } } else { // Skip label if label middle point is not in the scaleView if ((decimal)middlePoint < viewMin || (decimal)middlePoint > viewMax) { continue; } } // Make sure label To and From coordinates are processed by one scale segment based // on the label middle point position. if (_axis.ScaleSegments.Count > 0) { AxisScaleSegment scaleSegment = _axis.ScaleSegments.FindScaleSegmentForAxisValue(middlePoint); _axis.ScaleSegments.AllowOutOfScaleValues = true; _axis.ScaleSegments.EnforceSegment(scaleSegment); } // Use center point instead of the To/From if label takes all scaleView // This is done to avoid issues with labels drawing with high // zooming levels. if ((decimal)label.FromPosition < viewMin && (decimal)label.ToPosition > viewMax) { // Indicates that chart relative coordinates should be used instead of axis values useRelativeCoordiantes = true; // Calculate label From/To in relative coordinates using // label middle point and 100% width. labelFromRelative = _axis.GetLinearPosition(middlePoint) - 50.0; labelToRelative = labelFromRelative + 100.0; } } else { // Skip labels completly outside the scaleView if (label.ToPosition <= _axis.ViewMinimum || label.FromPosition >= _axis.ViewMaximum) { continue; } // Check if label is partially visible. if (!ajaxScrollingEnabled && _axis.ScaleView.IsZoomed) { if (label.FromPosition < _axis.ViewMinimum) { truncatedLeft = true; labelFrom = _axis.ViewMinimum; } if (label.ToPosition > _axis.ViewMaximum) { truncatedRight = true; labelTo = _axis.ViewMaximum; } } } // Calculate single label position RectangleF rect = rectLabels; // Label is in the first row if (label.RowIndex == 0) { if (_axis.AxisPosition == AxisPosition.Left) { rect.X = rectLabels.Right - _axis.unRotatedLabelSize; rect.Width = _axis.unRotatedLabelSize; // Adjust label rectangle if offset labels are used if ((this._axis.autoLabelOffset == -1) ? this.IsStaggered : (this._axis.autoLabelOffset == 1)) { rect.Width /= 2F; if (labelIndex % 2 != 0F) { rect.X += rect.Width; } } } else if (_axis.AxisPosition == AxisPosition.Right) { rect.Width = _axis.unRotatedLabelSize; // Adjust label rectangle if offset labels are used if ((this._axis.autoLabelOffset == -1) ? this.IsStaggered : (this._axis.autoLabelOffset == 1)) { rect.Width /= 2F; if (labelIndex % 2 != 0F) { rect.X += rect.Width; } } } else if (_axis.AxisPosition == AxisPosition.Top) { rect.Y = rectLabels.Bottom - _axis.unRotatedLabelSize; rect.Height = _axis.unRotatedLabelSize; // Adjust label rectangle if offset labels are used if ((this._axis.autoLabelOffset == -1) ? this.IsStaggered : (this._axis.autoLabelOffset == 1)) { rect.Height /= 2F; if (labelIndex % 2 != 0F) { rect.Y += rect.Height; } } } else if (_axis.AxisPosition == AxisPosition.Bottom) { rect.Height = _axis.unRotatedLabelSize; // Adjust label rectangle if offset labels are used if ((this._axis.autoLabelOffset == -1) ? this.IsStaggered : (this._axis.autoLabelOffset == 1)) { rect.Height /= 2F; if (labelIndex % 2 != 0F) { rect.Y += rect.Height; } } } // Increase label index ++labelIndex; } // Label is in the second row else if (label.RowIndex > 0) { if (_axis.AxisPosition == AxisPosition.Left) { rect.X += _axis.totlaGroupingLabelsSizeAdjustment; for (int index = _axis.groupingLabelSizes.Length; index > label.RowIndex; index--) { rect.X += _axis.groupingLabelSizes[index - 1]; } rect.Width = _axis.groupingLabelSizes[label.RowIndex - 1]; } else if (_axis.AxisPosition == AxisPosition.Right) { rect.X = rect.Right - _axis.totlaGroupingLabelsSize - _axis.totlaGroupingLabelsSizeAdjustment;// + Axis.elementSpacing * 0.25f; for (int index = 1; index < label.RowIndex; index++) { rect.X += _axis.groupingLabelSizes[index - 1]; } rect.Width = _axis.groupingLabelSizes[label.RowIndex - 1]; } else if (_axis.AxisPosition == AxisPosition.Top) { rect.Y += _axis.totlaGroupingLabelsSizeAdjustment; for (int index = _axis.groupingLabelSizes.Length; index > label.RowIndex; index--) { rect.Y += _axis.groupingLabelSizes[index - 1]; } rect.Height = _axis.groupingLabelSizes[label.RowIndex - 1]; } if (_axis.AxisPosition == AxisPosition.Bottom) { rect.Y = rect.Bottom - _axis.totlaGroupingLabelsSize - _axis.totlaGroupingLabelsSizeAdjustment; for (int index = 1; index < label.RowIndex; index++) { rect.Y += _axis.groupingLabelSizes[index - 1]; } rect.Height = _axis.groupingLabelSizes[label.RowIndex - 1]; } } // Unknown label row value else { throw (new InvalidOperationException(SR.ExceptionAxisLabelIndexIsNegative)); } // Set label From and To coordinates double fromPosition = _axis.GetLinearPosition(labelFrom); double toPosition = _axis.GetLinearPosition(labelTo); if (useRelativeCoordiantes) { useRelativeCoordiantes = false; fromPosition = labelFromRelative; toPosition = labelToRelative; } if (_axis.AxisPosition == AxisPosition.Top || _axis.AxisPosition == AxisPosition.Bottom) { rect.X = (float)Math.Min(fromPosition, toPosition); rect.Width = (float)Math.Max(fromPosition, toPosition) - rect.X; // Adjust label To/From position if offset labels are used if (label.RowIndex == 0 && ((this._axis.autoLabelOffset == -1) ? this.IsStaggered : (this._axis.autoLabelOffset == 1))) { rect.X -= rect.Width / 2F; rect.Width *= 2F; } } else { rect.Y = (float)Math.Min(fromPosition, toPosition); rect.Height = (float)Math.Max(fromPosition, toPosition) - rect.Y; // Adjust label To/From position if offset labels are used if (label.RowIndex == 0 && ((this._axis.autoLabelOffset == -1) ? this.IsStaggered : (this._axis.autoLabelOffset == 1))) { rect.Y -= rect.Height / 2F; rect.Height *= 2F; } } // Draw label using (Brush brush = new SolidBrush((label.ForeColor.IsEmpty) ? _foreColor : label.ForeColor)) { graph.DrawLabelStringRel(_axis, label.RowIndex, label.LabelMark, label.MarkColor, label.Text, label.Image, label.ImageTransparentColor, (_axis.autoLabelFont == null) ? _font : _axis.autoLabelFont, brush, rect, format, (_axis.autoLabelAngle < -90) ? angle : _axis.autoLabelAngle, (!this.TruncatedLabels || label.RowIndex > 0) ? RectangleF.Empty : boundaryRect, label, truncatedLeft, truncatedRight); } // Clear scale segment enforcement _axis.ScaleSegments.EnforceSegment(null); _axis.ScaleSegments.AllowOutOfScaleValues = false; } } } #endregion #region 3D Axis labels drawing methods /// /// Get a rectangle between chart area position and plotting area on specified side. /// Also sets axis labels string formatting for the specified labels position. /// /// Chart area object. /// Position in chart area. /// Axis labels string format. /// Axis labels rectangle. private RectangleF GetAllLabelsRect(ChartArea area, AxisPosition position, StringFormat stringFormat) { // Find axis with same position Axis labelsAxis = null; foreach(Axis curAxis in area.Axes) { if(curAxis.AxisPosition == position) { labelsAxis = curAxis; break; } } if(labelsAxis == null) { return RectangleF.Empty; } // Calculate rect for different positions RectangleF rectLabels = area.Position.ToRectangleF(); if( position == AxisPosition.Left ) { rectLabels.Width = labelsAxis.labelSize; if( labelsAxis.GetIsMarksNextToAxis() ) { rectLabels.X = (float)labelsAxis.GetAxisPosition(); rectLabels.Width = (float)Math.Max(rectLabels.Width, rectLabels.X - labelsAxis.PlotAreaPosition.X); } else { rectLabels.X = labelsAxis.PlotAreaPosition.X; } rectLabels.X -= rectLabels.Width; if(area.IsSideSceneWallOnLeft() || area.Area3DStyle.WallWidth == 0) { rectLabels.X -= labelsAxis.markSize; } // Set label text alignment stringFormat.Alignment = StringAlignment.Far; stringFormat.LineAlignment = StringAlignment.Center; } else if( position == AxisPosition.Right ) { rectLabels.Width = labelsAxis.labelSize; if( labelsAxis.GetIsMarksNextToAxis() ) { rectLabels.X = (float)labelsAxis.GetAxisPosition(); rectLabels.Width = (float)Math.Max(rectLabels.Width, labelsAxis.PlotAreaPosition.Right - rectLabels.X); } else { rectLabels.X = labelsAxis.PlotAreaPosition.Right; } if(!area.IsSideSceneWallOnLeft() || area.Area3DStyle.WallWidth == 0) { rectLabels.X += labelsAxis.markSize; } // Set label text alignment stringFormat.Alignment = StringAlignment.Near; stringFormat.LineAlignment = StringAlignment.Center; } else if( position == AxisPosition.Top ) { rectLabels.Height = labelsAxis.labelSize; if( labelsAxis.GetIsMarksNextToAxis() ) { rectLabels.Y = (float)labelsAxis.GetAxisPosition(); rectLabels.Height = (float)Math.Max(rectLabels.Height, rectLabels.Y - labelsAxis.PlotAreaPosition.Y); } else { rectLabels.Y = labelsAxis.PlotAreaPosition.Y; } rectLabels.Y -= rectLabels.Height; if(area.Area3DStyle.WallWidth == 0) { rectLabels.Y -= labelsAxis.markSize; } // Set label text alignment stringFormat.Alignment = StringAlignment.Center; stringFormat.LineAlignment = StringAlignment.Far; } else if( position == AxisPosition.Bottom ) { rectLabels.Height = labelsAxis.labelSize; if( labelsAxis.GetIsMarksNextToAxis() ) { rectLabels.Y = (float)labelsAxis.GetAxisPosition(); rectLabels.Height = (float)Math.Max(rectLabels.Height, labelsAxis.PlotAreaPosition.Bottom - rectLabels.Y); } else { rectLabels.Y = labelsAxis.PlotAreaPosition.Bottom; } rectLabels.Y += labelsAxis.markSize; // Set label text alignment stringFormat.Alignment = StringAlignment.Center; stringFormat.LineAlignment = StringAlignment.Near; } return rectLabels; } /// /// Gets position of axis labels. /// Top and Bottom axis labels can be drawn on the sides (left or right) /// of the plotting area. If angle between axis and it's projection is /// between -25 and 25 degrees the axis are drawn at the bottom/top, /// otherwise labels are moved on the left or right side. /// /// Axis object. /// Position where axis labels should be drawn. private AxisPosition GetLabelsPosition(Axis axis) { // Get angle between 2D axis and it's 3D projection. double axisAngle = axis.GetAxisProjectionAngle(); // Pick the side to draw the labels on if(axis.AxisPosition == AxisPosition.Bottom) { if(axisAngle <= -25 ) return AxisPosition.Right; else if(axisAngle >= 25 ) return AxisPosition.Left; } else if(axis.AxisPosition == AxisPosition.Top) { if(axisAngle <= -25 ) return AxisPosition.Left; else if(axisAngle >= 25 ) return AxisPosition.Right; } // Labels are on the same side as the axis return axis.AxisPosition; } /// /// Draws axis labels in 3D space. /// /// Reference to the Chart Graphics object. /// Back elements of the axis should be drawn in 3D scene. internal void Paint3D( ChartGraphics graph, bool backElements ) { // Label string drawing format using (StringFormat format = new StringFormat()) { format.Trimming = StringTrimming.EllipsisCharacter; // Calculate single pixel size in relative coordinates SizeF pixelSize = graph.GetRelativeSize(new SizeF(1f, 1f)); //******************************************************************** //** Determine the side of the plotting area to draw the labels on. //******************************************************************** AxisPosition labelsPosition = GetLabelsPosition(_axis); //***************************************************************** //** Set the labels Z position //***************************************************************** bool axisOnEdge; float labelsZPosition = _axis.GetMarksZPosition(out axisOnEdge); // Adjust Z position for the "bent" tick marks bool adjustForWallWidth = false; if (this._axis.AxisPosition == AxisPosition.Top && !this._axis.ChartArea.ShouldDrawOnSurface(SurfaceNames.Top, backElements, false)) { adjustForWallWidth = true; } if (this._axis.AxisPosition == AxisPosition.Left && !this._axis.ChartArea.ShouldDrawOnSurface(SurfaceNames.Left, backElements, false)) { adjustForWallWidth = true; } if (this._axis.AxisPosition == AxisPosition.Right && !this._axis.ChartArea.ShouldDrawOnSurface(SurfaceNames.Right, backElements, false)) { adjustForWallWidth = true; } if (adjustForWallWidth && this._axis.ChartArea.Area3DStyle.WallWidth > 0) { if (this._axis.MajorTickMark.TickMarkStyle == TickMarkStyle.InsideArea) { labelsZPosition -= this._axis.ChartArea.areaSceneWallWidth.Width; } else if (this._axis.MajorTickMark.TickMarkStyle == TickMarkStyle.OutsideArea) { labelsZPosition -= this._axis.MajorTickMark.Size + this._axis.ChartArea.areaSceneWallWidth.Width; } else if (this._axis.MajorTickMark.TickMarkStyle == TickMarkStyle.AcrossAxis) { labelsZPosition -= this._axis.MajorTickMark.Size / 2f + this._axis.ChartArea.areaSceneWallWidth.Width; } } //***************************************************************** //** Check if labels should be drawn as back or front element. //***************************************************************** bool labelsInsidePlotArea = (this._axis.GetIsMarksNextToAxis() && !axisOnEdge); if (backElements == labelsInsidePlotArea) { // Skip drawing return; } //******************************************************************** //** Initialize all labels position rectangle //******************************************************************** RectangleF rectLabels = this.GetAllLabelsRect(this._axis.ChartArea, this._axis.AxisPosition, format); //******************************************************************** //** Calculate bounding rectangle used to truncate labels on the //** chart area boundary if TruncatedLabels property is set to true. //******************************************************************** RectangleF boundaryRect = rectLabels; if (boundaryRect != RectangleF.Empty && _axis.totlaGroupingLabelsSize > 0) { if (this._axis.AxisPosition == AxisPosition.Left) { boundaryRect.X += _axis.totlaGroupingLabelsSize; boundaryRect.Width -= _axis.totlaGroupingLabelsSize; } else if (this._axis.AxisPosition == AxisPosition.Right) { boundaryRect.Width -= _axis.totlaGroupingLabelsSize; } else if (this._axis.AxisPosition == AxisPosition.Top) { boundaryRect.Y += _axis.totlaGroupingLabelsSize; boundaryRect.Height -= _axis.totlaGroupingLabelsSize; } else if (this._axis.AxisPosition == AxisPosition.Bottom) { boundaryRect.Height -= _axis.totlaGroupingLabelsSize; } } // Pre-calculated height of the first labels row float firstLabelsRowHeight = -1f; // For 3D axis labels the first row of labels // has to be drawn after all other rows because // of hot regions. for (int selectionRow = 0; selectionRow <= this._axis.GetGroupLabelLevelCount(); selectionRow++) { //******************************************************************** //** Draw all labels from the collection //******************************************************************** int labelIndex = 0; foreach (CustomLabel label in this._axis.CustomLabels) { bool truncatedLeft = false; bool truncatedRight = false; double labelFrom = label.FromPosition; double labelTo = label.ToPosition; if (label.RowIndex != selectionRow) { continue; } // Skip if label middle point is outside current scaleView if (label.RowIndex == 0) { double middlePoint = (label.FromPosition + label.ToPosition) / 2.0; if ((decimal)middlePoint < (decimal)_axis.ViewMinimum || (decimal)middlePoint > (decimal)_axis.ViewMaximum) { continue; } } else { // Skip labels completly outside the scaleView if (label.ToPosition <= _axis.ViewMinimum || label.FromPosition >= _axis.ViewMaximum) { continue; } // Check if label is partially visible if (_axis.ScaleView.IsZoomed) { if (label.FromPosition < _axis.ViewMinimum) { truncatedLeft = true; labelFrom = _axis.ViewMinimum; } if (label.ToPosition > _axis.ViewMaximum) { truncatedRight = true; labelTo = _axis.ViewMaximum; } } } // Calculate single label position RectangleF rect = rectLabels; // Label is in the first row if (label.RowIndex == 0) { if (this._axis.AxisPosition == AxisPosition.Left) { if (!this._axis.GetIsMarksNextToAxis()) { rect.X = rectLabels.Right - _axis.unRotatedLabelSize; rect.Width = _axis.unRotatedLabelSize; } // Adjust label rectangle if offset labels are used if ((this._axis.autoLabelOffset == -1) ? this.IsStaggered : (this._axis.autoLabelOffset == 1)) { rect.Width /= 2F; if (labelIndex % 2 != 0F) { rect.X += rect.Width; } } } else if (this._axis.AxisPosition == AxisPosition.Right) { if (!this._axis.GetIsMarksNextToAxis()) { rect.Width = _axis.unRotatedLabelSize; } // Adjust label rectangle if offset labels are used if ((this._axis.autoLabelOffset == -1) ? this.IsStaggered : (this._axis.autoLabelOffset == 1)) { rect.Width /= 2F; if (labelIndex % 2 != 0F) { rect.X += rect.Width; } } } else if (this._axis.AxisPosition == AxisPosition.Top) { if (!this._axis.GetIsMarksNextToAxis()) { rect.Y = rectLabels.Bottom - _axis.unRotatedLabelSize; rect.Height = _axis.unRotatedLabelSize; } // Adjust label rectangle if offset labels are used if ((this._axis.autoLabelOffset == -1) ? this.IsStaggered : (this._axis.autoLabelOffset == 1)) { rect.Height /= 2F; if (labelIndex % 2 != 0F) { rect.Y += rect.Height; } } } else if (this._axis.AxisPosition == AxisPosition.Bottom) { if (!this._axis.GetIsMarksNextToAxis()) { rect.Height = _axis.unRotatedLabelSize; } // Adjust label rectangle if offset labels are used if ((this._axis.autoLabelOffset == -1) ? this.IsStaggered : (this._axis.autoLabelOffset == 1)) { rect.Height /= 2F; if (labelIndex % 2 != 0F) { rect.Y += rect.Height; } } } // Increase label index ++labelIndex; } // Label is in the second row else if (label.RowIndex > 0) { // Hide grouping labels (where index of row > 0) when they are displayed // not on the same side as their axis. Fixes MS issue #64. if (labelsPosition != this._axis.AxisPosition) { continue; } if (_axis.AxisPosition == AxisPosition.Left) { rect.X += _axis.totlaGroupingLabelsSizeAdjustment; for (int index = _axis.groupingLabelSizes.Length; index > label.RowIndex; index--) { rect.X += _axis.groupingLabelSizes[index - 1]; } rect.Width = _axis.groupingLabelSizes[label.RowIndex - 1]; } else if (_axis.AxisPosition == AxisPosition.Right) { rect.X = rect.Right - _axis.totlaGroupingLabelsSize - _axis.totlaGroupingLabelsSizeAdjustment;// + Axis.elementSpacing * 0.25f; for (int index = 1; index < label.RowIndex; index++) { rect.X += _axis.groupingLabelSizes[index - 1]; } rect.Width = _axis.groupingLabelSizes[label.RowIndex - 1]; } else if (_axis.AxisPosition == AxisPosition.Top) { rect.Y += _axis.totlaGroupingLabelsSizeAdjustment; for (int index = _axis.groupingLabelSizes.Length; index > label.RowIndex; index--) { rect.Y += _axis.groupingLabelSizes[index - 1]; } rect.Height = _axis.groupingLabelSizes[label.RowIndex - 1]; } if (_axis.AxisPosition == AxisPosition.Bottom) { rect.Y = rect.Bottom - _axis.totlaGroupingLabelsSize - _axis.totlaGroupingLabelsSizeAdjustment; for (int index = 1; index < label.RowIndex; index++) { rect.Y += _axis.groupingLabelSizes[index - 1]; } rect.Height = _axis.groupingLabelSizes[label.RowIndex - 1]; } } // Unknown label row value else { throw (new InvalidOperationException(SR.ExceptionAxisLabelRowIndexMustBe1Or2)); } //******************************************************************** //** Set label From and To coordinates. //******************************************************************** double fromPosition = _axis.GetLinearPosition(labelFrom); double toPosition = _axis.GetLinearPosition(labelTo); if (this._axis.AxisPosition == AxisPosition.Top || this._axis.AxisPosition == AxisPosition.Bottom) { rect.X = (float)Math.Min(fromPosition, toPosition); rect.Width = (float)Math.Max(fromPosition, toPosition) - rect.X; if (rect.Width < pixelSize.Width) { rect.Width = pixelSize.Width; } // Adjust label To/From position if offset labels are used if (label.RowIndex == 0 && ((this._axis.autoLabelOffset == -1) ? this.IsStaggered : (this._axis.autoLabelOffset == 1))) { rect.X -= rect.Width / 2F; rect.Width *= 2F; } } else { rect.Y = (float)Math.Min(fromPosition, toPosition); rect.Height = (float)Math.Max(fromPosition, toPosition) - rect.Y; if (rect.Height < pixelSize.Height) { rect.Height = pixelSize.Height; } // Adjust label To/From position if offset labels are used if (label.RowIndex == 0 && ((this._axis.autoLabelOffset == -1) ? this.IsStaggered : (this._axis.autoLabelOffset == 1))) { rect.Y -= rect.Height / 2F; rect.Height *= 2F; } } // Save original rect RectangleF initialRect = new RectangleF(rect.Location, rect.Size); //******************************************************************** //** Transform and adjust label rectangle coordinates in 3D space. //******************************************************************** Point3D[] rectPoints = new Point3D[3]; if (this._axis.AxisPosition == AxisPosition.Left) { rectPoints[0] = new Point3D(rect.Right, rect.Y, labelsZPosition); rectPoints[1] = new Point3D(rect.Right, rect.Y + rect.Height / 2f, labelsZPosition); rectPoints[2] = new Point3D(rect.Right, rect.Bottom, labelsZPosition); this._axis.ChartArea.matrix3D.TransformPoints(rectPoints); rect.Y = rectPoints[0].Y; rect.Height = rectPoints[2].Y - rect.Y; rect.Width = rectPoints[1].X - rect.X; } else if (this._axis.AxisPosition == AxisPosition.Right) { rectPoints[0] = new Point3D(rect.X, rect.Y, labelsZPosition); rectPoints[1] = new Point3D(rect.X, rect.Y + rect.Height / 2f, labelsZPosition); rectPoints[2] = new Point3D(rect.X, rect.Bottom, labelsZPosition); this._axis.ChartArea.matrix3D.TransformPoints(rectPoints); rect.Y = rectPoints[0].Y; rect.Height = rectPoints[2].Y - rect.Y; rect.Width = rect.Right - rectPoints[1].X; rect.X = rectPoints[1].X; } else if (this._axis.AxisPosition == AxisPosition.Top) { // Transform 3 points of the rectangle rectPoints[0] = new Point3D(rect.X, rect.Bottom, labelsZPosition); rectPoints[1] = new Point3D(rect.X + rect.Width / 2f, rect.Bottom, labelsZPosition); rectPoints[2] = new Point3D(rect.Right, rect.Bottom, labelsZPosition); this._axis.ChartArea.matrix3D.TransformPoints(rectPoints); if (labelsPosition == AxisPosition.Top) { rect.X = rectPoints[0].X; rect.Width = rectPoints[2].X - rect.X; rect.Height = rectPoints[1].Y - rect.Y; } else if (labelsPosition == AxisPosition.Right) { RectangleF rightLabelsRect = this.GetAllLabelsRect(this._axis.ChartArea, labelsPosition, format); rect.Y = rectPoints[0].Y; rect.Height = rectPoints[2].Y - rect.Y; rect.X = rectPoints[1].X; rect.Width = rightLabelsRect.Right - rect.X; } else if (labelsPosition == AxisPosition.Left) { RectangleF rightLabelsRect = this.GetAllLabelsRect(this._axis.ChartArea, labelsPosition, format); rect.Y = rectPoints[2].Y; rect.Height = rectPoints[0].Y - rect.Y; rect.X = rightLabelsRect.X; rect.Width = rectPoints[1].X - rightLabelsRect.X; } } else if (this._axis.AxisPosition == AxisPosition.Bottom) { // Transform 3 points of the rectangle rectPoints[0] = new Point3D(rect.X, rect.Y, labelsZPosition); rectPoints[1] = new Point3D(rect.X + rect.Width / 2f, rect.Y, labelsZPosition); rectPoints[2] = new Point3D(rect.Right, rect.Y, labelsZPosition); this._axis.ChartArea.matrix3D.TransformPoints(rectPoints); if (labelsPosition == AxisPosition.Bottom) { rect.X = rectPoints[0].X; rect.Width = rectPoints[2].X - rect.X; rect.Height = rect.Bottom - rectPoints[1].Y; rect.Y = rectPoints[1].Y; } else if (labelsPosition == AxisPosition.Right) { RectangleF rightLabelsRect = this.GetAllLabelsRect(this._axis.ChartArea, labelsPosition, format); rect.Y = rectPoints[2].Y; rect.Height = rectPoints[0].Y - rect.Y; rect.X = rectPoints[1].X; rect.Width = rightLabelsRect.Right - rect.X; // Adjust label rect by shifting it down by quarter of the tick size if (this._axis.autoLabelAngle == 0) { rect.Y += this._axis.markSize / 4f; } } else if (labelsPosition == AxisPosition.Left) { RectangleF rightLabelsRect = this.GetAllLabelsRect(this._axis.ChartArea, labelsPosition, format); rect.Y = rectPoints[0].Y; rect.Height = rectPoints[2].Y - rect.Y; rect.X = rightLabelsRect.X; rect.Width = rectPoints[1].X - rightLabelsRect.X; // Adjust label rect by shifting it down by quarter of the tick size if (this._axis.autoLabelAngle == 0) { rect.Y += this._axis.markSize / 4f; } } } // Find axis with same position Axis labelsAxis = null; foreach (Axis curAxis in this._axis.ChartArea.Axes) { if (curAxis.AxisPosition == labelsPosition) { labelsAxis = curAxis; break; } } //******************************************************************** //** Adjust font angles for Top and Bottom axis //******************************************************************** int labelsFontAngle = (_axis.autoLabelAngle < -90) ? angle : _axis.autoLabelAngle; if (labelsPosition != this._axis.AxisPosition) { if ((this._axis.AxisPosition == AxisPosition.Top || this._axis.AxisPosition == AxisPosition.Bottom) && (labelsFontAngle == 90 || labelsFontAngle == -90)) { labelsFontAngle = 0; } else if (this._axis.AxisPosition == AxisPosition.Bottom) { if (labelsPosition == AxisPosition.Left && labelsFontAngle > 0) { labelsFontAngle = -labelsFontAngle; } else if (labelsPosition == AxisPosition.Right && labelsFontAngle < 0) { labelsFontAngle = -labelsFontAngle; } } else if (this._axis.AxisPosition == AxisPosition.Top) { if (labelsPosition == AxisPosition.Left && labelsFontAngle < 0) { labelsFontAngle = -labelsFontAngle; } else if (labelsPosition == AxisPosition.Right && labelsFontAngle > 0) { labelsFontAngle = -labelsFontAngle; } } } //******************************************************************** //** NOTE: Code below improves chart labels readability in scenarios //** described in MS issue #65. //** //** Prevent labels in the first row from overlapping the grouping //** labels in the rows below. The solution only apply to the limited //** use cases defined by the condition below. //******************************************************************** StringFormatFlags oldFormatFlags = format.FormatFlags; if (label.RowIndex == 0 && labelsFontAngle == 0 && _axis.groupingLabelSizes != null && _axis.groupingLabelSizes.Length > 0 && this._axis.AxisPosition == AxisPosition.Bottom && labelsPosition == AxisPosition.Bottom && !((this._axis.autoLabelOffset == -1) ? this.IsStaggered : (this._axis.autoLabelOffset == 1))) { if (firstLabelsRowHeight == -1f) { // Calculate first labels row max height Point3D[] labelPositionPoints = new Point3D[1]; labelPositionPoints[0] = new Point3D(initialRect.X, initialRect.Bottom - _axis.totlaGroupingLabelsSize - _axis.totlaGroupingLabelsSizeAdjustment, labelsZPosition); this._axis.ChartArea.matrix3D.TransformPoints(labelPositionPoints); float height = labelPositionPoints[0].Y - rect.Y; firstLabelsRowHeight = (height > 0f) ? height : rect.Height; } // Resuse pre-calculated first labels row height rect.Height = firstLabelsRowHeight; // Change current string format to prevent strings to go out of the // specified bounding rectangle if ((format.FormatFlags & StringFormatFlags.LineLimit) == 0) { format.FormatFlags |= StringFormatFlags.LineLimit; } } //******************************************************************** //** Draw label text. //******************************************************************** using (Brush brush = new SolidBrush((label.ForeColor.IsEmpty) ? _foreColor : label.ForeColor)) { graph.DrawLabelStringRel( labelsAxis, label.RowIndex, label.LabelMark, label.MarkColor, label.Text, label.Image, label.ImageTransparentColor, (_axis.autoLabelFont == null) ? _font : _axis.autoLabelFont, brush, rect, format, labelsFontAngle, (!this.TruncatedLabels || label.RowIndex > 0) ? RectangleF.Empty : boundaryRect, label, truncatedLeft, truncatedRight); } // Restore old string format that was temporary modified if (format.FormatFlags != oldFormatFlags) { format.FormatFlags = oldFormatFlags; } } } } } #endregion #region Helper methods /// /// Sets the axis to which this object attached to. /// /// Axis object. internal Axis Axis { set { _axis = value; } } /// /// Invalidate chart picture /// internal override void Invalidate() { if(this._axis != null) { this._axis.Invalidate(); } base.Invalidate(); } #endregion #region Label properties /// /// Gets or sets the interval offset of the label. /// [ SRCategory("CategoryAttributeData"), Bindable(true), SRDescription("DescriptionAttributeLabel_IntervalOffset"), DefaultValue(Double.NaN), RefreshPropertiesAttribute(RefreshProperties.All), TypeConverter(typeof(AxisElementIntervalValueConverter)) ] public double IntervalOffset { get { return intervalOffset; } set { intervalOffset = value; this.Invalidate(); CallOnModifing(); } } /// /// Gets the interval offset. /// /// internal double GetIntervalOffset() { if(double.IsNaN(intervalOffset) && this._axis != null) { return this._axis.IntervalOffset; } return intervalOffset; } /// /// Gets or sets the unit of measurement of the label offset. /// [ SRCategory("CategoryAttributeData"), Bindable(true), DefaultValue(DateTimeIntervalType.NotSet), SRDescription("DescriptionAttributeLabel_IntervalOffsetType"), RefreshPropertiesAttribute(RefreshProperties.All), ] public DateTimeIntervalType IntervalOffsetType { get { return intervalOffsetType; } set { intervalOffsetType = value; this.Invalidate(); CallOnModifing(); } } /// /// Gets the type of the interval offset. /// /// internal DateTimeIntervalType GetIntervalOffsetType() { if(intervalOffsetType == DateTimeIntervalType.NotSet && this._axis != null) { return this._axis.IntervalOffsetType; } return intervalOffsetType; } /// /// Gets or sets the interval size of the label. /// [ SRCategory("CategoryAttributeData"), Bindable(true), DefaultValue(Double.NaN), SRDescription("DescriptionAttributeLabel_Interval"), TypeConverter(typeof(AxisElementIntervalValueConverter)), ] public double Interval { get { return interval; } set { interval = value; // Reset original property value fields if (this._axis != null) { this._axis.tempLabelInterval = interval; } this.Invalidate(); CallOnModifing(); } } /// /// Gets the interval. /// /// internal double GetInterval() { if(double.IsNaN(interval) && this._axis != null) { return this._axis.Interval; } return interval; } /// /// Gets or sets the unit of measurement of the interval size of the label. /// [ SRCategory("CategoryAttributeData"), Bindable(true), DefaultValue(DateTimeIntervalType.NotSet), SRDescription("DescriptionAttributeLabel_IntervalType"), RefreshPropertiesAttribute(RefreshProperties.All) ] public DateTimeIntervalType IntervalType { get { return intervalType; } set { intervalType = value; // Reset original property value fields if (this._axis != null) { this._axis.tempLabelIntervalType = intervalType; } this.Invalidate(); CallOnModifing(); } } /// /// Gets the type of the interval. /// /// internal DateTimeIntervalType GetIntervalType() { if(intervalType == DateTimeIntervalType.NotSet && this._axis != null) { return this._axis.IntervalType; } return intervalType; } /// /// Gets or sets the font of the label. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(typeof(Font), "Microsoft Sans Serif, 8pt"), SRDescription("DescriptionAttributeLabel_Font") ] public Font Font { get { return _font; } set { // Turn off labels autofitting if (this._axis != null && this._axis.Common!=null && this._axis.Common.Chart != null) { if(!this._axis.Common.Chart.serializing) { this._axis.IsLabelAutoFit = false; } } _font = value; this.Invalidate(); CallOnModifing(); } } /// /// Gets or sets the fore color of the label. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(typeof(Color), "Black"), SRDescription("DescriptionAttributeFontColor"), NotifyParentPropertyAttribute(true), TypeConverter(typeof(ColorConverter)), #if DESIGNER Editor(typeof(ChartColorEditor), typeof(UITypeEditor)) #endif ] public Color ForeColor { get { return _foreColor; } set { _foreColor = value; this.Invalidate(); CallOnModifing(); } } /// /// Gets or sets a value that represents the angle at which font is drawn. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(0), SRDescription("DescriptionAttributeLabel_FontAngle"), RefreshPropertiesAttribute(RefreshProperties.All) ] public int Angle { get { return angle; } set { if(value < -90 || value > 90) { throw (new ArgumentOutOfRangeException("value", SR.ExceptionAxisLabelFontAngleInvalid)); } // Turn of label offset if angle is not 0, 90 or -90 if(IsStaggered && value != 0 && value != -90 && value != 90) { IsStaggered = false; } // Turn off labels autofitting if(this._axis != null && this._axis.Common!=null && this._axis.Common.Chart != null) { if (!this._axis.Common.Chart.serializing) { this._axis.IsLabelAutoFit = false; } } angle = value; this.Invalidate(); CallOnModifing(); } } /// /// Gets or sets a property which specifies whether the labels are shown with offset. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(false), SRDescription("DescriptionAttributeLabel_OffsetLabels"), RefreshPropertiesAttribute(RefreshProperties.All) ] public bool IsStaggered { get { return isStaggered; } set { // Make sure that angle is 0, 90 or -90 if(value && (this.Angle != 0 || this.Angle != -90 || this.Angle != 90)) { this.Angle = 0; } // Turn off labels autofitting if (this._axis != null && this._axis.Common != null && this._axis.Common.Chart != null) { if (!this._axis.Common.Chart.serializing) { this._axis.IsLabelAutoFit = false; } } isStaggered = value; this.Invalidate(); CallOnModifing(); } } /// /// Gets or sets a property which specifies whether the labels are shown at axis ends. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(true), SRDescription("DescriptionAttributeLabel_ShowEndLabels"), ] public bool IsEndLabelVisible { get { return _isEndLabelVisible; } set { _isEndLabelVisible = value; this.Invalidate(); CallOnModifing(); } } /// /// Gets or sets a property which specifies whether the label can be truncated. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(false), SRDescription("DescriptionAttributeLabel_TruncatedLabels"), ] public bool TruncatedLabels { get { return _truncatedLabels; } set { _truncatedLabels = value; this.Invalidate(); CallOnModifing(); } } /// /// Gets or sets the formatting string for the label text. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(""), SRDescription("DescriptionAttributeLabel_Format"), ] public string Format { get { return _format; } set { _format = value; this.Invalidate(); CallOnModifing(); } } /// /// Gets or sets a property which indicates whether the label is enabled. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(true), SRDescription("DescriptionAttributeLabel_Enabled"), ] public bool Enabled { get { return _enabled; } set { _enabled = value; this.Invalidate(); CallOnModifing(); } } #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) { //Free managed resources if (_fontCache!=null) { _fontCache.Dispose(); _fontCache = null; } } } #endregion } }