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.

358 lines
15 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.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace System.Windows.Controls.DataVisualization.Charting
{
/// <summary>
/// This series serves as the base class for the column and bar series.
/// </summary>
/// <typeparam name="T">The type of the data point.</typeparam>
public abstract class ColumnBarBaseSeries<T> : DataPointSingleSeriesWithAxes, IAnchoredToOrigin
where T : DataPoint, new()
{
#region public IRangeAxis DependentRangeAxis
/// <summary>
/// Gets or sets the dependent range axis.
/// </summary>
public IRangeAxis DependentRangeAxis
{
get { return GetValue(DependentRangeAxisProperty) as IRangeAxis; }
set { SetValue(DependentRangeAxisProperty, value); }
}
/// <summary>
/// Identifies the DependentRangeAxis dependency property.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", Justification = "This member is necessary because child classes need to share this dependency property.")]
public static readonly DependencyProperty DependentRangeAxisProperty =
DependencyProperty.Register(
"DependentRangeAxis",
typeof(IRangeAxis),
typeof(ColumnBarBaseSeries<T>),
new PropertyMetadata(null, OnDependentRangeAxisPropertyChanged));
/// <summary>
/// DependentRangeAxisProperty property changed handler.
/// </summary>
/// <param name="d">ColumnBarBaseSeries that changed its DependentRangeAxis.</param>
/// <param name="e">Event arguments.</param>
private static void OnDependentRangeAxisPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ColumnBarBaseSeries<T> source = (ColumnBarBaseSeries<T>)d;
IRangeAxis newValue = (IRangeAxis)e.NewValue;
source.OnDependentRangeAxisPropertyChanged(newValue);
}
/// <summary>
/// DependentRangeAxisProperty property changed handler.
/// </summary>
/// <param name="newValue">New value.</param>
private void OnDependentRangeAxisPropertyChanged(IRangeAxis newValue)
{
InternalDependentAxis = (IAxis)newValue;
}
#endregion public IRangeAxis DependentRangeAxis
#region public IAxis IndependentAxis
/// <summary>
/// Gets or sets the independent category axis.
/// </summary>
public IAxis IndependentAxis
{
get { return GetValue(IndependentAxisProperty) as IAxis; }
set { SetValue(IndependentAxisProperty, value); }
}
/// <summary>
/// Identifies the IndependentAxis dependency property.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", Justification = "This member is necessary because child classes need to share this dependency property.")]
public static readonly DependencyProperty IndependentAxisProperty =
DependencyProperty.Register(
"IndependentAxis",
typeof(IAxis),
typeof(ColumnBarBaseSeries<T>),
new PropertyMetadata(null, OnIndependentAxisPropertyChanged));
/// <summary>
/// IndependentAxisProperty property changed handler.
/// </summary>
/// <param name="d">ColumnBarBaseSeries that changed its IndependentAxis.</param>
/// <param name="e">Event arguments.</param>
private static void OnIndependentAxisPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ColumnBarBaseSeries<T> source = (ColumnBarBaseSeries<T>)d;
IAxis newValue = (IAxis)e.NewValue;
source.OnIndependentAxisPropertyChanged(newValue);
}
/// <summary>
/// IndependentAxisProperty property changed handler.
/// </summary>
/// <param name="newValue">New value.</param>
private void OnIndependentAxisPropertyChanged(IAxis newValue)
{
InternalIndependentAxis = (IAxis)newValue;
}
#endregion public IAxis IndependentAxis
/// <summary>
/// Keeps a list of DataPoints that share the same category.
/// </summary>
private IDictionary<object, IGrouping<object, DataPoint>> _categoriesWithMultipleDataPoints;
/// <summary>
/// Returns the group of data points in a given category.
/// </summary>
/// <param name="category">The category for which to return the data
/// point group.</param>
/// <returns>The group of data points in a given category.</returns>
protected IGrouping<object, DataPoint> GetDataPointGroup(object category)
{
return _categoriesWithMultipleDataPoints[category];
}
/// <summary>
/// Returns a value indicating whether a data point corresponding to
/// a category is grouped.
/// </summary>
/// <param name="category">The category.</param>
/// <returns>A value indicating whether a data point corresponding to
/// a category is grouped.</returns>
protected bool GetIsDataPointGrouped(object category)
{
return _categoriesWithMultipleDataPoints.ContainsKey(category);
}
/// <summary>
/// The length of each data point.
/// </summary>
private double? _dataPointlength;
/// <summary>
/// Gets the dependent axis as a range axis.
/// </summary>
public IRangeAxis ActualDependentRangeAxis { get { return this.InternalActualDependentAxis as IRangeAxis; } }
/// <summary>
/// Gets the independent axis as a category axis.
/// </summary>
public IAxis ActualIndependentAxis { get { return this.InternalActualIndependentAxis; } }
/// <summary>
/// Initializes a new instance of the ColumnBarBaseSeries class.
/// </summary>
protected ColumnBarBaseSeries()
{
}
/// <summary>
/// Method run before DataPoints are updated.
/// </summary>
protected override void OnBeforeUpdateDataPoints()
{
base.OnBeforeUpdateDataPoints();
CalculateDataPointLength();
// Update the list of DataPoints with the same category
_categoriesWithMultipleDataPoints = ActiveDataPoints
.Where(point => null != point.IndependentValue)
.OrderBy(point => point.DependentValue)
.GroupBy(point => point.IndependentValue)
.Where(grouping => 1 < grouping.Count())
.ToDictionary(grouping => grouping.Key);
}
/// <summary>
/// Returns the custom ResourceDictionary to use for necessary resources.
/// </summary>
/// <returns>
/// ResourceDictionary to use for necessary resources.
/// </returns>
protected override IEnumerator<ResourceDictionary> GetResourceDictionaryEnumeratorFromHost()
{
return GetResourceDictionaryWithTargetType(SeriesHost, typeof(T), true);
}
/// <summary>
/// Updates a data point when its actual dependent value has changed.
/// </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)
{
UpdateDataPoint(dataPoint);
base.OnDataPointActualDependentValueChanged(dataPoint, oldValue, newValue);
}
/// <summary>
/// Redraws other column series to assure they allocate the right amount
/// of space for their columns.
/// </summary>
/// <param name="seriesHost">The series host to update.</param>
protected void RedrawOtherSeries(ISeriesHost seriesHost)
{
Type thisType = typeof(ColumnBarBaseSeries<T>);
// redraw all other column series to ensure they make space for new one
foreach (ColumnBarBaseSeries<T> series in seriesHost.Series.Where(series => thisType.IsAssignableFrom(series.GetType())).OfType<ColumnBarBaseSeries<T>>().Where(series => series != this))
{
series.UpdateDataPoints(series.ActiveDataPoints);
}
}
/// <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)
{
base.OnDataPointsChanged(newDataPoints, oldDataPoints);
CalculateDataPointLength();
if (this.SeriesHost != null)
{
RedrawOtherSeries(this.SeriesHost);
}
}
/// <summary>
/// Redraw other column series when removed from a series host.
/// </summary>
/// <param name="oldValue">The old value of the series host property.</param>
/// <param name="newValue">The new value of the series host property.</param>
protected override void OnSeriesHostPropertyChanged(ISeriesHost oldValue, ISeriesHost newValue)
{
base.OnSeriesHostPropertyChanged(oldValue, newValue);
// If being removed from series host, redraw all column series.
if (newValue == null || oldValue != null)
{
RedrawOtherSeries(oldValue);
}
}
/// <summary>
/// Creates the bar data point.
/// </summary>
/// <returns>A bar data point.</returns>
protected override DataPoint CreateDataPoint()
{
return new T();
}
/// <summary>
/// Calculates the length of the data points.
/// </summary>
protected void CalculateDataPointLength()
{
if (!(ActualIndependentAxis is ICategoryAxis))
{
IEnumerable<UnitValue> values =
ActiveDataPoints
.Select(dataPoint => ActualIndependentAxis.GetPlotAreaCoordinate(dataPoint.ActualIndependentValue))
.Where(value => ValueHelper.CanGraph(value.Value))
.OrderBy(value => value.Value)
.ToList();
_dataPointlength =
EnumerableFunctions.Zip(
values,
values.Skip(1),
(left, right) => new Range<double>(left.Value, right.Value))
.Select(range => range.Maximum - range.Minimum)
.MinOrNullable();
}
}
/// <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 override IEnumerable<ValueMargin> GetValueMargins(IValueMarginConsumer consumer)
{
double dependentValueMargin = this.ActualHeight / 10;
IAxis axis = consumer as IAxis;
if (axis != null && ActiveDataPoints.Any())
{
Func<DataPoint, IComparable> selector = null;
if (axis == InternalActualIndependentAxis)
{
selector = (dataPoint) => (IComparable)dataPoint.ActualIndependentValue;
DataPoint minimumPoint = ActiveDataPoints.MinOrNull(selector);
DataPoint maximumPoint = ActiveDataPoints.MaxOrNull(selector);
double minimumMargin = minimumPoint.GetMargin(axis);
yield return new ValueMargin(selector(minimumPoint), minimumMargin, minimumMargin);
double maximumMargin = maximumPoint.GetMargin(axis);
yield return new ValueMargin(selector(maximumPoint), maximumMargin, maximumMargin);
}
else if (axis == InternalActualDependentAxis)
{
selector = (dataPoint) => (IComparable)dataPoint.ActualDependentValue;
DataPoint minimumPoint = ActiveDataPoints.MinOrNull(selector);
DataPoint maximumPoint = ActiveDataPoints.MaxOrNull(selector);
yield return new ValueMargin(selector(minimumPoint), dependentValueMargin, dependentValueMargin);
yield return new ValueMargin(selector(maximumPoint), dependentValueMargin, dependentValueMargin);
}
}
else
{
yield break;
}
}
/// <summary>
/// Gets a range in which to render a data point.
/// </summary>
/// <param name="category">The category to retrieve the range for.
/// </param>
/// <returns>The range in which to render a data point.</returns>
protected Range<UnitValue> GetCategoryRange(object category)
{
ICategoryAxis categoryAxis = ActualIndependentAxis as CategoryAxis;
if (categoryAxis != null)
{
return categoryAxis.GetPlotAreaCoordinateRange(category);
}
else
{
UnitValue unitValue = ActualIndependentAxis.GetPlotAreaCoordinate(category);
if (ValueHelper.CanGraph(unitValue.Value) && _dataPointlength.HasValue)
{
double halfLength = _dataPointlength.Value / 2.0;
return new Range<UnitValue>(
new UnitValue(unitValue.Value - halfLength, unitValue.Unit),
new UnitValue(unitValue.Value + halfLength, unitValue.Unit));
}
return new Range<UnitValue>();
}
}
/// <summary>
/// Gets the axis to which the data is anchored.
/// </summary>
IRangeAxis IAnchoredToOrigin.AnchoredAxis
{
get { return this.ActualDependentRangeAxis; }
}
}
}