// (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; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using System.Windows.Controls.DataVisualization.Collections; using System.Windows.Data; using System.Windows.Media; using System.Windows.Media.Animation; #if !SILVERLIGHT using System.Diagnostics.CodeAnalysis; #endif namespace System.Windows.Controls.DataVisualization.Charting { /// /// Represents a control that contains a dynamic data series. /// /// Preview public abstract partial class DataPointSeries : Series { /// /// The name of the template part with the plot area. /// protected const string PlotAreaName = "PlotArea"; /// /// The name of the DataPointStyle property and ResourceDictionary entry. /// protected const string DataPointStyleName = "DataPointStyle"; /// /// The name of the LegendItemStyle property and ResourceDictionary entry. /// protected const string LegendItemStyleName = "LegendItemStyle"; /// /// The name of the ActualLegendItemStyle property. /// protected internal const string ActualLegendItemStyleName = "ActualLegendItemStyle"; #if !SILVERLIGHT /// /// Event that is raised when selection is changed. /// public static readonly RoutedEvent SelectionChangedEvent = EventManager.RegisterRoutedEvent( "SelectionChanged", RoutingStrategy.Bubble, typeof(SelectionChangedEventHandler), typeof(DataPointSeries)); #endif /// /// Queue of hide/reveal storyboards to play. /// private StoryboardQueue _storyBoardQueue = new StoryboardQueue(); /// /// The binding used to identify the dependent value binding. /// private Binding _dependentValueBinding; /// /// Gets or sets the Binding to use for identifying the dependent value. /// public Binding DependentValueBinding { get { return _dependentValueBinding; } set { if (value != _dependentValueBinding) { _dependentValueBinding = value; Refresh(); } } } /// /// Data points collection sorted by object. /// private MultipleDictionary _dataPointsByObject = new MultipleDictionary( true, new GenericEqualityComparer( (left, right) => left.Equals(right), (obj) => obj.GetHashCode()), new GenericEqualityComparer( (left, right) => object.ReferenceEquals(left, right), (obj) => obj.GetHashCode())); /// /// Gets or sets the Binding Path to use for identifying the dependent value. /// public string DependentValuePath { get { return (null != DependentValueBinding) ? DependentValueBinding.Path.Path : null; } set { if (null == value) { DependentValueBinding = null; } else { DependentValueBinding = new Binding(value); } } } /// /// The binding used to identify the independent value binding. /// private Binding _independentValueBinding; /// /// Gets or sets the Binding to use for identifying the independent value. /// public Binding IndependentValueBinding { get { return _independentValueBinding; } set { if (_independentValueBinding != value) { _independentValueBinding = value; Refresh(); } } } /// /// Gets or sets the Binding Path to use for identifying the independent value. /// public string IndependentValuePath { get { return (null != IndependentValueBinding) ? IndependentValueBinding.Path.Path : null; } set { if (null == value) { IndependentValueBinding = null; } else { IndependentValueBinding = new Binding(value); } } } #region public IEnumerable ItemsSource /// /// Gets or sets a collection used to contain the data points of the Series. /// public IEnumerable ItemsSource { get { return (IEnumerable)GetValue(ItemsSourceProperty); } set { SetValue(ItemsSourceProperty, value); } } /// /// Identifies the ItemsSource dependency property. /// public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register( "ItemsSource", typeof(IEnumerable), typeof(DataPointSeries), new PropertyMetadata(OnItemsSourceChanged)); /// /// ItemsSourceProperty property changed callback. /// /// Series for which the ItemsSource changed. /// Event arguments. private static void OnItemsSourceChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) { ((DataPointSeries)o).OnItemsSourceChanged((IEnumerable)e.OldValue, (IEnumerable)e.NewValue); } /// /// Called when the ItemsSource property changes. /// /// Old value of the ItemsSource property. /// New value of the ItemsSource property. protected virtual void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue) { // Remove handler for oldValue.CollectionChanged (if present) INotifyCollectionChanged oldValueINotifyCollectionChanged = oldValue as INotifyCollectionChanged; if (null != oldValueINotifyCollectionChanged) { // Detach the WeakEventListener if (null != _weakEventListener) { _weakEventListener.Detach(); _weakEventListener = null; } } // Add handler for newValue.CollectionChanged (if possible) INotifyCollectionChanged newValueINotifyCollectionChanged = newValue as INotifyCollectionChanged; if (null != newValueINotifyCollectionChanged) { // Use a WeakEventListener so that the backwards reference doesn't keep this object alive _weakEventListener = new WeakEventListener(this); _weakEventListener.OnEventAction = (instance, source, eventArgs) => instance.ItemsSourceCollectionChanged(source, eventArgs); _weakEventListener.OnDetachAction = (weakEventListener) => newValueINotifyCollectionChanged.CollectionChanged -= weakEventListener.OnEvent; newValueINotifyCollectionChanged.CollectionChanged += _weakEventListener.OnEvent; } if (TemplateApplied) { Refresh(); } } #endregion public IEnumerable ItemsSource #region public AnimationSequence AnimationSequence /// /// Gets or sets the animation sequence to use for the DataPoints of the Series. /// public AnimationSequence AnimationSequence { get { return (AnimationSequence)GetValue(AnimationSequenceProperty); } set { SetValue(AnimationSequenceProperty, value); } } /// /// Gets a stream of the active data points in the plot area. /// protected virtual IEnumerable ActiveDataPoints { get { return (null != PlotArea) ? PlotArea.Children.OfType().Where(dataPoint => dataPoint.IsActive) : Enumerable.Empty(); } } /// /// Gets the number of active data points in the plot area. /// protected int ActiveDataPointCount { get; private set; } #region public bool IsSelectionEnabled #region public IEasingFunction TransitionEasingFunction /// /// Gets or sets the easing function to use when transitioning the /// data points. /// #if !NO_EASING_FUNCTIONS public IEasingFunction TransitionEasingFunction { get { return GetValue(TransitionEasingFunctionProperty) as IEasingFunction; } set { SetValue(TransitionEasingFunctionProperty, value); } } /// /// Identifies the TransitionEasingFunction dependency property. /// public static readonly DependencyProperty TransitionEasingFunctionProperty = DependencyProperty.Register( "TransitionEasingFunction", typeof(IEasingFunction), typeof(DataPointSeries), new PropertyMetadata(new QuadraticEase { EasingMode = EasingMode.EaseInOut })); #else internal IEasingFunction TransitionEasingFunction { get; set; } #endif #endregion public IEasingFunction TransitionEasingFunction /// /// Gets or sets a value indicating whether elements in the series can /// be selected. /// public bool IsSelectionEnabled { get { return (bool)GetValue(IsSelectionEnabledProperty); } set { SetValue(IsSelectionEnabledProperty, value); } } /// /// Identifies the IsSelectionEnabled dependency property. /// public static readonly DependencyProperty IsSelectionEnabledProperty = DependencyProperty.Register( "IsSelectionEnabled", typeof(bool), typeof(DataPointSeries), new PropertyMetadata(false, OnIsSelectionEnabledPropertyChanged)); /// /// IsSelectionEnabledProperty property changed handler. /// /// DynamicSeries that changed its IsSelectionEnabled. /// /// Event arguments. private static void OnIsSelectionEnabledPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { DataPointSeries source = (DataPointSeries)d; bool oldValue = (bool)e.OldValue; bool newValue = (bool)e.NewValue; source.OnIsSelectionEnabledPropertyChanged(oldValue, newValue); } /// /// IsSelectionEnabledProperty property changed handler. /// /// Old value. /// New value. protected virtual void OnIsSelectionEnabledPropertyChanged(bool oldValue, bool newValue) { foreach (DataPoint dataPoint in ActiveDataPoints) { dataPoint.IsSelectionEnabled = newValue; } } #endregion public bool IsSelectionEnabled /// /// Identifies the AnimationSequence dependency property. /// public static readonly DependencyProperty AnimationSequenceProperty = DependencyProperty.Register( "AnimationSequence", typeof(AnimationSequence), typeof(DataPointSeries), new PropertyMetadata(AnimationSequence.Simultaneous)); #endregion public AnimationSequence AnimationSequence /// /// WeakEventListener used to handle INotifyCollectionChanged events. /// private WeakEventListener _weakEventListener; /// /// The plot area canvas. /// private Panel _plotArea; /// /// Gets the plot area canvas. /// internal Panel PlotArea { get { return _plotArea; } private set { Panel oldValue = _plotArea; _plotArea = value; if (_plotArea != oldValue) { OnPlotAreaChanged(oldValue, value); } } } /// /// Gets the size of the plot area. /// /// /// Use this method instead of PlotArea.ActualWidth/ActualHeight /// because the ActualWidth and ActualHeight properties are set after /// the SizeChanged handler runs. /// protected Size PlotAreaSize { get; private set; } /// /// Event raised when selection has changed. /// #if SILVERLIGHT public event SelectionChangedEventHandler SelectionChanged; #else public event SelectionChangedEventHandler SelectionChanged { add { AddHandler(SelectionChangedEvent, value); } remove { RemoveHandler(SelectionChangedEvent, value); } } #endif /// /// Tracks whether a call to OnSelectedItemPropertyChanged is already in progress. /// private bool _processingOnSelectedItemPropertyChanged; #region public object SelectedItem /// /// Gets or sets the selected item. /// public object SelectedItem { get { return GetValue(SelectedItemProperty) as object; } set { SetValue(SelectedItemProperty, value); } } /// /// Identifies the SelectedItem dependency property. /// public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register( "SelectedItem", typeof(object), typeof(DataPointSeries), new PropertyMetadata(null, OnSelectedItemPropertyChanged)); /// /// Called when the value of the SelectedItem property changes. /// /// DynamicSeries that changed its SelectedItem. /// Event arguments. private static void OnSelectedItemPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { DataPointSeries source = (DataPointSeries)d; object oldValue = (object)e.OldValue; object newValue = (object)e.NewValue; source.OnSelectedItemPropertyChanged(oldValue, newValue); } /// /// Called when the value of the SelectedItem property changes. /// /// The old selected index. /// The new value. protected virtual void OnSelectedItemPropertyChanged(object oldValue, object newValue) { DataPoint dataPoint = null; if (null != newValue) { // Find the corresponding Control dataPoint = _dataPointsByObject[newValue].Where(dp => object.Equals(newValue, dp.DataContext) && dp.IsActive).FirstOrDefault(); if (null == dataPoint) { // None; clear SelectedItem try { _processingOnSelectedItemPropertyChanged = true; SelectedItem = null; // Clear newValue so the SelectionChanged event will be correct (or suppressed) newValue = null; } finally { _processingOnSelectedItemPropertyChanged = false; } } } // Unselect everything else foreach (DataPoint dataPointUnselect in ActiveDataPoints.Where(activeDataPoint => (activeDataPoint != dataPoint) && activeDataPoint.IsSelected)) { dataPointUnselect.IsSelectedChanged -= OnDataPointIsSelectedChanged; dataPointUnselect.IsSelected = false; dataPointUnselect.IsSelectedChanged += OnDataPointIsSelectedChanged; } if ((null != dataPoint) && !dataPoint.IsSelected) { // Select the new data point dataPoint.IsSelectedChanged -= OnDataPointIsSelectedChanged; dataPoint.IsSelected = true; dataPoint.IsSelectedChanged += OnDataPointIsSelectedChanged; } // Fire SelectionChanged (if appropriate) if (!_processingOnSelectedItemPropertyChanged && (oldValue != newValue)) { IList oldValues = new List(); if (oldValue != null) { oldValues.Add(oldValue); } IList newValues = new List(); if (newValue != null) { newValues.Add(newValue); } #if SILVERLIGHT SelectionChangedEventHandler handler = SelectionChanged; if (null != handler) { handler(this, new SelectionChangedEventArgs(oldValues, newValues)); } #else RaiseEvent(new SelectionChangedEventArgs(SelectionChangedEvent, oldValues, newValues)); #endif } } #endregion public object SelectedItem /// /// Gets or sets a value indicating whether the template has been /// applied. /// private bool TemplateApplied { get; set; } #region public Style DataPointStyle /// /// Gets or sets the style to use for the data points. /// public Style DataPointStyle { get { return GetValue(DataPointStyleProperty) as Style; } set { SetValue(DataPointStyleProperty, value); } } /// /// Identifies the DataPointStyle dependency property. /// public static readonly DependencyProperty DataPointStyleProperty = DependencyProperty.Register( DataPointStyleName, typeof(Style), typeof(DataPointSeries), new PropertyMetadata(null, OnDataPointStylePropertyChanged)); /// /// DataPointStyleProperty property changed handler. /// /// DataPointSingleSeriesWithAxes that changed its DataPointStyle. /// Event arguments. private static void OnDataPointStylePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((DataPointSeries)d).OnDataPointStylePropertyChanged((Style)e.OldValue, (Style)e.NewValue); } /// /// DataPointStyleProperty property changed handler. /// /// Old value. /// New value. protected virtual void OnDataPointStylePropertyChanged(Style oldValue, Style newValue) { foreach (LegendItem legendItem in LegendItems.OfType()) { // Silverlight requires the following to pick up the new Style for the LegendItem marker object dataContext = legendItem.DataContext; legendItem.DataContext = null; legendItem.DataContext = dataContext; } } #endregion public Style DataPointStyle #region public Style LegendItemStyle /// /// Gets or sets the style to use for the legend items. /// public Style LegendItemStyle { get { return GetValue(LegendItemStyleProperty) as Style; } set { SetValue(LegendItemStyleProperty, value); } } /// /// Identifies the LegendItemStyle dependency property. /// public static readonly DependencyProperty LegendItemStyleProperty = DependencyProperty.Register( LegendItemStyleName, typeof(Style), typeof(DataPointSeries), new PropertyMetadata(null, OnLegendItemStylePropertyChanged)); /// /// LegendItemStyleProperty property changed handler. /// /// DataPointSeries that changed its LegendItemStyle. /// Event arguments. private static void OnLegendItemStylePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { DataPointSeries source = (DataPointSeries)d; source.OnLegendItemStylePropertyChanged((Style)e.OldValue, (Style)e.NewValue); } /// /// Called when the value of the LegendItemStyle property changes. /// /// Old value. /// New value. protected virtual void OnLegendItemStylePropertyChanged(Style oldValue, Style newValue) { } #endregion public Style LegendItemStyle /// /// Gets or sets the Geometry used to clip DataPoints to the PlotArea bounds. /// private RectangleGeometry ClipGeometry { get; set; } /// /// Indicates whether a call to Refresh is required when the control's /// size changes. /// private bool _needRefreshWhenSizeChanged = true; #region public TimeSpan TransitionDuration /// /// Gets or sets the duration of the value Transition animation. /// public TimeSpan TransitionDuration { get { return (TimeSpan)GetValue(TransitionDurationProperty); } set { SetValue(TransitionDurationProperty, value); } } /// /// Identifies the TransitionDuration dependency property. /// public static readonly DependencyProperty TransitionDurationProperty = DependencyProperty.Register( "TransitionDuration", typeof(TimeSpan), typeof(DataPointSeries), new PropertyMetadata(TimeSpan.FromSeconds(0.5))); #endregion public TimeSpan TransitionDuration #if !SILVERLIGHT /// /// Initializes the static members of the DataPointSeries class. /// [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Dependency properties are initialized in-line.")] static DataPointSeries() { DefaultStyleKeyProperty.OverrideMetadata(typeof(DataPointSeries), new FrameworkPropertyMetadata(typeof(DataPointSeries))); } #endif /// /// Initializes a new instance of the DataPointSeries class. /// protected DataPointSeries() { #if SILVERLIGHT this.DefaultStyleKey = typeof(DataPointSeries); #endif ClipGeometry = new RectangleGeometry(); Clip = ClipGeometry; } /// /// Adds an object to the series host by creating a corresponding data point /// for it. /// /// The object to add to the series host. /// The data point created for the object. protected virtual DataPoint AddObject(object dataContext) { if (ShouldCreateDataPoint(dataContext)) { DataPoint dataPoint = CreateAndPrepareDataPoint(dataContext); _dataPointsByObject.Add(dataContext, dataPoint); AddDataPoint(dataPoint); return dataPoint; } return null; } /// /// Returns whether a data point should be created for the data context. /// /// The data context that will be used for the /// data point. /// A value indicating whether a data point should be created /// for the data context. protected virtual bool ShouldCreateDataPoint(object dataContext) { return true; } /// /// Returns the index at which to insert data point in the plot area /// child collection. /// /// The data point to retrieve the insertion /// index for. /// The insertion index. protected virtual int GetInsertionIndex(DataPoint dataPoint) { return PlotArea.Children.Count; } /// /// Adds a data point to the plot area. /// /// The data point to add to the plot area. /// protected virtual void AddDataPoint(DataPoint dataPoint) { if (dataPoint.IsSelected) { Select(dataPoint); } if (PlotArea != null) { // Positioning data point outside the visible area. Canvas.SetLeft(dataPoint, float.MinValue); Canvas.SetTop(dataPoint, float.MinValue); dataPoint.IsSelectionEnabled = IsSelectionEnabled; AttachEventHandlersToDataPoint(dataPoint); PlotArea.Children.Insert(GetInsertionIndex(dataPoint), dataPoint); ActiveDataPointCount++; } } /// /// Retrieves the data point corresponding to the object passed as the /// parameter. /// /// The data context used for the point. /// /// The data point associated with the object. protected virtual DataPoint GetDataPoint(object dataContext) { DataPoint dataPoint = _dataPointsByObject[dataContext].Where(dp => object.Equals(dataContext, dp.DataContext)).FirstOrDefault(); return dataPoint; } /// /// Creates and prepares a data point. /// /// The object to use as the data context /// of the data point. /// The newly created data point. private DataPoint CreateAndPrepareDataPoint(object dataContext) { DataPoint dataPoint = CreateDataPoint(); PrepareDataPoint(dataPoint, dataContext); return dataPoint; } /// /// Returns a Control suitable for the Series. /// /// The DataPoint instance. protected abstract DataPoint CreateDataPoint(); /// /// Creates a legend item. /// /// A legend item for insertion in the legend items collection. /// /// The owner of the new LegendItem. protected virtual LegendItem CreateLegendItem(DataPointSeries owner) { LegendItem legendItem = new LegendItem() { Owner = owner }; legendItem.SetBinding(LegendItem.StyleProperty, new Binding(ActualLegendItemStyleName) { Source = this }); legendItem.SetBinding(LegendItem.ContentProperty, new Binding(TitleName) { Source = this }); return legendItem; } /// /// Method that handles the ObservableCollection.CollectionChanged event for the ItemsSource property. /// /// The object that raised the event. /// The event data. private void ItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { // Pass notification on OnItemsSourceCollectionChanged(ItemsSource, e); } /// /// Updates data points collection with items retrieved from items /// source and removes the old items. /// /// The items to load. /// The items to remove. protected void LoadDataPoints(IEnumerable newItems, IEnumerable oldItems) { if ((PlotArea != null) && (SeriesHost != null)) { IList removedDataPoints = new List(); if (oldItems != null) { if (oldItems != null) { // Remove existing objects from internal collections. foreach (object dataContext in oldItems) { DataPoint removedDataPoint = RemoveObject(dataContext); _dataPointsByObject.Remove(dataContext, removedDataPoint); if (removedDataPoint != null) { removedDataPoints.Add(removedDataPoint); } } } StaggeredStateChange(removedDataPoints, removedDataPoints.Count, DataPointState.Hiding); } IList addedDataPoints = new List(); if (newItems != null) { foreach (object dataContext in newItems) { DataPoint dataPoint = AddObject(dataContext); if (dataPoint != null) { addedDataPoints.Add(dataPoint); } } } OnDataPointsChanged(addedDataPoints, removedDataPoints); } } /// /// Attaches handler plot area after loading it from XAML. /// public override void OnApplyTemplate() { base.OnApplyTemplate(); // Get reference to new ChartArea and hook its SizeChanged event PlotArea = GetTemplateChild(PlotAreaName) as Panel; if (!TemplateApplied) { TemplateApplied = true; SizeChanged += new SizeChangedEventHandler(OnSizeChanged); } } /// /// Invokes an action when the plot area's layout is updated. /// /// The action to execute. internal void InvokeOnLayoutUpdated(Action action) { EventHandler handler = null; handler = delegate { this.PlotArea.LayoutUpdated -= handler; action(); }; this.PlotArea.LayoutUpdated += handler; } /// /// Handles changes to the SeriesHost property. /// /// Old value. /// New value. protected override void OnSeriesHostPropertyChanged(ISeriesHost oldValue, ISeriesHost newValue) { base.OnSeriesHostPropertyChanged(oldValue, newValue); if (null == newValue) { // Reset flag to prepare for next addition to a series host _needRefreshWhenSizeChanged = true; } } /// /// Called after data points have been loaded from the items source. /// /// New active data points. /// Old inactive data points. protected virtual void OnDataPointsChanged(IList newDataPoints, IList oldDataPoints) { StaggeredStateChange(newDataPoints, newDataPoints.Count(), DataPointState.Showing); } /// /// Method called when the ItemsSource collection changes. /// /// New value of the collection. /// Information about the change. protected virtual void OnItemsSourceCollectionChanged(IEnumerable collection, NotifyCollectionChangedEventArgs e) { if (e.Action == NotifyCollectionChangedAction.Replace) { IList updatedDataPoints = new List(); for (int index = 0; index < e.OldItems.Count; index++) { DataPoint dataPointToUpdate = _dataPointsByObject[e.OldItems[index]].Where(dp => object.Equals(e.OldItems[index], dp.DataContext)).Except(updatedDataPoints).FirstOrDefault(); if (null != dataPointToUpdate) { updatedDataPoints.Add(dataPointToUpdate); dataPointToUpdate.DataContext = e.NewItems[index]; _dataPointsByObject.Remove(e.OldItems[index], dataPointToUpdate); _dataPointsByObject.Add(e.NewItems[index], dataPointToUpdate); } } } else if (e.Action == NotifyCollectionChangedAction.Add || e.Action == NotifyCollectionChangedAction.Remove) { LoadDataPoints( e.NewItems, e.OldItems); } else { Refresh(); } } /// /// Removes items from the existing plot area and adds items to new /// plot area. /// /// The previous plot area. /// The new plot area. protected virtual void OnPlotAreaChanged(Panel oldValue, Panel newValue) { if (oldValue != null) { foreach (DataPoint dataPoint in ActiveDataPoints) { oldValue.Children.Remove(dataPoint); } } if (newValue != null) { foreach (DataPoint dataPoint in ActiveDataPoints) { newValue.Children.Add(dataPoint); } } } /// /// Updates the visual appearance of all the data points when the size /// changes. /// /// The source of the event. /// Information about the event. private void OnSizeChanged(object sender, SizeChangedEventArgs e) { PlotAreaSize = e.NewSize; ClipGeometry.Rect = new Rect(0, 0, PlotAreaSize.Width, PlotAreaSize.Height); if (null != PlotArea) { PlotArea.Width = PlotAreaSize.Width; PlotArea.Height = PlotAreaSize.Height; if (_needRefreshWhenSizeChanged) { _needRefreshWhenSizeChanged = false; Refresh(); } else { UpdateDataPoints(ActiveDataPoints); } } } /// /// Refreshes data from data source and renders the series. /// public void Refresh() { try { IEnumerable itemsSource = ItemsSource; LoadDataPoints(itemsSource, ActiveDataPoints.Select(dataPoint => dataPoint.DataContext)); } catch { if (DesignerProperties.GetIsInDesignMode(this)) { // Suppress exception to improve the design-time experience } else { throw; } } } /// /// Removes an object from the series host by removing its corresponding /// data point. /// /// The object to remove from the series data /// source. /// The data point corresponding to the removed object. /// protected virtual DataPoint RemoveObject(object dataContext) { DataPoint dataPoint = GetDataPoint(dataContext); if (dataPoint != null) { RemoveDataPoint(dataPoint); } return dataPoint; } /// /// Removes a data point from the plot area. /// /// The data point to remove. protected virtual void RemoveDataPoint(DataPoint dataPoint) { if (dataPoint.IsSelected) { Unselect(dataPoint); } ActiveDataPointCount--; #if !SILVERLIGHT // Cancel any Storyboards that might be holding the State property's value dataPoint.BeginAnimation(DataPoint.StateProperty, null); #endif dataPoint.State = DataPointState.PendingRemoval; } /// /// Gets a value indicating whether all data points are being /// updated. /// protected bool UpdatingDataPoints { get; private set; } /// /// Updates the visual representation of all data points in the plot /// area. /// /// A sequence of data points to update. /// protected virtual void UpdateDataPoints(IEnumerable dataPoints) { UpdatingDataPoints = true; DetachEventHandlersFromDataPoints(dataPoints); try { OnBeforeUpdateDataPoints(); foreach (DataPoint dataPoint in dataPoints) { UpdateDataPoint(dataPoint); } OnAfterUpdateDataPoints(); } finally { AttachEventHandlersToDataPoints(dataPoints); UpdatingDataPoints = false; } } /// /// Attaches event handlers to the data points. /// /// A sequence of data points. private void AttachEventHandlersToDataPoints(IEnumerable dataPoints) { foreach (DataPoint dataPoint in dataPoints) { AttachEventHandlersToDataPoint(dataPoint); } } /// /// Detaches event handlers from the data points. /// /// A sequence of data points. private void DetachEventHandlersFromDataPoints(IEnumerable dataPoints) { foreach (DataPoint dataPoint in dataPoints) { DetachEventHandlersFromDataPoint(dataPoint); } } /// /// Attaches event handlers to a data point. /// /// The data point. protected virtual void AttachEventHandlersToDataPoint(DataPoint dataPoint) { dataPoint.IsSelectedChanged += OnDataPointIsSelectedChanged; dataPoint.ActualDependentValueChanged += OnDataPointActualDependentValueChanged; dataPoint.ActualIndependentValueChanged += OnDataPointActualIndependentValueChanged; dataPoint.DependentValueChanged += OnDataPointDependentValueChanged; dataPoint.IndependentValueChanged += OnDataPointIndependentValueChanged; dataPoint.StateChanged += OnDataPointStateChanged; } /// /// Unselects a data point. /// /// The data point to unselect. private void Unselect(DataPoint dataPoint) { if (dataPoint.DataContext.Equals(SelectedItem)) { SelectedItem = null; } } /// /// Selects a data point. /// /// The data point to select. private void Select(DataPoint dataPoint) { SelectedItem = dataPoint.DataContext; } /// /// Method executed when a data point is either selected or unselected. /// /// The source of the event. /// Information about the event. private void OnDataPointIsSelectedChanged(object sender, RoutedPropertyChangedEventArgs e) { DataPoint dataPoint = sender as DataPoint; if (e.NewValue) { Select(dataPoint); } else { Unselect(dataPoint); } } /// /// Detaches event handlers from a data point. /// /// The data point. protected virtual void DetachEventHandlersFromDataPoint(DataPoint dataPoint) { dataPoint.IsSelectedChanged -= OnDataPointIsSelectedChanged; dataPoint.ActualDependentValueChanged -= OnDataPointActualDependentValueChanged; dataPoint.ActualIndependentValueChanged -= OnDataPointActualIndependentValueChanged; dataPoint.DependentValueChanged -= OnDataPointDependentValueChanged; dataPoint.IndependentValueChanged -= OnDataPointIndependentValueChanged; dataPoint.StateChanged -= OnDataPointStateChanged; } /// /// This method that executes before data points are updated. /// protected virtual void OnBeforeUpdateDataPoints() { } /// /// This method that executes after data points are updated. /// protected virtual void OnAfterUpdateDataPoints() { } /// /// Updates the visual representation of a single data point in the plot /// area. /// /// The data point to update. protected abstract void UpdateDataPoint(DataPoint dataPoint); /// /// Prepares a data point by extracting binding it to a data context /// object. /// /// A data point. /// A data context object. protected virtual void PrepareDataPoint(DataPoint dataPoint, object dataContext) { // Create a Control with DataContext set to the data source dataPoint.DataContext = dataContext; // Set bindings for IndependentValue/DependentValue if (IndependentValueBinding != null) { dataPoint.SetBinding(DataPoint.IndependentValueProperty, IndependentValueBinding); } if (DependentValueBinding == null) { dataPoint.SetBinding(DataPoint.DependentValueProperty, new Binding()); } else { dataPoint.SetBinding(DataPoint.DependentValueProperty, DependentValueBinding); } } /// /// Reveals data points using a storyboard. /// /// The data points to change the state of. /// /// The number of data points in the sequence. /// The state to change to. private void StaggeredStateChange(IEnumerable dataPoints, int dataPointCount, DataPointState newState) { if (PlotArea == null || dataPointCount == 0) { return; } Storyboard stateChangeStoryBoard = new Storyboard(); dataPoints.ForEachWithIndex((dataPoint, count) => { // Create an Animation ObjectAnimationUsingKeyFrames objectAnimationUsingKeyFrames = new ObjectAnimationUsingKeyFrames(); Storyboard.SetTarget(objectAnimationUsingKeyFrames, dataPoint); Storyboard.SetTargetProperty(objectAnimationUsingKeyFrames, new PropertyPath("State")); // Create a key frame DiscreteObjectKeyFrame discreteObjectKeyFrame = new DiscreteObjectKeyFrame(); discreteObjectKeyFrame.Value = newState; // Create the specified animation type switch (AnimationSequence) { case AnimationSequence.Simultaneous: discreteObjectKeyFrame.KeyTime = TimeSpan.Zero; break; case AnimationSequence.FirstToLast: discreteObjectKeyFrame.KeyTime = TimeSpan.FromMilliseconds(1000 * ((double)count / dataPointCount)); break; case AnimationSequence.LastToFirst: discreteObjectKeyFrame.KeyTime = TimeSpan.FromMilliseconds(1000 * ((double)(dataPointCount - count - 1) / dataPointCount)); break; } // Add the Animation to the Storyboard objectAnimationUsingKeyFrames.KeyFrames.Add(discreteObjectKeyFrame); stateChangeStoryBoard.Children.Add(objectAnimationUsingKeyFrames); }); stateChangeStoryBoard.Duration = new Duration(AnimationSequence.Simultaneous == AnimationSequence ? TimeSpan.FromTicks(1) : TimeSpan.FromMilliseconds(1001)); _storyBoardQueue.Enqueue( stateChangeStoryBoard, (sender, args) => { stateChangeStoryBoard.Stop(); }); } /// /// Handles data point state property change. /// /// The data point. /// Information about the event. private void OnDataPointStateChanged(object sender, RoutedPropertyChangedEventArgs args) { OnDataPointStateChanged(sender as DataPoint, args.OldValue, args.NewValue); } /// /// Handles data point state property change. /// /// The data point. /// The old value. /// The new value. protected virtual void OnDataPointStateChanged(DataPoint dataPoint, DataPointState oldValue, DataPointState newValue) { if (dataPoint.State == DataPointState.Hidden) { DetachEventHandlersFromDataPoint(dataPoint); PlotArea.Children.Remove(dataPoint); } } /// /// Handles data point actual dependent value property changes. /// /// The data point. /// Information about the event. private void OnDataPointActualDependentValueChanged(object sender, RoutedPropertyChangedEventArgs args) { OnDataPointActualDependentValueChanged(sender as DataPoint, args.OldValue, args.NewValue); } /// /// Handles data point actual dependent value property change. /// /// The data point. /// The old value. /// The new value. protected virtual void OnDataPointActualDependentValueChanged(DataPoint dataPoint, IComparable oldValue, IComparable newValue) { } /// /// Handles data point actual independent value property changes. /// /// The data point. /// Information about the event. private void OnDataPointActualIndependentValueChanged(object sender, RoutedPropertyChangedEventArgs args) { OnDataPointActualIndependentValueChanged(sender as DataPoint, args.OldValue, args.NewValue); } /// /// Handles data point actual independent value property change. /// /// The data point. /// The old value. /// The new value. protected virtual void OnDataPointActualIndependentValueChanged(DataPoint dataPoint, object oldValue, object newValue) { } /// /// Handles data point dependent value property changes. /// /// The data point. /// Information about the event. private void OnDataPointDependentValueChanged(object sender, RoutedPropertyChangedEventArgs args) { OnDataPointDependentValueChanged(sender as DataPoint, args.OldValue, args.NewValue); } /// /// Handles data point dependent value property change. /// /// The data point. /// The old value. /// The new value. protected virtual void OnDataPointDependentValueChanged(DataPoint dataPoint, IComparable oldValue, IComparable newValue) { } /// /// Handles data point independent value property changes. /// /// The data point. /// Information about the event. private void OnDataPointIndependentValueChanged(object sender, RoutedPropertyChangedEventArgs args) { OnDataPointIndependentValueChanged(sender as DataPoint, args.OldValue, args.NewValue); } /// /// Handles data point independent value property change. /// /// The data point. /// The old value. /// The new value. protected virtual void OnDataPointIndependentValueChanged(DataPoint dataPoint, object oldValue, object newValue) { } /// /// Returns a ResourceDictionaryEnumerator that returns ResourceDictionaries with a /// DataPointStyle having the specified TargetType or with a TargetType that is an /// ancestor of the specified type. /// /// The ResourceDictionaryDispenser. /// The TargetType. /// A value indicating whether to accept ancestors of the TargetType. /// A ResourceDictionary enumerator. internal static IEnumerator GetResourceDictionaryWithTargetType(IResourceDictionaryDispenser dispenser, Type targetType, bool takeAncestors) { return dispenser.GetResourceDictionariesWhere(dictionary => { Style style = dictionary[DataPointStyleName] as Style; if (null != style) { return (null != style.TargetType) && ((targetType == style.TargetType) || (takeAncestors && style.TargetType.IsAssignableFrom(targetType))); } return false; }); } } }