// (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Windows; using System.Windows.Media; using System.Windows.Controls; using System.Windows.Shapes; namespace System.Windows.Controls.DataVisualization.Charting { /// /// An axis that has a range. /// public abstract class DisplayAxis : Axis, IRequireSeriesHost { /// /// Maximum intervals per 200 pixels. /// protected const double MaximumAxisIntervalsPer200Pixels = 8; /// /// The name of the axis grid template part. /// protected const string AxisGridName = "AxisGrid"; /// /// The name of the axis title template part. /// protected const string AxisTitleName = "AxisTitle"; #region public Style AxisLabelStyle /// /// Gets or sets the style used for the axis labels. /// public Style AxisLabelStyle { get { return GetValue(AxisLabelStyleProperty) as Style; } set { SetValue(AxisLabelStyleProperty, value); } } /// /// Identifies the AxisLabelStyle dependency property. /// public static readonly DependencyProperty AxisLabelStyleProperty = DependencyProperty.Register( "AxisLabelStyle", typeof(Style), typeof(DisplayAxis), new PropertyMetadata(null, OnAxisLabelStylePropertyChanged)); /// /// AxisLabelStyleProperty property changed handler. /// /// DisplayAxis that changed its AxisLabelStyle. /// Event arguments. private static void OnAxisLabelStylePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { DisplayAxis source = (DisplayAxis)d; Style oldValue = (Style)e.OldValue; Style newValue = (Style)e.NewValue; source.OnAxisLabelStylePropertyChanged(oldValue, newValue); } /// /// AxisLabelStyleProperty property changed handler. /// /// Old value. /// New value. protected virtual void OnAxisLabelStylePropertyChanged(Style oldValue, Style newValue) { } #endregion public Style AxisLabelStyle /// /// Gets the actual length. /// protected double ActualLength { get { return GetLength(new Size(this.ActualWidth, this.ActualHeight)); } } /// /// Returns the length of the axis given an available size. /// /// The available size. /// The length of the axis given an available size. protected double GetLength(Size availableSize) { if (this.ActualHeight == 0.0 && this.ActualWidth == 0.0) { return 0.0; } if (this.Orientation == AxisOrientation.X) { return availableSize.Width; } else if (this.Orientation == AxisOrientation.Y) { return availableSize.Height; } else { throw new InvalidOperationException(Properties.Resources.DisplayAxis_GetLength_CannotDetermineTheLengthOfAnAxisWithAnOrientationOfNone); } } #region private GridLines GridLines /// /// This field stores the grid lines element. /// private DisplayAxisGridLines _gridLines; /// /// Gets or sets the grid lines property. /// internal DisplayAxisGridLines GridLines { get { return _gridLines; } set { if (value != _gridLines) { DisplayAxisGridLines oldValue = _gridLines; _gridLines = value; OnGridLinesPropertyChanged(oldValue, value); } } } /// /// GridLinesProperty property changed handler. /// /// Old value. /// New value. private void OnGridLinesPropertyChanged(DisplayAxisGridLines oldValue, DisplayAxisGridLines newValue) { if (SeriesHost != null && oldValue != null) { SeriesHost.BackgroundElements.Remove(oldValue); } if (SeriesHost != null && newValue != null) { SeriesHost.BackgroundElements.Add(newValue); } } #endregion private GridLines GridLines #region public Style MajorTickMarkStyle /// /// Gets or sets the style applied to the Axis tick marks. /// /// The Style applied to the Axis tick marks. public Style MajorTickMarkStyle { get { return GetValue(MajorTickMarkStyleProperty) as Style; } set { SetValue(MajorTickMarkStyleProperty, value); } } /// /// Identifies the MajorTickMarkStyle dependency property. /// public static readonly DependencyProperty MajorTickMarkStyleProperty = DependencyProperty.Register( "MajorTickMarkStyle", typeof(Style), typeof(DisplayAxis), new PropertyMetadata(null, OnMajorTickMarkStylePropertyChanged)); /// /// MajorTickMarkStyleProperty property changed handler. /// /// DisplayAxis that changed its MajorTickMarkStyle. /// Event arguments. private static void OnMajorTickMarkStylePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { DisplayAxis source = (DisplayAxis)d; Style oldValue = (Style)e.OldValue; Style newValue = (Style)e.NewValue; source.OnMajorTickMarkStylePropertyChanged(oldValue, newValue); } /// /// MajorTickMarkStyleProperty property changed handler. /// /// Old value. /// New value. protected virtual void OnMajorTickMarkStylePropertyChanged(Style oldValue, Style newValue) { } #endregion public Style MajorTickMarkStyle #region public object Title /// /// Gets or sets the title property. /// public object Title { get { return GetValue(TitleProperty) as object; } set { SetValue(TitleProperty, value); } } /// /// Identifies the Title dependency property. /// public static readonly DependencyProperty TitleProperty = DependencyProperty.Register( "Title", typeof(object), typeof(DisplayAxis), new PropertyMetadata(null, OnTitlePropertyChanged)); /// /// TitleProperty property changed handler. /// /// DisplayAxis that changed its Title. /// Event arguments. private static void OnTitlePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { DisplayAxis source = (DisplayAxis)d; object oldValue = (object)e.OldValue; object newValue = (object)e.NewValue; source.OnTitlePropertyChanged(oldValue, newValue); } /// /// TitleProperty property changed handler. /// /// Old value. /// New value. protected virtual void OnTitlePropertyChanged(object oldValue, object newValue) { if (this.AxisTitle != null) { this.AxisTitle.Content = Title; } } #endregion public object Title /// /// Gets or sets the LayoutTransformControl used to rotate the title. /// private LayoutTransformControl TitleLayoutTransformControl { get; set; } #region public Style TitleStyle /// /// Gets or sets the style applied to the Axis title. /// /// The Style applied to the Axis title. public Style TitleStyle { get { return GetValue(TitleStyleProperty) as Style; } set { SetValue(TitleStyleProperty, value); } } /// /// Identifies the TitleStyle dependency property. /// public static readonly DependencyProperty TitleStyleProperty = DependencyProperty.Register( "TitleStyle", typeof(Style), typeof(DisplayAxis), null); #endregion #region public bool ShowGridLines /// /// Gets or sets a value indicating whether grid lines should be shown. /// [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "GridLines", Justification = "This is the expected casing.")] public bool ShowGridLines { get { return (bool)GetValue(ShowGridLinesProperty); } set { SetValue(ShowGridLinesProperty, value); } } /// /// Identifies the ShowGridLines dependency property. /// [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "GridLines", Justification = "This is the expected capitalization.")] [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "GridLine", Justification = "This is the expected capitalization.")] public static readonly DependencyProperty ShowGridLinesProperty = DependencyProperty.Register( "ShowGridLines", typeof(bool), typeof(DisplayAxis), new PropertyMetadata(false, OnShowGridLinesPropertyChanged)); /// /// ShowGridLinesProperty property changed handler. /// /// Axis that changed its ShowGridLines. /// Event arguments. private static void OnShowGridLinesPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { DisplayAxis source = (DisplayAxis)d; bool oldValue = (bool)e.OldValue; bool newValue = (bool)e.NewValue; source.OnShowGridLinesPropertyChanged(oldValue, newValue); } /// /// ShowGridLinesProperty property changed handler. /// /// Old value. /// New value. [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "GridLine", Justification = "This is the expected capitalization.")] [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "GridLines", Justification = "This is the expected capitalization.")] protected virtual void OnShowGridLinesPropertyChanged(bool oldValue, bool newValue) { SetShowGridLines(newValue); } #endregion public bool ShowGridLines /// /// Creates and destroys a grid lines element based on the specified /// value. /// /// A value indicating whether to display grid /// lines or not. private void SetShowGridLines(bool newValue) { if (newValue == true) { this.GridLines = new OrientedAxisGridLines(this); } else { this.GridLines = null; } } #region public Style GridLineStyle /// /// Gets or sets the Style of the Axis's gridlines. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "GridLine", Justification = "Current casing is the expected one.")] public Style GridLineStyle { get { return GetValue(GridLineStyleProperty) as Style; } set { SetValue(GridLineStyleProperty, value); } } /// /// Identifies the GridlineStyle dependency property. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "GridLine", Justification = "Current casing is the expected one.")] public static readonly DependencyProperty GridLineStyleProperty = DependencyProperty.Register( "GridLineStyle", typeof(Style), typeof(DisplayAxis), null); #endregion /// /// The grid used to layout the axis. /// private Grid _grid; /// /// Gets or sets the grid used to layout the axis. /// private Grid AxisGrid { get { return _grid; } set { if (_grid != value) { if (_grid != null) { _grid.Children.Clear(); } _grid = value; if (_grid != null) { _grid.Children.Add(this.OrientedPanel); if (this.AxisTitle != null) { _grid.Children.Add(this.AxisTitle); } } } } } /// /// Gets or sets a grid to lay out the dependent axis. /// private Grid DependentAxisGrid { get; set; } /// /// Gets the oriented panel used to layout the axis labels. /// internal OrientedPanel OrientedPanel { get; private set; } /// /// The control used to display the axis title. /// private Title _axisTitle; /// /// Gets or sets the title control used to display the title. /// private Title AxisTitle { get { return _axisTitle; } set { if (_axisTitle != value) { if (_axisTitle != null) { _axisTitle.Content = null; } _axisTitle = value; if (Title != null) { _axisTitle.Content = Title; } } } } /// /// Creates a major axis tick mark. /// /// A line to used to render a tick mark. protected virtual Line CreateMajorTickMark() { return CreateTickMark(MajorTickMarkStyle); } /// /// Creates a tick mark and applies a style to it. /// /// The style to apply. /// The newly created tick mark. protected Line CreateTickMark(Style style) { Line line = new Line(); line.Style = style; if (this.Orientation == AxisOrientation.Y) { line.Y1 = 0.5; line.Y2 = 0.5; } else if (this.Orientation == AxisOrientation.X) { line.X1 = 0.5; line.X2 = 0.5; } return line; } /// /// This method is used to share the grid line coordinates with the /// internal grid lines control. /// /// A sequence of the major grid line coordinates. internal IEnumerable InternalGetMajorGridLinePositions() { return GetMajorGridLineCoordinates(new Size(this.ActualWidth, this.ActualHeight)); } /// /// Returns the coordinates to use for the grid line control. /// /// The available size. /// A sequence of coordinates at which to draw grid lines. /// [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "GridLine", Justification = "This is the expected capitalization.")] [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Returns the coordinates of the grid lines.")] protected abstract IEnumerable GetMajorGridLineCoordinates(Size availableSize); #if !SILVERLIGHT /// /// Initializes the static members of the DisplayAxis class. /// [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Dependency properties are initialized in-line.")] static DisplayAxis() { DefaultStyleKeyProperty.OverrideMetadata(typeof(DisplayAxis), new FrameworkPropertyMetadata(typeof(DisplayAxis))); } #endif /// /// Instantiates a new instance of the DisplayAxis class. /// protected DisplayAxis() { this.OrientedPanel = new OrientedPanel(); #if SILVERLIGHT this.DefaultStyleKey = typeof(DisplayAxis); this.OrientedPanel.UseLayoutRounding = true; #endif this.DependentAxisGrid = new Grid(); this.TitleLayoutTransformControl = new LayoutTransformControl(); this.TitleLayoutTransformControl.HorizontalAlignment = HorizontalAlignment.Center; this.TitleLayoutTransformControl.VerticalAlignment = VerticalAlignment.Center; this.SizeChanged += new SizeChangedEventHandler(DisplayAxisSizeChanged); } /// /// If display axis has just become visible, invalidate. /// /// The source of the event. /// Information about the event. private void DisplayAxisSizeChanged(object sender, SizeChangedEventArgs e) { if (e.PreviousSize.Width == 0.0 && e.PreviousSize.Height == 0.0) { Invalidate(); } } /// /// Creates an axis label. /// /// The new axis label. protected virtual Control CreateAxisLabel() { return new AxisLabel(); } /// /// Updates the grid lines element if a suitable dependent axis has /// been added to a radial axis. /// protected override void OnDependentAxesCollectionChanged() { SetShowGridLines(ShowGridLines); base.OnDependentAxesCollectionChanged(); } /// /// Prepares an axis label to be plotted. /// /// The axis label to prepare. /// The data context to use for the axis /// label. protected virtual void PrepareAxisLabel(Control label, object dataContext) { label.DataContext = dataContext; label.SetStyle(AxisLabelStyle); } /// /// Retrieves template parts and configures layout. /// public override void OnApplyTemplate() { base.OnApplyTemplate(); this.AxisGrid = GetTemplateChild(AxisGridName) as Grid; this.AxisTitle = GetTemplateChild(AxisTitleName) as Title; if (this.AxisTitle != null && this.AxisGrid.Children.Contains(this.AxisTitle)) { this.AxisGrid.Children.Remove(this.AxisTitle); this.TitleLayoutTransformControl.Child = this.AxisTitle; this.AxisGrid.Children.Add(this.TitleLayoutTransformControl); } ArrangeAxisGrid(); } /// /// When the size of the oriented panel changes invalidate the axis. /// /// The source of the event. /// Information about the event. private void OnOrientedPanelSizeChanged(object sender, SizeChangedEventArgs e) { Invalidate(); } /// /// Arranges the grid when the location property is changed. /// /// The old location. /// The new location. protected override void OnLocationPropertyChanged(AxisLocation oldValue, AxisLocation newValue) { ArrangeAxisGrid(); base.OnLocationPropertyChanged(oldValue, newValue); } /// /// Arranges the elements in the axis grid. /// private void ArrangeAxisGrid() { if (this.AxisGrid != null) { this.AxisGrid.ColumnDefinitions.Clear(); this.AxisGrid.RowDefinitions.Clear(); this.AxisGrid.Children.Clear(); if (this.Orientation == AxisOrientation.Y) { this.OrientedPanel.Orientation = System.Windows.Controls.Orientation.Vertical; this.OrientedPanel.IsReversed = true; if (this.Location == AxisLocation.Left || this.Location == AxisLocation.Right) { this.TitleLayoutTransformControl.Transform = new RotateTransform { Angle = -90.0 }; this.OrientedPanel.IsInverted = !(Location == AxisLocation.Right); this.AxisGrid.ColumnDefinitions.Add(new ColumnDefinition()); this.AxisGrid.RowDefinitions.Add(new RowDefinition()); int column = 0; if (this.AxisTitle != null) { this.AxisGrid.ColumnDefinitions.Add(new ColumnDefinition()); Grid.SetRow(this.TitleLayoutTransformControl, 0); Grid.SetColumn(this.TitleLayoutTransformControl, 0); column++; } Grid.SetRow(this.OrientedPanel, 0); Grid.SetColumn(this.OrientedPanel, column); this.AxisGrid.Children.Add(this.TitleLayoutTransformControl); this.AxisGrid.Children.Add(this.OrientedPanel); if (this.Location == AxisLocation.Right) { AxisGrid.Mirror(System.Windows.Controls.Orientation.Vertical); this.TitleLayoutTransformControl.Transform = new RotateTransform { Angle = 90 }; } } } else if (this.Orientation == AxisOrientation.X) { this.OrientedPanel.Orientation = System.Windows.Controls.Orientation.Horizontal; this.OrientedPanel.IsReversed = false; if (this.Location == AxisLocation.Top || this.Location == AxisLocation.Bottom) { this.OrientedPanel.IsInverted = (Location == AxisLocation.Top); this.TitleLayoutTransformControl.Transform = new RotateTransform { Angle = 0 }; this.AxisGrid.ColumnDefinitions.Add(new ColumnDefinition()); this.AxisGrid.RowDefinitions.Add(new RowDefinition()); if (this.AxisTitle != null) { this.AxisGrid.RowDefinitions.Add(new RowDefinition()); Grid.SetColumn(this.TitleLayoutTransformControl, 0); Grid.SetRow(this.TitleLayoutTransformControl, 1); } Grid.SetColumn(this.OrientedPanel, 0); Grid.SetRow(this.OrientedPanel, 0); this.AxisGrid.Children.Add(this.TitleLayoutTransformControl); this.AxisGrid.Children.Add(this.OrientedPanel); if (this.Location == AxisLocation.Top) { AxisGrid.Mirror(System.Windows.Controls.Orientation.Horizontal); } } } Invalidate(); } } /// /// Renders the axis. /// /// The available size. /// The required size. protected override Size MeasureOverride(Size availableSize) { RenderAxis(availableSize); return base.MeasureOverride(availableSize); } /// /// Reformulates the grid when the orientation is changed. Grid is /// either separated into two columns or two rows. The title is /// inserted with the outermost section from the edge and an oriented /// panel is inserted into the innermost section. /// /// The old value. /// The new value. protected override void OnOrientationPropertyChanged(AxisOrientation oldValue, AxisOrientation newValue) { ArrangeAxisGrid(); base.OnOrientationPropertyChanged(oldValue, newValue); } /// /// Updates the visual appearance of the axis when it is invalidated. /// /// Information for the invalidated event. protected override void OnInvalidated(RoutedEventArgs args) { InvalidateMeasure(); base.OnInvalidated(args); } /// /// Renders the axis if there is a valid value for orientation. /// /// The available size in which to render /// the axis. private void RenderAxis(Size availableSize) { if (Orientation != AxisOrientation.None && Location != AxisLocation.Auto) { Render(availableSize); } } /// /// Renders the axis labels, tick marks, and other visual elements. /// /// The available size. protected abstract void Render(Size availableSize); /// /// Invalidates the axis. /// protected void Invalidate() { OnInvalidated(new RoutedEventArgs()); } /// /// The series host. /// private ISeriesHost _seriesHost; /// /// Gets or sets the series host. /// public ISeriesHost SeriesHost { get { return _seriesHost; } set { if (value != _seriesHost) { ISeriesHost oldValue = _seriesHost; _seriesHost = value; OnSeriesHostPropertyChanged(oldValue, value); } } } /// /// This method is run when the series host property is changed. /// /// The old series host. /// The new series host. protected virtual void OnSeriesHostPropertyChanged(ISeriesHost oldValue, ISeriesHost newValue) { if (oldValue != null && this.GridLines != null) { oldValue.BackgroundElements.Remove(this.GridLines); } if (newValue != null && this.GridLines != null) { newValue.BackgroundElements.Add(this.GridLines); } } } }