// 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: Chart control accessible object. // #if ACCESSIBLE using System.Collections.Generic; namespace FastReport.DataVisualization.Charting.Utilities { using System.Drawing; internal class ChartAccessibleObject : Control.ControlAccessibleObject { #region Fields // Reference to the chart control private Chart _chart = null; // List of chart accessible objects private List _chartAccessibleObjectList = null; // Position of the chart in screen coordinates (optianl can be set to empty) private Point _chartScreenPosition = Point.Empty; // Chart scaleView transformation matrix private PointF _chartScale = new PointF(1f, 1f); #endregion // Fields #region Constructors /// /// Object constructor. /// /// Reference to the chart control. public ChartAccessibleObject(Chart chart) : base(chart) { this._chart = chart; } #endregion // Constructors #region Properties /// /// Position of the chart in screen coordinates (optianl can be set to empty) /// public Point ChartScreenPosition { get { return this._chartScreenPosition; } } /// /// Gets the role for the Chart. This is used by accessibility programs. /// public override AccessibleRole Role { get { return AccessibleRole.Chart; } } #endregion // Properties #region Methods /// /// Indicates if chart child accessibility objects should be reset /// public void ResetChildren() { this._chartAccessibleObjectList = null; } /// /// Chart child count. /// /// Number of chart child eleements. public override int GetChildCount() { // Fill list of chart accessible child elements if (this._chartAccessibleObjectList == null) { this.FillChartAccessibleObjectList(); } return _chartAccessibleObjectList.Count; } /// /// Get chart child element by index. /// /// Index of the chart child element. /// Chart element accessibility object. public override AccessibleObject GetChild(int index) { // Fill list of chart accessible child elements if (this._chartAccessibleObjectList == null) { this.FillChartAccessibleObjectList(); } // Return accessible object by index if (index >= 0 && index < this._chartAccessibleObjectList.Count) { return this._chartAccessibleObjectList[index]; } return null; } /// /// Creates a list of chart accessible child elements. /// /// List of chart accessible child elements. private void FillChartAccessibleObjectList() { // Create new list this._chartAccessibleObjectList = new List(); // Chart reference must set first if (this._chart != null) { // Add all Titles into the list foreach (Title title in this._chart.Titles) { this._chartAccessibleObjectList.Add(new ChartChildAccessibleObject( this, this, title, ChartElementType.Title, SR.AccessibilityTitleName(title.Name), title.Text, AccessibleRole.StaticText)); } // Add all Legends into the list foreach (Legend legend in this._chart.Legends) { this._chartAccessibleObjectList.Add(new ChartChildLegendAccessibleObject(this, legend)); } // Add all Chart Areas into the list foreach (ChartArea chartArea in this._chart.ChartAreas) { this._chartAccessibleObjectList.Add(new ChartChildChartAreaAccessibleObject(this, chartArea)); } // Add all annotations into the list foreach (Annotation annotation in this._chart.Annotations) { TextAnnotation textAnnotation = annotation as TextAnnotation; if (textAnnotation != null) { this._chartAccessibleObjectList.Add(new ChartChildAccessibleObject( this, this, annotation, ChartElementType.Annotation, SR.AccessibilityAnnotationName(annotation.Name), textAnnotation.Text, AccessibleRole.StaticText)); } else { this._chartAccessibleObjectList.Add(new ChartChildAccessibleObject( this, this, annotation, ChartElementType.Annotation, SR.AccessibilityAnnotationName(annotation.Name), string.Empty, AccessibleRole.Graphic)); } } } } /// /// Navigates from specified child into specified direction. /// /// Chart child element. /// Chart child element type. /// Navigation direction. /// Accessibility object we just navigated to. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "direction"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "chartElementType"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "chartChildElement")] public AccessibleObject NavigateFromChild(object chartChildElement, ChartElementType chartElementType, AccessibleNavigation direction) { // Not Implemented. Requires Selection Manager code changes. Remove CodeAnalysis.SuppressMessageAttributes return null; } /// /// Selects child chart element. /// /// Chart child element. /// Chart child element type. /// Selection actin. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "selection"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "chartElementType"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "chartChildElement")] public void SelectChild(object chartChildElement, ChartElementType chartElementType, AccessibleSelection selection) { // Not Implemented. Requires Selection Manager code changes. Remove CodeAnalysis.SuppressMessageAttributes } /// /// Checks if specified chart child element is selected. /// /// Chart child element. /// Chart child element type. /// True if child is selected. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "chartElementType"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "chartChildElement")] public bool IsChildSelected(object chartChildElement, ChartElementType chartElementType) { // Not Implemented. Requires Selection Manager code changes. Remove CodeAnalysis.SuppressMessageAttributes return false; } /// /// Gets chart child element bounds position in screen coordinates /// /// Chart child element. /// Chart child element type. /// Series name. /// Series data point index. /// Element boundary in screen coordinates. public Rectangle GetChildBounds(object chartElement, ChartElementType chartElementType, string seriesName, int pointIndex) { // Make sure we have a valid reference on the chart control Rectangle result = Rectangle.Empty; if (this._chart != null && this._chart.chartPicture != null && this._chart.chartPicture.Common != null && this._chart.chartPicture.Common.HotRegionsList != null) { // Execute chart hit test to initialize list of chart element positions if (this._chart.chartPicture.Common.HotRegionsList.List == null || this._chart.chartPicture.Common.HotRegionsList.List.Count == 0) { this._chart.HitTest(0, 0); } // Find specified chart element in the list foreach (HotRegion hotRegion in this._chart.chartPicture.Common.HotRegionsList.List) { if (hotRegion.Type == chartElementType) { // Determine if region should be processed bool processRegion = false; if (chartElementType == ChartElementType.DataPoint || chartElementType == ChartElementType.DataPointLabel) { // In case of data point and data point label their series name and label should match if (hotRegion.SeriesName == seriesName && hotRegion.PointIndex == pointIndex) { processRegion = true; } } else if (hotRegion.SelectedObject == chartElement || hotRegion.SelectedSubObject == chartElement) { processRegion = true; } if (processRegion) { RectangleF bounds = hotRegion.BoundingRectangle; // Conver chart relative coordinates to chart absolute (pixel) coordinates if (hotRegion.RelativeCoordinates) { RectangleF absolute = RectangleF.Empty; absolute.X = bounds.X * (this._chart.Width - 1) / 100F; absolute.Y = bounds.Y * (this._chart.Height - 1) / 100F; absolute.Width = bounds.Width * (this._chart.Width - 1) / 100F; absolute.Height = bounds.Height * (this._chart.Height - 1) / 100F; bounds = absolute; } // Check if chart should be scaled Rectangle rect = Rectangle.Round(bounds); if (this._chartScale.X != 1f || this._chartScale.Y != 1f) { SizeF rectSize = rect.Size; rect.X = (int)(rect.X * this._chartScale.X); rect.Y = (int)(rect.Y * this._chartScale.Y); rectSize.Width *= this._chartScale.X; rectSize.Height *= this._chartScale.Y; rect.Size = Size.Round(rectSize); } // Convert to screen coordinates if (!this.ChartScreenPosition.IsEmpty) { rect.Offset(this.ChartScreenPosition); } else { rect = this._chart.RectangleToScreen(rect); } // If elementd is not gridlines just return the rectangle if (chartElementType != ChartElementType.Gridlines) { return rect; } // For gridlines continue accumulation all gridlines positions if (result.IsEmpty) { result = rect; } else { result = Rectangle.Union(result, rect); } } } } } return result; } #endregion // Methods } /// /// Chart child element accessible object /// internal class ChartChildAccessibleObject : AccessibleObject { #region Fields // Chart element presented by this accessibility object internal object chartChildObject = null; // Chart child object type internal ChartElementType chartChildObjectType = ChartElementType.Nothing; // Chart accessibility object internal ChartAccessibleObject chartAccessibleObject = null; // Chart accessibility object internal AccessibleObject chartAccessibleParentObject = null; // Accessible object role internal AccessibleRole role = AccessibleRole.StaticText; // Accessible object value internal string name = string.Empty; // Accessible object name internal string objectValue = string.Empty; // Series name protected string seriesName = string.Empty; // Data point index internal int dataPointIndex = -1; #endregion // Fields #region Constructors /// /// Initializes chart element accessibility object with specified title. /// /// Chart accessibility object. /// The chart accessible parent object. /// Chart child object. /// Chart child object type. /// Chart child object name. /// Chart child object value. /// Chart child object role. public ChartChildAccessibleObject( ChartAccessibleObject chartAccessibleObject, AccessibleObject chartAccessibleParentObject, object chartChildObject, ChartElementType chartChildObjectType, string name, string objectValue, AccessibleRole role) { this.chartAccessibleObject = chartAccessibleObject; this.chartAccessibleParentObject = chartAccessibleParentObject; this.chartChildObject = chartChildObject; this.chartChildObjectType = chartChildObjectType; this.name = name; this.role = role; this.objectValue = objectValue; } /// /// Initializes chart element accessibility object with specified title. /// /// Chart accessibility object. /// The chart accessible parent object. /// Chart child object. /// Chart child object type. /// Chart child object name. /// Chart child object value. /// Chart child object role. /// Chart series name. /// Chart data point index. public ChartChildAccessibleObject( ChartAccessibleObject chartAccessibleObject, AccessibleObject chartAccessibleParentObject, object chartChildObject, ChartElementType chartChildObjectType, string name, string objectValue, AccessibleRole role, string seriesName, int pointIndex) { this.chartAccessibleObject = chartAccessibleObject; this.chartAccessibleParentObject = chartAccessibleParentObject; this.chartChildObject = chartChildObject; this.chartChildObjectType = chartChildObjectType; this.name = name; this.role = role; this.objectValue = objectValue; this.seriesName = seriesName; this.dataPointIndex = pointIndex; } #endregion // Constructors #region Properties /// /// Gets the Bounds for the accessible object. /// public override Rectangle Bounds { get { return this.chartAccessibleObject.GetChildBounds(this.chartChildObject, this.chartChildObjectType, this.seriesName, this.dataPointIndex); } } /// /// Gets parent accessible object /// public override AccessibleObject Parent { get { if (this.chartAccessibleParentObject != null) { return this.chartAccessibleParentObject; } return this.chartAccessibleObject; } } /// /// Object value /// public override string Value { get { return this.objectValue; } set { this.objectValue = value; } } /// /// Gets accessible object role /// public override AccessibleRole Role { get { return this.role; } } /// /// Gets accessible object state. /// public override AccessibleStates State { get { AccessibleStates state = AccessibleStates.Selectable; if (this.chartAccessibleObject.IsChildSelected(this.chartChildObject, this.chartChildObjectType)) { state |= AccessibleStates.Selected; } return state; } } /// /// Gets the name of the accessibility object. /// public override string Name { get { return this.name; } set { this.name = value; } } #endregion // Properties #region Methods /// /// Navigate through chart child objects. /// /// Navigation direction. /// Accessibility object to navigate to. public override AccessibleObject Navigate(AccessibleNavigation direction) { return this.chartAccessibleObject.NavigateFromChild(this.chartChildObject, this.chartChildObjectType, direction); } /// /// Selects chart child element. /// /// Element to select. public override void Select(AccessibleSelection selection) { this.chartAccessibleObject.SelectChild(this.chartChildObject, this.chartChildObjectType, selection); } #endregion // Methods } /// /// Chart legend element accessible object /// internal class ChartChildLegendAccessibleObject : ChartChildAccessibleObject { #region Fields // List of child accessible objects private List _childList = new List(); #endregion // Fields #region Constructor /// /// Object constructor. /// /// Chart accessible object. /// Chart legend object. public ChartChildLegendAccessibleObject(ChartAccessibleObject chartAccessibleObject, Legend legend) : base( chartAccessibleObject, chartAccessibleObject, legend, ChartElementType.LegendArea, SR.AccessibilityLegendName(legend.Name), string.Empty, AccessibleRole.StaticText) { // Add legend title as a child element if (legend.Title.Length > 0) { this._childList.Add(new ChartChildAccessibleObject( chartAccessibleObject, this, legend, ChartElementType.LegendTitle, SR.AccessibilityLegendTitleName(legend.Name), legend.Title, AccessibleRole.StaticText)); } // NOTE: Legend items are dynamically generated and curently are not part of the list } #endregion // Constructor #region Methods /// /// Gets child accessible object. /// /// Index of the child object to get. /// Chart child accessible object. public override AccessibleObject GetChild(int index) { if (index >= 0 && index < this._childList.Count) { return this._childList[index]; } return null; } /// /// Get number of chart accessible objects. /// /// Number of chart accessible objects. public override int GetChildCount() { return this._childList.Count; } #endregion // Methods } /// /// Chart area element accessible object /// internal class ChartChildChartAreaAccessibleObject : ChartChildAccessibleObject { #region Fields // List of child accessible objects private List _childList = new List(); #endregion // Fields #region Constructor /// /// Object constructor. /// /// Chart accessible object. /// Chart area object. public ChartChildChartAreaAccessibleObject(ChartAccessibleObject chartAccessibleObject, ChartArea chartArea) : base( chartAccessibleObject, chartAccessibleObject, chartArea, ChartElementType.PlottingArea, SR.AccessibilityChartAreaName(chartArea.Name), string.Empty, AccessibleRole.Graphic) { // Add all series shown in the chart area List areaSeries = chartArea.GetSeries(); foreach (Series series in areaSeries) { this._childList.Add(new ChartChildSeriesAccessibleObject(chartAccessibleObject, this, series)); } // Add all axes this.AddAxisAccessibilityObjects(chartAccessibleObject, chartArea.AxisX); this.AddAxisAccessibilityObjects(chartAccessibleObject, chartArea.AxisY); this.AddAxisAccessibilityObjects(chartAccessibleObject, chartArea.AxisX2); this.AddAxisAccessibilityObjects(chartAccessibleObject, chartArea.AxisY2); } #endregion // Constructor #region Methods /// /// Helper method which adds all accessibility objects for specified axis /// /// Chart accessibility object. /// Axis to add accessibility for. private void AddAxisAccessibilityObjects(ChartAccessibleObject chartAccessibleObject, Axis axis) { // Y axis plus title if (axis.enabled) { this._childList.Add(new ChartChildAccessibleObject( chartAccessibleObject, this, axis, ChartElementType.Axis, axis.Name, string.Empty, AccessibleRole.Graphic)); if (axis.Title.Length > 0) { this._childList.Add(new ChartChildAccessibleObject( chartAccessibleObject, this, axis, ChartElementType.AxisTitle, SR.AccessibilityChartAxisTitleName(axis.Name), axis.Title, AccessibleRole.StaticText)); } if (axis.MajorGrid.Enabled) { this._childList.Add(new ChartChildAccessibleObject( chartAccessibleObject, this, axis.MajorGrid, ChartElementType.Gridlines, SR.AccessibilityChartAxisMajorGridlinesName(axis.Name), string.Empty, AccessibleRole.Graphic)); } if (axis.MinorGrid.Enabled) { this._childList.Add(new ChartChildAccessibleObject( chartAccessibleObject, this, axis.MinorGrid, ChartElementType.Gridlines, SR.AccessibilityChartAxisMinorGridlinesName(axis.Name), string.Empty, AccessibleRole.Graphic)); } } } /// /// Gets child accessible object. /// /// Index of the child object to get. /// Chart child accessible object. public override AccessibleObject GetChild(int index) { if (index >= 0 && index < this._childList.Count) { return this._childList[index]; } return null; } /// /// Get number of chart accessible objects. /// /// Number of chart accessible objects. public override int GetChildCount() { return this._childList.Count; } #endregion // Methods } /// /// Chart series element accessible object /// internal class ChartChildSeriesAccessibleObject : ChartChildAccessibleObject { #region Fields // List of child accessible objects private List _childList = new List(); #endregion // Fields #region Constructor /// /// Object constructor. /// /// Chart accessible object. /// Chart series object. public ChartChildSeriesAccessibleObject(ChartAccessibleObject chartAccessibleObject, AccessibleObject parent, Series series) : base( chartAccessibleObject, parent, series, ChartElementType.DataPoint, SR.AccessibilitySeriesName(series.Name), string.Empty, AccessibleRole.Graphic) { // Add all series data points int index = 1; foreach (DataPoint point in series.Points) { this._childList.Add(new ChartChildAccessibleObject( chartAccessibleObject, this, point, ChartElementType.DataPoint, SR.AccessibilityDataPointName(index), string.Empty, AccessibleRole.Graphic, series.Name, index - 1)); if (point.Label.Length > 0 || point.IsValueShownAsLabel) { this._childList.Add(new ChartChildAccessibleObject( chartAccessibleObject, this, point, ChartElementType.DataPointLabel, SR.AccessibilityDataPointLabelName(index), !String.IsNullOrEmpty(point._lastLabelText) ? point._lastLabelText : point.Label, AccessibleRole.Text, series.Name, index - 1)); } ++index; } } #endregion // Constructor #region Methods /// /// Gets child accessible object. /// /// Index of the child object to get. /// Chart child accessible object. public override AccessibleObject GetChild(int index) { if (index >= 0 && index < this._childList.Count) { return this._childList[index]; } return null; } /// /// Get number of chart accessible objects. /// /// Number of chart accessible objects. public override int GetChildCount() { return this._childList.Count; } #endregion // Methods } } #endif