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
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; }
|
|
}
|
|
}
|
|
}
|