// (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