// (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 { /// /// This series serves as the base class for the column and bar series. /// /// The type of the data point. public abstract class ColumnBarBaseSeries : DataPointSingleSeriesWithAxes, IAnchoredToOrigin where T : DataPoint, new() { #region public IRangeAxis DependentRangeAxis /// /// Gets or sets the dependent range axis. /// public IRangeAxis DependentRangeAxis { get { return GetValue(DependentRangeAxisProperty) as IRangeAxis; } set { SetValue(DependentRangeAxisProperty, value); } } /// /// Identifies the DependentRangeAxis dependency property. /// [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), new PropertyMetadata(null, OnDependentRangeAxisPropertyChanged)); /// /// DependentRangeAxisProperty property changed handler. /// /// ColumnBarBaseSeries that changed its DependentRangeAxis. /// Event arguments. private static void OnDependentRangeAxisPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ColumnBarBaseSeries source = (ColumnBarBaseSeries)d; IRangeAxis newValue = (IRangeAxis)e.NewValue; source.OnDependentRangeAxisPropertyChanged(newValue); } /// /// DependentRangeAxisProperty property changed handler. /// /// New value. private void OnDependentRangeAxisPropertyChanged(IRangeAxis newValue) { InternalDependentAxis = (IAxis)newValue; } #endregion public IRangeAxis DependentRangeAxis #region public IAxis IndependentAxis /// /// Gets or sets the independent category axis. /// public IAxis IndependentAxis { get { return GetValue(IndependentAxisProperty) as IAxis; } set { SetValue(IndependentAxisProperty, value); } } /// /// Identifies the IndependentAxis dependency property. /// [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), new PropertyMetadata(null, OnIndependentAxisPropertyChanged)); /// /// IndependentAxisProperty property changed handler. /// /// ColumnBarBaseSeries that changed its IndependentAxis. /// Event arguments. private static void OnIndependentAxisPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ColumnBarBaseSeries source = (ColumnBarBaseSeries)d; IAxis newValue = (IAxis)e.NewValue; source.OnIndependentAxisPropertyChanged(newValue); } /// /// IndependentAxisProperty property changed handler. /// /// New value. private void OnIndependentAxisPropertyChanged(IAxis newValue) { InternalIndependentAxis = (IAxis)newValue; } #endregion public IAxis IndependentAxis /// /// Keeps a list of DataPoints that share the same category. /// private IDictionary> _categoriesWithMultipleDataPoints; /// /// Returns the group of data points in a given category. /// /// The category for which to return the data /// point group. /// The group of data points in a given category. protected IGrouping GetDataPointGroup(object category) { return _categoriesWithMultipleDataPoints[category]; } /// /// Returns a value indicating whether a data point corresponding to /// a category is grouped. /// /// The category. /// A value indicating whether a data point corresponding to /// a category is grouped. protected bool GetIsDataPointGrouped(object category) { return _categoriesWithMultipleDataPoints.ContainsKey(category); } /// /// The length of each data point. /// private double? _dataPointlength; /// /// Gets the dependent axis as a range axis. /// public IRangeAxis ActualDependentRangeAxis { get { return this.InternalActualDependentAxis as IRangeAxis; } } /// /// Gets the independent axis as a category axis. /// public IAxis ActualIndependentAxis { get { return this.InternalActualIndependentAxis; } } /// /// Initializes a new instance of the ColumnBarBaseSeries class. /// protected ColumnBarBaseSeries() { } /// /// Method run before DataPoints are updated. /// 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); } /// /// Returns the custom ResourceDictionary to use for necessary resources. /// /// /// ResourceDictionary to use for necessary resources. /// protected override IEnumerator GetResourceDictionaryEnumeratorFromHost() { return GetResourceDictionaryWithTargetType(SeriesHost, typeof(T), true); } /// /// Updates a data point when its actual dependent value has changed. /// /// The data point. /// The old value. /// The new value. protected override void OnDataPointActualDependentValueChanged(DataPoint dataPoint, IComparable oldValue, IComparable newValue) { UpdateDataPoint(dataPoint); base.OnDataPointActualDependentValueChanged(dataPoint, oldValue, newValue); } /// /// Redraws other column series to assure they allocate the right amount /// of space for their columns. /// /// The series host to update. protected void RedrawOtherSeries(ISeriesHost seriesHost) { Type thisType = typeof(ColumnBarBaseSeries); // redraw all other column series to ensure they make space for new one foreach (ColumnBarBaseSeries series in seriesHost.Series.Where(series => thisType.IsAssignableFrom(series.GetType())).OfType>().Where(series => series != this)) { series.UpdateDataPoints(series.ActiveDataPoints); } } /// /// 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) { base.OnDataPointsChanged(newDataPoints, oldDataPoints); CalculateDataPointLength(); if (this.SeriesHost != null) { RedrawOtherSeries(this.SeriesHost); } } /// /// Redraw other column series when removed from a series host. /// /// The old value of the series host property. /// The new value of the series host property. 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); } } /// /// Creates the bar data point. /// /// A bar data point. protected override DataPoint CreateDataPoint() { return new T(); } /// /// Calculates the length of the data points. /// protected void CalculateDataPointLength() { if (!(ActualIndependentAxis is ICategoryAxis)) { IEnumerable 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(left.Value, right.Value)) .Select(range => range.Maximum - range.Minimum) .MinOrNullable(); } } /// /// Returns the value margins for a given axis. /// /// The axis to retrieve the value margins for. /// /// A sequence of value margins. protected override IEnumerable GetValueMargins(IValueMarginConsumer consumer) { double dependentValueMargin = this.ActualHeight / 10; IAxis axis = consumer as IAxis; if (axis != null && ActiveDataPoints.Any()) { Func 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; } } /// /// Gets a range in which to render a data point. /// /// The category to retrieve the range for. /// /// The range in which to render a data point. protected Range 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( new UnitValue(unitValue.Value - halfLength, unitValue.Unit), new UnitValue(unitValue.Value + halfLength, unitValue.Unit)); } return new Range(); } } /// /// Gets the axis to which the data is anchored. /// IRangeAxis IAnchoredToOrigin.AnchoredAxis { get { return this.ActualDependentRangeAxis; } } } }