// (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;
});
}
}
}