// (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.Globalization; using System.Linq; using System.Runtime.CompilerServices; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.DataVisualization; using System.Windows.Controls.DataVisualization.Collections; namespace System.Windows.Controls.DataVisualization.Charting { /// /// Represents a dynamic series that uses axes to display data points. /// /// Preview public abstract class DataPointSeriesWithAxes : DataPointSeries, IDataProvider, IRangeProvider, IAxisListener, IValueMarginProvider { /// /// Gets or sets the data points by dependent value. /// private OrderedMultipleDictionary DataPointsByActualDependentValue { get; set; } /// /// Creates the correct range axis based on the data. /// /// The value to evaluate to determine which type of /// axis to create. /// The range axis appropriate that can plot the provided /// value. protected static IRangeAxis CreateRangeAxisFromData(object value) { double doubleValue; DateTime dateTime; if (ValueHelper.TryConvert(value, out doubleValue)) { return new LinearAxis(); } else if (ValueHelper.TryConvert(value, out dateTime)) { return new DateTimeAxis(); } else { return null; } } /// /// Retrieves the value for a given access from a data point. /// /// The data point to retrieve the value from. /// The axis to retrieve the value for. /// A function that returns a value appropriate for the axis /// when provided a DataPoint. protected virtual object GetActualDataPointAxisValue(DataPoint dataPoint, IAxis axis) { if (axis == InternalActualIndependentAxis) { return dataPoint.ActualIndependentValue; } else if (axis == InternalActualDependentAxis) { return dataPoint.ActualDependentValue; } return null; } /// /// Gets or sets the actual dependent axis. /// protected IAxis InternalActualDependentAxis { get; set; } #region public Axis InternalDependentAxis /// /// Stores the internal dependent axis. /// private IAxis _internalDependentAxis; /// /// Gets or sets the value of the internal dependent axis. /// protected IAxis InternalDependentAxis { get { return _internalDependentAxis; } set { if (_internalDependentAxis != value) { IAxis oldValue = _internalDependentAxis; _internalDependentAxis = value; OnInternalDependentAxisPropertyChanged(oldValue, value); } } } /// /// DependentAxisProperty property changed handler. /// /// Old value. /// New value. protected virtual void OnInternalDependentAxisPropertyChanged(IAxis oldValue, IAxis newValue) { if (newValue != null && InternalActualDependentAxis != null && InternalActualDependentAxis != newValue && InternalActualDependentAxis.RegisteredListeners.Contains(this)) { InternalActualDependentAxis.RegisteredListeners.Remove(this); InternalActualDependentAxis = null; GetAxes(); } } #endregion public Axis InternalDependentAxis /// /// Gets or sets the actual independent axis value. /// protected IAxis InternalActualIndependentAxis { get; set; } #region protected Axis InternalIndependentAxis /// /// The internal independent axis. /// private IAxis _internalIndependentAxis; /// /// Gets or sets the value of the internal independent axis. /// protected IAxis InternalIndependentAxis { get { return _internalIndependentAxis; } set { if (value != _internalIndependentAxis) { IAxis oldValue = _internalIndependentAxis; _internalIndependentAxis = value; OnInternalIndependentAxisPropertyChanged(oldValue, value); } } } /// /// IndependentAxisProperty property changed handler. /// /// Old value. /// New value. protected virtual void OnInternalIndependentAxisPropertyChanged(IAxis oldValue, IAxis newValue) { if (newValue != null && InternalActualIndependentAxis != null && InternalActualIndependentAxis != newValue && InternalActualIndependentAxis.RegisteredListeners.Contains(this)) { InternalActualIndependentAxis.RegisteredListeners.Remove(this); InternalActualIndependentAxis = null; GetAxes(); } } #endregion protected Axis IndependentAxis /// /// Initializes a new instance of the DataPointSeriesWithAxes class. /// protected DataPointSeriesWithAxes() { this.DataPointsByActualDependentValue = new OrderedMultipleDictionary( false, (left, right) => left.CompareTo(right), (leftDataPoint, rightDataPoint) => RuntimeHelpers.GetHashCode(leftDataPoint).CompareTo(RuntimeHelpers.GetHashCode(rightDataPoint))); } /// /// Update the axes when the specified data point's ActualDependentValue property changes. /// /// The data point. /// The old value. /// The new value. protected override void OnDataPointActualDependentValueChanged(DataPoint dataPoint, IComparable oldValue, IComparable newValue) { if (oldValue != null) { bool removed = DataPointsByActualDependentValue.Remove(oldValue, dataPoint); if (removed) { DataPointsByActualDependentValue.Add(newValue, dataPoint); } } UpdateActualDependentAxis(); UpdateDataPoint(dataPoint); base.OnDataPointActualDependentValueChanged(dataPoint, oldValue, newValue); } /// /// Update the axes when the specified data point's DependentValue property changes. /// /// The data point. /// The old value. /// The new value. protected override void OnDataPointDependentValueChanged(DataPoint dataPoint, IComparable oldValue, IComparable newValue) { if ((null != InternalActualDependentAxis)) { dataPoint.BeginAnimation(DataPoint.ActualDependentValueProperty, "ActualDependentValue", newValue, this.TransitionDuration, this.TransitionEasingFunction); } else { dataPoint.ActualDependentValue = newValue; } base.OnDataPointDependentValueChanged(dataPoint, oldValue, newValue); } /// /// Update axes when the specified data point's effective dependent value changes. /// private void UpdateActualDependentAxis() { if (InternalActualDependentAxis != null) { IDataConsumer dataConsumer = InternalActualDependentAxis as IDataConsumer; if (dataConsumer != null) { IDataProvider categoryInformationProvider = (IDataProvider)this; dataConsumer.DataChanged(categoryInformationProvider, categoryInformationProvider.GetData(dataConsumer)); } IRangeConsumer rangeAxis = InternalActualDependentAxis as IRangeConsumer; if (rangeAxis != null) { IRangeProvider rangeInformationProvider = (IRangeProvider)this; rangeAxis.RangeChanged(rangeInformationProvider, rangeInformationProvider.GetRange(rangeAxis)); } } } /// /// Update axes when the specified data point's actual independent value changes. /// /// The data point. /// The old value. /// The new value. protected override void OnDataPointActualIndependentValueChanged(DataPoint dataPoint, object oldValue, object newValue) { UpdateActualIndependentAxis(); UpdateDataPoint(dataPoint); base.OnDataPointActualIndependentValueChanged(dataPoint, oldValue, newValue); } /// /// Update axes when the specified data point's independent value changes. /// /// The data point. /// The old value. /// The new value. protected override void OnDataPointIndependentValueChanged(DataPoint dataPoint, object oldValue, object newValue) { if ((null != InternalActualIndependentAxis) && (InternalActualIndependentAxis is IRangeAxis)) { dataPoint.BeginAnimation(DataPoint.ActualIndependentValueProperty, "ActualIndependentValue", newValue, this.TransitionDuration, this.TransitionEasingFunction); } else { dataPoint.ActualIndependentValue = newValue; } base.OnDataPointIndependentValueChanged(dataPoint, oldValue, newValue); } /// /// Update axes when a data point's effective independent value changes. /// private void UpdateActualIndependentAxis() { if (InternalActualIndependentAxis != null) { ICategoryAxis categoryAxis = InternalActualIndependentAxis as ICategoryAxis; if (categoryAxis != null) { IDataProvider categoryInformationProvider = (IDataProvider)this; categoryAxis.DataChanged(categoryInformationProvider, categoryInformationProvider.GetData(categoryAxis)); } IRangeConsumer rangeAxis = InternalActualIndependentAxis as IRangeConsumer; if (rangeAxis != null) { IRangeProvider rangeInformationProvider = (IRangeProvider)this; rangeAxis.RangeChanged(rangeInformationProvider, rangeInformationProvider.GetRange(rangeAxis)); } } } /// /// Called after data points have been loaded from the items source. /// /// New active data points. /// Old inactive data points. protected override void OnDataPointsChanged(IList newDataPoints, IList oldDataPoints) { foreach (DataPoint dataPoint in newDataPoints) { DataPointsByActualDependentValue.Add(dataPoint.ActualDependentValue, dataPoint); } foreach (DataPoint dataPoint in oldDataPoints) { DataPointsByActualDependentValue.Remove(dataPoint.ActualDependentValue, dataPoint); } GetAxes(); if (InternalActualDependentAxis != null && InternalActualIndependentAxis != null) { Action action = () => { AxesInvalidated = false; UpdatingAllAxes = true; try { UpdateActualIndependentAxis(); UpdateActualDependentAxis(); } finally { UpdatingAllAxes = false; } if (AxesInvalidated) { UpdateDataPoints(ActiveDataPoints); } else { UpdateDataPoints(newDataPoints); } AxesInvalidated = false; }; InvokeOnLayoutUpdated(action); } base.OnDataPointsChanged(newDataPoints, oldDataPoints); } /// /// Gets or sets a value indicating whether to the axes are being /// updated. /// private bool UpdatingAllAxes { get; set; } /// /// Gets or sets a value indicating whether the axes have been /// invalidated. /// private bool AxesInvalidated { get; set; } /// /// Only updates all data points if series has axes. /// /// A sequence of data points to update. /// protected override void UpdateDataPoints(IEnumerable dataPoints) { if (InternalActualIndependentAxis != null && InternalActualDependentAxis != null) { base.UpdateDataPoints(dataPoints); } } /// /// Method called to get series to acquire the axes it needs. Acquires /// no axes by default. /// private void GetAxes() { if (SeriesHost != null) { DataPoint firstDataPoint = ActiveDataPoints.FirstOrDefault(); if (firstDataPoint == null) { return; } GetAxes(firstDataPoint); } } /// /// Method called to get series to acquire the axes it needs. Acquires /// no axes by default. /// /// The first data point. protected abstract void GetAxes(DataPoint firstDataPoint); /// /// Method called to get the axes that the series needs. /// /// The first data point. /// A predicate that returns /// a value indicating whether an axis is an acceptable candidate for /// the series independent axis. /// A function that creates an /// acceptable independent axis. /// A predicate that returns /// a value indicating whether an axis is an acceptable candidate for /// the series dependent axis. /// A function that creates an /// acceptable dependent axis. protected virtual void GetAxes(DataPoint firstDataPoint, Func independentAxisPredicate, Func independentAxisFactory, Func dependentAxisPredicate, Func dependentAxisFactory) { Func actualIndependentAxisPredicate = (axis) => independentAxisPredicate(axis) && axis.CanPlot(firstDataPoint.IndependentValue); IAxis workingIndependentAxis = null; if (this.InternalActualIndependentAxis == null) { if (this.InternalIndependentAxis != null) { if (actualIndependentAxisPredicate(this.InternalIndependentAxis)) { workingIndependentAxis = this.InternalIndependentAxis; } else { throw new InvalidOperationException(Properties.Resources.DataPointSeriesWithAxes_GetAxes_AssignedIndependentAxisCannotBeUsed); } } if (workingIndependentAxis == null) { workingIndependentAxis = this.SeriesHost.Axes.FirstOrDefault(actualIndependentAxisPredicate); } if (workingIndependentAxis == null) { workingIndependentAxis = independentAxisFactory(); } this.InternalActualIndependentAxis = workingIndependentAxis; if (!workingIndependentAxis.RegisteredListeners.Contains(this)) { workingIndependentAxis.RegisteredListeners.Add(this); } if (!this.SeriesHost.Axes.Contains(workingIndependentAxis)) { this.SeriesHost.Axes.Add(workingIndependentAxis); } } Func actualDependentAxisPredicate = (axis) => dependentAxisPredicate(axis) && axis.CanPlot(firstDataPoint.DependentValue); IAxis workingDependentAxis = null; if (this.InternalActualDependentAxis == null) { if (this.InternalDependentAxis != null) { if (actualDependentAxisPredicate(this.InternalDependentAxis)) { workingDependentAxis = this.InternalDependentAxis; } else { throw new InvalidOperationException(Properties.Resources.DataPointSeriesWithAxes_GetAxes_AssignedDependentAxisCannotBeUsed); } } if (workingDependentAxis == null) { workingDependentAxis = InternalActualIndependentAxis.DependentAxes.Concat(this.SeriesHost.Axes).FirstOrDefault(actualDependentAxisPredicate); } if (workingDependentAxis == null) { workingDependentAxis = dependentAxisFactory(); } this.InternalActualDependentAxis = workingDependentAxis; if (!workingDependentAxis.RegisteredListeners.Contains(this)) { workingDependentAxis.RegisteredListeners.Add(this); } // Only add axis to the axes collection of the series host if // it is not a dependent axis belonging to the acquired // independent axis. if (!this.SeriesHost.Axes.Contains(workingDependentAxis) && !InternalActualIndependentAxis.DependentAxes.Contains(workingDependentAxis)) { this.SeriesHost.Axes.Add(workingDependentAxis); } } } /// /// Updates data points when the axis is invalidated. /// /// The axis that was invalidated. void IAxisListener.AxisInvalidated(IAxis axis) { if (InternalActualDependentAxis != null && InternalActualIndependentAxis != null && PlotArea != null) { if (!UpdatingAllAxes) { UpdateDataPoints(ActiveDataPoints); } else { AxesInvalidated = true; } } } /// /// Returns the actual range of data for a given axis. /// /// The axis to retrieve the range for. /// The actual range of data. protected virtual Range GetRange(IRangeConsumer consumer) { if (consumer == null) { throw new ArgumentNullException("consumer"); } if (consumer == InternalActualDependentAxis) { if (this.DataPointsByActualDependentValue.Count > 0) { return this.DataPointsByActualDependentValue.GetKeyRange(); } } IAxis axis = consumer as IAxis; return (axis != null) ? ActiveDataPoints.Select(dataPoint => (IComparable)GetActualDataPointAxisValue(dataPoint, axis)).GetRange() : new Range(); } /// /// Returns the value margins for a given axis. /// /// The axis to retrieve the value margins for. /// /// A sequence of value margins. protected virtual IEnumerable GetValueMargins(IValueMarginConsumer consumer) { IAxis axis = consumer as IAxis; if (axis != null && ActiveDataPoints.Any()) { Func selector = null; DataPoint minimumPoint = null; DataPoint maximumPoint = null; double margin = 0.0; if (axis == InternalActualIndependentAxis) { selector = (dataPoint) => (IComparable)dataPoint.ActualIndependentValue; minimumPoint = ActiveDataPoints.MinOrNull(selector); maximumPoint = ActiveDataPoints.MaxOrNull(selector); margin = minimumPoint.GetActualMargin(this.InternalActualIndependentAxis); } else if (axis == InternalActualDependentAxis) { selector = (dataPoint) => (IComparable)dataPoint.ActualDependentValue; Tuple largestAndSmallestValues = this.DataPointsByActualDependentValue.GetLargestAndSmallestValues(); minimumPoint = largestAndSmallestValues.Item1; maximumPoint = largestAndSmallestValues.Item2; margin = minimumPoint.GetActualMargin(this.InternalActualDependentAxis); } yield return new ValueMargin(selector(minimumPoint), margin, margin); yield return new ValueMargin(selector(maximumPoint), margin, margin); } else { yield break; } } /// /// Returns data to a data consumer. /// /// The data consumer requesting the data. /// /// The data for a given data consumer. IEnumerable IDataProvider.GetData(IDataConsumer dataConsumer) { IAxis axis = (IAxis)dataConsumer; if (axis == null) { throw new ArgumentNullException("dataConsumer"); } Func selector = null; if (axis == InternalActualIndependentAxis) { if (IndependentValueBinding == null) { return Enumerable.Range(1, ActiveDataPointCount).CastWrapper(); } selector = (dataPoint) => dataPoint.ActualIndependentValue ?? dataPoint.ActualDependentValue; } else if (axis == InternalActualDependentAxis) { selector = (dataPoint) => dataPoint.ActualDependentValue; } return ActiveDataPoints.Select(selector).Distinct(); } /// /// Called when the value of the SeriesHost property changes. /// /// The value to be replaced. /// The new series host value. protected override void OnSeriesHostPropertyChanged(ISeriesHost oldValue, ISeriesHost newValue) { if (oldValue != null) { if (InternalActualIndependentAxis != null) { InternalActualIndependentAxis.RegisteredListeners.Remove(this); InternalActualIndependentAxis = null; } if (InternalActualDependentAxis != null) { InternalActualDependentAxis.RegisteredListeners.Remove(this); InternalActualDependentAxis = null; } } base.OnSeriesHostPropertyChanged(oldValue, newValue); } /// /// Returns the data range. /// /// The consumer requesting the range. /// The data range. Range IRangeProvider.GetRange(IRangeConsumer rangeConsumer) { return GetRange(rangeConsumer); } /// /// Returns the value margins for a given axis. /// /// The axis to retrieve the value margins for. /// /// A sequence of value margins. IEnumerable IValueMarginProvider.GetValueMargins(IValueMarginConsumer axis) { return GetValueMargins(axis); } } }