// (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.Linq; using System.Windows.Data; namespace System.Windows.Controls.DataVisualization.Charting { /// /// Represents a control that contains a data series to be rendered in X/Y /// line format. A third binding determines the size of the data point. /// /// Preview [TemplatePart(Name = DataPointSeries.PlotAreaName, Type = typeof(Canvas))] [StyleTypedProperty(Property = DataPointStyleName, StyleTargetType = typeof(BubbleDataPoint))] [StyleTypedProperty(Property = "LegendItemStyle", StyleTargetType = typeof(LegendItem))] public class BubbleSeries : DataPointSingleSeriesWithAxes { /// /// The maximum bubble size as a ratio of the smallest dimension. /// private const double MaximumBubbleSizeAsRatioOfSmallestDimension = 0.25; /// /// The binding used to identify the size value. /// private Binding _sizeValueBinding; /// /// Gets or sets the Binding to use for identifying the size of the bubble. /// public Binding SizeValueBinding { get { return _sizeValueBinding; } set { if (_sizeValueBinding != value) { _sizeValueBinding = value; Refresh(); } } } /// /// Gets or sets the Binding Path to use for identifying the size of the bubble. /// public string SizeValuePath { get { return (null != SizeValueBinding) ? SizeValueBinding.Path.Path : null; } set { if (null == value) { SizeValueBinding = null; } else { SizeValueBinding = new Binding(value); } } } /// /// Stores the range of ActualSize values for the BubbleDataPoints. /// private Range _rangeOfActualSizeValues = new Range(); /// /// Initializes a new instance of the bubble series. /// public BubbleSeries() { } /// /// Creates a new instance of bubble data point. /// /// A new instance of bubble data point. protected override DataPoint CreateDataPoint() { return new BubbleDataPoint(); } /// /// Returns the custom ResourceDictionary to use for necessary resources. /// /// /// ResourceDictionary to use for necessary resources. /// protected override IEnumerator GetResourceDictionaryEnumeratorFromHost() { return GetResourceDictionaryWithTargetType(SeriesHost, typeof(BubbleDataPoint), true); } /// /// Acquire a horizontal linear axis and a vertical linear axis. /// /// The first data point. protected override void GetAxes(DataPoint firstDataPoint) { GetAxes( firstDataPoint, (axis) => axis.Orientation == AxisOrientation.X, () => { IAxis axis = CreateRangeAxisFromData(firstDataPoint.IndependentValue); if (axis == null) { axis = new CategoryAxis(); } axis.Orientation = AxisOrientation.X; return axis; }, (axis) => axis.Orientation == AxisOrientation.Y && axis is IRangeAxis, () => { DisplayAxis axis = (DisplayAxis)CreateRangeAxisFromData(firstDataPoint.DependentValue); if (axis == null) { throw new InvalidOperationException(Properties.Resources.DataPointSeriesWithAxes_NoSuitableAxisAvailableForPlottingDependentValue); } axis.ShowGridLines = true; axis.Orientation = AxisOrientation.Y; return axis; }); } /// /// Prepares a bubble data point by binding the size value binding to /// the size property. /// /// The data point to prepare. /// The data context of the data point. /// protected override void PrepareDataPoint(DataPoint dataPoint, object dataContext) { base.PrepareDataPoint(dataPoint, dataContext); BubbleDataPoint bubbleDataPoint = (BubbleDataPoint)dataPoint; bubbleDataPoint.SetBinding(BubbleDataPoint.SizeProperty, SizeValueBinding ?? DependentValueBinding ?? IndependentValueBinding); } /// /// Attaches size change and actual size change event handlers to the /// data point. /// /// The data point. protected override void AttachEventHandlersToDataPoint(DataPoint dataPoint) { BubbleDataPoint bubbleDataPoint = (BubbleDataPoint)dataPoint; bubbleDataPoint.SizePropertyChanged += BubbleDataPointSizePropertyChanged; bubbleDataPoint.ActualSizePropertyChanged += BubbleDataPointActualSizePropertyChanged; base.AttachEventHandlersToDataPoint(dataPoint); } /// /// Detaches size change and actual size change event handlers from the /// data point. /// /// The data point. protected override void DetachEventHandlersFromDataPoint(DataPoint dataPoint) { BubbleDataPoint bubbleDataPoint = (BubbleDataPoint)dataPoint; bubbleDataPoint.SizePropertyChanged -= BubbleDataPointSizePropertyChanged; bubbleDataPoint.ActualSizePropertyChanged -= BubbleDataPointActualSizePropertyChanged; base.DetachEventHandlersFromDataPoint(dataPoint); } /// /// Updates all data points when the actual size property of a data /// point changes. /// /// The source of the event. /// Information about the event. private void BubbleDataPointActualSizePropertyChanged(object sender, RoutedPropertyChangedEventArgs e) { Range newRangeOfActualSizeValues = ActiveDataPoints.OfType().Select(d => Math.Abs(d.ActualSize)).GetRange(); if (newRangeOfActualSizeValues == _rangeOfActualSizeValues) { // No range change - only need to update the current point UpdateDataPoint((BubbleDataPoint)sender); } else { // Range has changed - need to update all points UpdateDataPoints(ActiveDataPoints); } } /// /// Animates the value of the ActualSize property to the size property /// when it changes. /// /// The source of the event. /// Information about the event. private void BubbleDataPointSizePropertyChanged(object sender, RoutedPropertyChangedEventArgs e) { BubbleDataPoint dataPoint = (BubbleDataPoint)sender; DependencyPropertyAnimationHelper.BeginAnimation( dataPoint, BubbleDataPoint.ActualSizeProperty, "ActualSize", e.NewValue, TransitionDuration, this.TransitionEasingFunction); } /// /// Calculates the range of ActualSize values of all active BubbleDataPoints. /// protected override void OnBeforeUpdateDataPoints() { _rangeOfActualSizeValues = ActiveDataPoints.OfType().Select(d => Math.Abs(d.ActualSize)).GetRange(); } /// /// Ensure that if any data points are updated, all data points are /// updated. /// /// The data points to update. protected override void UpdateDataPoints(IEnumerable dataPoints) { base.UpdateDataPoints(ActiveDataPoints); } /// /// Updates the data point's visual representation. /// /// The data point. protected override void UpdateDataPoint(DataPoint dataPoint) { double maximumDiameter = Math.Min(PlotAreaSize.Width, PlotAreaSize.Height) * MaximumBubbleSizeAsRatioOfSmallestDimension; BubbleDataPoint bubbleDataPoint = (BubbleDataPoint)dataPoint; double ratioOfLargestBubble = (_rangeOfActualSizeValues.HasData && _rangeOfActualSizeValues.Maximum != 0.0 && bubbleDataPoint.ActualSize >= 0.0) ? Math.Abs(bubbleDataPoint.ActualSize) / _rangeOfActualSizeValues.Maximum : 0.0; bubbleDataPoint.Width = ratioOfLargestBubble * maximumDiameter; bubbleDataPoint.Height = ratioOfLargestBubble * maximumDiameter; double left = (ActualIndependentAxis.GetPlotAreaCoordinate(bubbleDataPoint.ActualIndependentValue)).Value - (bubbleDataPoint.Width / 2.0); double top = (PlotAreaSize.Height - (bubbleDataPoint.Height / 2.0)) - ActualDependentRangeAxis.GetPlotAreaCoordinate(bubbleDataPoint.ActualDependentValue).Value; if (ValueHelper.CanGraph(left) && ValueHelper.CanGraph(top)) { dataPoint.Visibility = Visibility.Visible; Canvas.SetLeft(bubbleDataPoint, left); Canvas.SetTop(bubbleDataPoint, top); } else { dataPoint.Visibility = Visibility.Collapsed; } } /// /// Updates the value margins after all data points are updated. /// protected override void OnAfterUpdateDataPoints() { IValueMarginProvider provider = this as IValueMarginProvider; { IValueMarginConsumer consumer = ActualDependentRangeAxis as IValueMarginConsumer; if (consumer != null) { consumer.ValueMarginsChanged(provider, GetValueMargins(consumer)); } } { IValueMarginConsumer consumer = ActualIndependentAxis as IValueMarginConsumer; if (consumer != null) { consumer.ValueMarginsChanged(provider, GetValueMargins(consumer)); } } base.OnAfterUpdateDataPoints(); } /// /// Gets the dependent axis as a range axis. /// public IRangeAxis ActualDependentRangeAxis { get { return this.InternalActualDependentAxis as IRangeAxis; } } #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. /// public static readonly DependencyProperty DependentRangeAxisProperty = DependencyProperty.Register( "DependentRangeAxis", typeof(IRangeAxis), typeof(BubbleSeries), new PropertyMetadata(null, OnDependentRangeAxisPropertyChanged)); /// /// DependentRangeAxisProperty property changed handler. /// /// BubbleSeries that changed its DependentRangeAxis. /// Event arguments. private static void OnDependentRangeAxisPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { BubbleSeries source = (BubbleSeries)d; IRangeAxis newValue = (IRangeAxis)e.NewValue; source.OnDependentRangeAxisPropertyChanged(newValue); } /// /// DependentRangeAxisProperty property changed handler. /// /// New value. private void OnDependentRangeAxisPropertyChanged(IRangeAxis newValue) { this.InternalDependentAxis = (IAxis)newValue; } #endregion public IRangeAxis DependentRangeAxis /// /// Gets the independent axis as a range axis. /// public IAxis ActualIndependentAxis { get { return this.InternalActualIndependentAxis as IAxis; } } #region public IAxis IndependentAxis /// /// Gets or sets independent range axis. /// public IAxis IndependentAxis { get { return GetValue(IndependentAxisProperty) as IAxis; } set { SetValue(IndependentAxisProperty, value); } } /// /// Identifies the IndependentAxis dependency property. /// public static readonly DependencyProperty IndependentAxisProperty = DependencyProperty.Register( "IndependentAxis", typeof(IAxis), typeof(BubbleSeries), new PropertyMetadata(null, OnIndependentAxisPropertyChanged)); /// /// IndependentAxisProperty property changed handler. /// /// BubbleSeries that changed its IndependentAxis. /// Event arguments. private static void OnIndependentAxisPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { BubbleSeries source = (BubbleSeries)d; IAxis newValue = (IAxis)e.NewValue; source.OnIndependentAxisPropertyChanged(newValue); } /// /// IndependentAxisProperty property changed handler. /// /// New value. private void OnIndependentAxisPropertyChanged(IAxis newValue) { this.InternalIndependentAxis = (IAxis)newValue; } #endregion public IAxis IndependentAxis /// /// The margins required for each value. /// /// The consumer to return the value margins for. /// A sequence of margins for each value. protected override IEnumerable GetValueMargins(IValueMarginConsumer consumer) { IAxis axis = consumer as IAxis; if (axis != null) { return ActiveDataPoints.Select(dataPoint => { double margin = dataPoint.GetMargin(axis); return new ValueMargin( GetActualDataPointAxisValue(dataPoint, axis), margin, margin); }); } return Enumerable.Empty(); } } }