All the controls missing in WPF. Over 1 million downloads.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

665 lines
27 KiB

// (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
{
/// <summary>
/// Represents a dynamic series that uses axes to display data points.
/// </summary>
/// <QualityBand>Preview</QualityBand>
public abstract class DataPointSeriesWithAxes : DataPointSeries, IDataProvider, IRangeProvider, IAxisListener, IValueMarginProvider
{
/// <summary>
/// Gets or sets the data points by dependent value.
/// </summary>
private OrderedMultipleDictionary<IComparable, DataPoint> DataPointsByActualDependentValue { get; set; }
/// <summary>
/// Creates the correct range axis based on the data.
/// </summary>
/// <param name="value">The value to evaluate to determine which type of
/// axis to create.</param>
/// <returns>The range axis appropriate that can plot the provided
/// value.</returns>
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;
}
}
/// <summary>
/// Retrieves the value for a given access from a data point.
/// </summary>
/// <param name="dataPoint">The data point to retrieve the value from.</param>
/// <param name="axis">The axis to retrieve the value for.</param>
/// <returns>A function that returns a value appropriate for the axis
/// when provided a DataPoint.</returns>
protected virtual object GetActualDataPointAxisValue(DataPoint dataPoint, IAxis axis)
{
if (axis == InternalActualIndependentAxis)
{
return dataPoint.ActualIndependentValue;
}
else if (axis == InternalActualDependentAxis)
{
return dataPoint.ActualDependentValue;
}
return null;
}
/// <summary>
/// Gets or sets the actual dependent axis.
/// </summary>
protected IAxis InternalActualDependentAxis { get; set; }
#region public Axis InternalDependentAxis
/// <summary>
/// Stores the internal dependent axis.
/// </summary>
private IAxis _internalDependentAxis;
/// <summary>
/// Gets or sets the value of the internal dependent axis.
/// </summary>
protected IAxis InternalDependentAxis
{
get { return _internalDependentAxis; }
set
{
if (_internalDependentAxis != value)
{
IAxis oldValue = _internalDependentAxis;
_internalDependentAxis = value;
OnInternalDependentAxisPropertyChanged(oldValue, value);
}
}
}
/// <summary>
/// DependentAxisProperty property changed handler.
/// </summary>
/// <param name="oldValue">Old value.</param>
/// <param name="newValue">New value.</param>
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
/// <summary>
/// Gets or sets the actual independent axis value.
/// </summary>
protected IAxis InternalActualIndependentAxis { get; set; }
#region protected Axis InternalIndependentAxis
/// <summary>
/// The internal independent axis.
/// </summary>
private IAxis _internalIndependentAxis;
/// <summary>
/// Gets or sets the value of the internal independent axis.
/// </summary>
protected IAxis InternalIndependentAxis
{
get { return _internalIndependentAxis; }
set
{
if (value != _internalIndependentAxis)
{
IAxis oldValue = _internalIndependentAxis;
_internalIndependentAxis = value;
OnInternalIndependentAxisPropertyChanged(oldValue, value);
}
}
}
/// <summary>
/// IndependentAxisProperty property changed handler.
/// </summary>
/// <param name="oldValue">Old value.</param>
/// <param name="newValue">New value.</param>
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
/// <summary>
/// Initializes a new instance of the DataPointSeriesWithAxes class.
/// </summary>
protected DataPointSeriesWithAxes()
{
this.DataPointsByActualDependentValue =
new OrderedMultipleDictionary<IComparable, DataPoint>(
false,
(left, right) =>
left.CompareTo(right),
(leftDataPoint, rightDataPoint) =>
RuntimeHelpers.GetHashCode(leftDataPoint).CompareTo(RuntimeHelpers.GetHashCode(rightDataPoint)));
}
/// <summary>
/// Update the axes when the specified data point's ActualDependentValue property changes.
/// </summary>
/// <param name="dataPoint">The data point.</param>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
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);
}
/// <summary>
/// Update the axes when the specified data point's DependentValue property changes.
/// </summary>
/// <param name="dataPoint">The data point.</param>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
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);
}
/// <summary>
/// Update axes when the specified data point's effective dependent value changes.
/// </summary>
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));
}
}
}
/// <summary>
/// Update axes when the specified data point's actual independent value changes.
/// </summary>
/// <param name="dataPoint">The data point.</param>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
protected override void OnDataPointActualIndependentValueChanged(DataPoint dataPoint, object oldValue, object newValue)
{
UpdateActualIndependentAxis();
UpdateDataPoint(dataPoint);
base.OnDataPointActualIndependentValueChanged(dataPoint, oldValue, newValue);
}
/// <summary>
/// Update axes when the specified data point's independent value changes.
/// </summary>
/// <param name="dataPoint">The data point.</param>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
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);
}
/// <summary>
/// Update axes when a data point's effective independent value changes.
/// </summary>
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));
}
}
}
/// <summary>
/// Called after data points have been loaded from the items source.
/// </summary>
/// <param name="newDataPoints">New active data points.</param>
/// <param name="oldDataPoints">Old inactive data points.</param>
protected override void OnDataPointsChanged(IList<DataPoint> newDataPoints, IList<DataPoint> 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);
}
/// <summary>
/// Gets or sets a value indicating whether to the axes are being
/// updated.
/// </summary>
private bool UpdatingAllAxes { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the axes have been
/// invalidated.
/// </summary>
private bool AxesInvalidated { get; set; }
/// <summary>
/// Only updates all data points if series has axes.
/// </summary>
/// <param name="dataPoints">A sequence of data points to update.
/// </param>
protected override void UpdateDataPoints(IEnumerable<DataPoint> dataPoints)
{
if (InternalActualIndependentAxis != null && InternalActualDependentAxis != null)
{
base.UpdateDataPoints(dataPoints);
}
}
/// <summary>
/// Method called to get series to acquire the axes it needs. Acquires
/// no axes by default.
/// </summary>
private void GetAxes()
{
if (SeriesHost != null)
{
DataPoint firstDataPoint = ActiveDataPoints.FirstOrDefault();
if (firstDataPoint == null)
{
return;
}
GetAxes(firstDataPoint);
}
}
/// <summary>
/// Method called to get series to acquire the axes it needs. Acquires
/// no axes by default.
/// </summary>
/// <param name="firstDataPoint">The first data point.</param>
protected abstract void GetAxes(DataPoint firstDataPoint);
/// <summary>
/// Method called to get the axes that the series needs.
/// </summary>
/// <param name="firstDataPoint">The first data point.</param>
/// <param name="independentAxisPredicate">A predicate that returns
/// a value indicating whether an axis is an acceptable candidate for
/// the series independent axis.</param>
/// <param name="independentAxisFactory">A function that creates an
/// acceptable independent axis.</param>
/// <param name="dependentAxisPredicate">A predicate that returns
/// a value indicating whether an axis is an acceptable candidate for
/// the series dependent axis.</param>
/// <param name="dependentAxisFactory">A function that creates an
/// acceptable dependent axis.</param>
protected virtual void GetAxes(DataPoint firstDataPoint, Func<IAxis, bool> independentAxisPredicate, Func<IAxis> independentAxisFactory, Func<IAxis, bool> dependentAxisPredicate, Func<IAxis> dependentAxisFactory)
{
Func<IAxis, bool> 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<IAxis, bool> 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);
}
}
}
/// <summary>
/// Updates data points when the axis is invalidated.
/// </summary>
/// <param name="axis">The axis that was invalidated.</param>
void IAxisListener.AxisInvalidated(IAxis axis)
{
if (InternalActualDependentAxis != null && InternalActualIndependentAxis != null && PlotArea != null)
{
if (!UpdatingAllAxes)
{
UpdateDataPoints(ActiveDataPoints);
}
else
{
AxesInvalidated = true;
}
}
}
/// <summary>
/// Returns the actual range of data for a given axis.
/// </summary>
/// <param name="consumer">The axis to retrieve the range for.</param>
/// <returns>The actual range of data.</returns>
protected virtual Range<IComparable> 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<IComparable>();
}
/// <summary>
/// Returns the value margins for a given axis.
/// </summary>
/// <param name="consumer">The axis to retrieve the value margins for.
/// </param>
/// <returns>A sequence of value margins.</returns>
protected virtual IEnumerable<ValueMargin> GetValueMargins(IValueMarginConsumer consumer)
{
IAxis axis = consumer as IAxis;
if (axis != null && ActiveDataPoints.Any())
{
Func<DataPoint, IComparable> 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<DataPoint, DataPoint> 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;
}
}
/// <summary>
/// Returns data to a data consumer.
/// </summary>
/// <param name="dataConsumer">The data consumer requesting the data.
/// </param>
/// <returns>The data for a given data consumer.</returns>
IEnumerable<object> IDataProvider.GetData(IDataConsumer dataConsumer)
{
IAxis axis = (IAxis)dataConsumer;
if (axis == null)
{
throw new ArgumentNullException("dataConsumer");
}
Func<DataPoint, object> selector = null;
if (axis == InternalActualIndependentAxis)
{
if (IndependentValueBinding == null)
{
return Enumerable.Range(1, ActiveDataPointCount).CastWrapper<object>();
}
selector = (dataPoint) => dataPoint.ActualIndependentValue ?? dataPoint.ActualDependentValue;
}
else if (axis == InternalActualDependentAxis)
{
selector = (dataPoint) => dataPoint.ActualDependentValue;
}
return ActiveDataPoints.Select(selector).Distinct();
}
/// <summary>
/// Called when the value of the SeriesHost property changes.
/// </summary>
/// <param name="oldValue">The value to be replaced.</param>
/// <param name="newValue">The new series host value.</param>
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);
}
/// <summary>
/// Returns the data range.
/// </summary>
/// <param name="rangeConsumer">The consumer requesting the range.</param>
/// <returns>The data range.</returns>
Range<IComparable> IRangeProvider.GetRange(IRangeConsumer rangeConsumer)
{
return GetRange(rangeConsumer);
}
/// <summary>
/// Returns the value margins for a given axis.
/// </summary>
/// <param name="axis">The axis to retrieve the value margins for.
/// </param>
/// <returns>A sequence of value margins.</returns>
IEnumerable<ValueMargin> IValueMarginProvider.GetValueMargins(IValueMarginConsumer axis)
{
return GetValueMargins(axis);
}
}
}