All the controls missing in WPF. Over 1 million downloads.
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.

1611 lines
73 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;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Windows.Controls.DataVisualization.Charting.Primitives;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Markup;
using System.Windows.Media;
namespace System.Windows.Controls.DataVisualization.Charting
{
/// <summary>
/// Implements a series that is defined by one or more instances of the DefinitionSeries class.
/// </summary>
/// <QualityBand>Preview</QualityBand>
[ContentProperty("SeriesDefinitions")]
[TemplatePart(Name = SeriesAreaName, Type = typeof(Grid))]
[TemplatePart(Name = ItemContainerName, Type = typeof(DelegatingListBox))]
[SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Class is maintainable.")]
public abstract class DefinitionSeries : Control, ISeries, IAxisListener, IRangeProvider, IValueMarginProvider, IDataProvider, ISeriesHost
{
/// <summary>
/// Name of the SeriesArea property.
/// </summary>
private const string SeriesAreaName = "SeriesArea";
/// <summary>
/// Name of the ItemContainer property.
/// </summary>
private const string ItemContainerName = "ItemContainer";
/// <summary>
/// Gets or sets a value indicating whether the series is 100% stacked (versus normally stacked).
/// </summary>
protected bool IsStacked100 { get; set; }
/// <summary>
/// Gets the collection of DataItems representing the data of the series.
/// </summary>
protected ObservableCollection<DataItem> DataItems { get; private set; }
/// <summary>
/// Gets the SeriesArea template part instance.
/// </summary>
protected Panel SeriesArea { get; private set; }
/// <summary>
/// Stores an aggregated collection of legend items from the series definitions.
/// </summary>
private readonly AggregatedObservableCollection<object> _legendItems = new AggregatedObservableCollection<object>();
/// <summary>
/// Stores the collection of SeriesDefinitions that define the series.
/// </summary>
private readonly ObservableCollection<SeriesDefinition> _seriesDefinitions = new UniqueObservableCollection<SeriesDefinition>();
/// <summary>
/// Stores a mirror collection of ISeries corresponding directly to the collection of SeriesDefinitions.
/// </summary>
/// <remarks>
/// Not using ObservableCollectionListAdapter because of race condition on ItemsChanged event
/// </remarks>
private readonly ObservableCollection<ISeries> _seriesDefinitionsAsISeries = new ObservableCollection<ISeries>();
/// <summary>
/// Keeps the SeriesDefinitions collection synchronized with the Children collection of the SeriesArea.
/// </summary>
private readonly ObservableCollectionListAdapter<UIElement> _seriesAreaChildrenListAdapter = new ObservableCollectionListAdapter<UIElement>();
/// <summary>
/// Stores the clip geometry for the ItemContainer.
/// </summary>
private readonly RectangleGeometry _clipGeometry = new RectangleGeometry();
/// <summary>
/// Stores a reference to the ItemContainer template part.
/// </summary>
private DelegatingListBox _itemContainer;
/// <summary>
/// Tracks the collection of DataItem that are queued for update.
/// </summary>
private readonly List<DataItem> _queueUpdateDataItemPlacement_DataItems = new List<DataItem>();
/// <summary>
/// Tracks whether the dependent axis values changed for the next update.
/// </summary>
private bool _queueUpdateDataItemPlacement_DependentAxisValuesChanged;
/// <summary>
/// Tracks whether the independent axis values changed for the next update.
/// </summary>
private bool _queueUpdateDataItemPlacement_IndependentAxisValuesChanged;
/// <summary>
/// Stores a reference to the backing collection for the SelectedItems property.
/// </summary>
private ObservableCollection<object> _selectedItems = new ObservableCollection<object>();
/// <summary>
/// Tracks whether the SelectedItems collection is being synchronized (to prevent reentrancy).
/// </summary>
private bool _synchronizingSelectedItems;
#if !SILVERLIGHT
/// <summary>
/// Performs one-time initialization of DefinitionSeries data.
/// </summary>
[SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Dependency properties are initialized in-line.")]
static DefinitionSeries()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(DefinitionSeries), new FrameworkPropertyMetadata(typeof(DefinitionSeries)));
}
#endif
/// <summary>
/// Initializes a new instance of the DefinitionSeries class.
/// </summary>
protected DefinitionSeries()
{
#if SILVERLIGHT
this.DefaultStyleKey = typeof(DefinitionSeries);
#endif
_seriesDefinitions.CollectionChanged += new NotifyCollectionChangedEventHandler(SeriesDefinitionsCollectionChanged);
_seriesAreaChildrenListAdapter.Collection = _seriesDefinitions;
_selectedItems.CollectionChanged += new NotifyCollectionChangedEventHandler(SelectedItemsCollectionChanged);
DataItems = new ObservableCollection<DataItem>();
}
/// <summary>
/// Gets or sets the dependent axis of the series.
/// </summary>
public IAxis DependentAxis
{
get { return (IAxis)GetValue(DependentAxisProperty); }
set { SetValue(DependentAxisProperty, value); }
}
/// <summary>
/// Identifies the DependentAxis dependency property.
/// </summary>
public static readonly DependencyProperty DependentAxisProperty =
DependencyProperty.Register("DependentAxis", typeof(IAxis), typeof(DefinitionSeries), new PropertyMetadata(OnDependentAxisChanged));
/// <summary>
/// Handles changes to the DependentAxis dependency property.
/// </summary>
/// <param name="o">DependencyObject that changed.</param>
/// <param name="e">Event data for the DependencyPropertyChangedEvent.</param>
private static void OnDependentAxisChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
((DefinitionSeries)o).OnDependentAxisChanged((IAxis)e.OldValue, (IAxis)e.NewValue);
}
/// <summary>
/// Handles changes to the DependentAxis property.
/// </summary>
/// <param name="oldValue">Old value.</param>
/// <param name="newValue">New value.</param>
[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "oldValue", Justification = "Parameter is part of the pattern for DependencyProperty change handlers.")]
[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "newValue", Justification = "Parameter is part of the pattern for DependencyProperty change handlers.")]
private void OnDependentAxisChanged(IAxis oldValue, IAxis newValue)
{
if (null != ActualDependentAxis)
{
EnsureAxes(true, false, false);
}
}
/// <summary>
/// Gets or sets the independent axis of the series.
/// </summary>
public IAxis IndependentAxis
{
get { return (IAxis)GetValue(IndependentAxisProperty); }
set { SetValue(IndependentAxisProperty, value); }
}
/// <summary>
/// Identifies the IndependentAxis dependency property.
/// </summary>
public static readonly DependencyProperty IndependentAxisProperty =
DependencyProperty.Register("IndependentAxis", typeof(IAxis), typeof(DefinitionSeries), new PropertyMetadata(OnIndependentAxisChanged));
/// <summary>
/// Handles changes to the IndependentAxis dependency property.
/// </summary>
/// <param name="o">DependencyObject that changed.</param>
/// <param name="e">Event data for the DependencyPropertyChangedEvent.</param>
private static void OnIndependentAxisChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
((DefinitionSeries)o).OnIndependentAxisChanged((IAxis)e.OldValue, (IAxis)e.NewValue);
}
/// <summary>
/// Handles changes to the IndependentAxis property.
/// </summary>
/// <param name="oldValue">Old value.</param>
/// <param name="newValue">New value.</param>
[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "oldValue", Justification = "Parameter is part of the pattern for DependencyProperty change handlers.")]
[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "newValue", Justification = "Parameter is part of the pattern for DependencyProperty change handlers.")]
private void OnIndependentAxisChanged(IAxis oldValue, IAxis newValue)
{
if (null != ActualIndependentAxis)
{
EnsureAxes(false, true, false);
}
}
/// <summary>
/// Gets the rendered dependent axis of the series.
/// </summary>
public IAxis ActualDependentAxis
{
get { return (IAxis)GetValue(ActualDependentAxisProperty); }
protected set { SetValue(ActualDependentAxisProperty, value); }
}
/// <summary>
/// Identifies the ActualDependentAxis dependency property.
/// </summary>
public static readonly DependencyProperty ActualDependentAxisProperty =
DependencyProperty.Register("ActualDependentAxis", typeof(IAxis), typeof(DefinitionSeries), null);
/// <summary>
/// Gets the rendered independent axis of the series.
/// </summary>
public IAxis ActualIndependentAxis
{
get { return (IAxis)GetValue(ActualIndependentAxisProperty); }
protected set { SetValue(ActualIndependentAxisProperty, value); }
}
/// <summary>
/// Identifies the ActualIndependentAxis dependency property.
/// </summary>
public static readonly DependencyProperty ActualIndependentAxisProperty =
DependencyProperty.Register("ActualIndependentAxis", typeof(IAxis), typeof(DefinitionSeries), null);
/// <summary>
/// Gets the ActualDependentAxis as an IRangeAxis instance.
/// </summary>
protected IRangeAxis ActualDependentRangeAxis
{
get { return (IRangeAxis)ActualDependentAxis; }
}
/// <summary>
/// Gets the collection of legend items for the series.
/// </summary>
public ObservableCollection<object> LegendItems
{
get { return _legendItems; }
}
/// <summary>
/// Gets or sets the SeriesHost for the series.
/// </summary>
public ISeriesHost SeriesHost
{
get { return _seriesHost; }
set
{
if (null != _seriesHost)
{
_seriesHost.ResourceDictionariesChanged -= new EventHandler(SeriesHostResourceDictionariesChanged);
if (null != ActualDependentAxis)
{
ActualDependentAxis.RegisteredListeners.Remove(this);
ActualDependentAxis = null;
}
if (null != ActualIndependentAxis)
{
ActualIndependentAxis.RegisteredListeners.Remove(this);
ActualIndependentAxis = null;
}
foreach (SeriesDefinition definition in SeriesDefinitions)
{
SeriesDefinitionItemsSourceChanged(definition, definition.ItemsSource, null);
}
}
_seriesHost = value;
SeriesHostResourceDictionariesChanged(null, null);
if (null != _seriesHost)
{
_seriesHost.ResourceDictionariesChanged += new EventHandler(SeriesHostResourceDictionariesChanged);
foreach (SeriesDefinition definition in SeriesDefinitions)
{
SeriesDefinitionItemsSourceChanged(definition, null, definition.ItemsSource);
}
}
}
}
/// <summary>
/// Stores the SeriesHost for the series.
/// </summary>
private ISeriesHost _seriesHost;
/// <summary>
/// Gets or sets the collection of SeriesDefinitions that define the series.
/// </summary>
[SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "Setter is public to work around a limitation with the XAML editing tools.")]
[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "value", Justification = "Setter is public to work around a limitation with the XAML editing tools.")]
public Collection<SeriesDefinition> SeriesDefinitions
{
get { return _seriesDefinitions; }
set { throw new NotSupportedException(Properties.Resources.DefinitionSeries_SeriesDefinitions_SetterNotSupported); }
}
/// <summary>
/// Gets or sets the SelectionMode property.
/// </summary>
public SeriesSelectionMode SelectionMode
{
get { return (SeriesSelectionMode)GetValue(SelectionModeProperty); }
set { SetValue(SelectionModeProperty, value); }
}
/// <summary>
/// Identifies the SelectionMode dependency property.
/// </summary>
public static readonly DependencyProperty SelectionModeProperty =
DependencyProperty.Register("SelectionMode", typeof(SeriesSelectionMode), typeof(DefinitionSeries), new PropertyMetadata(SeriesSelectionMode.None, OnSelectionModeChanged));
/// <summary>
/// Handles changes to the SelectionMode dependency property.
/// </summary>
/// <param name="o">DependencyObject that changed.</param>
/// <param name="e">Event data for the DependencyPropertyChangedEvent.</param>
private static void OnSelectionModeChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
((DefinitionSeries)o).OnSelectionModeChanged((SeriesSelectionMode)e.OldValue, (SeriesSelectionMode)e.NewValue);
}
/// <summary>
/// Handles changes to the SelectionMode property.
/// </summary>
/// <param name="oldValue">Old value.</param>
/// <param name="newValue">New value.</param>
[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "oldValue", Justification = "Parameter is part of the pattern for DependencyProperty change handlers.")]
private void OnSelectionModeChanged(SeriesSelectionMode oldValue, SeriesSelectionMode newValue)
{
if (null != _itemContainer)
{
switch (newValue)
{
case SeriesSelectionMode.None:
_itemContainer.SelectedItem = null;
_itemContainer.SelectionMode = Controls.SelectionMode.Single;
break;
case SeriesSelectionMode.Single:
_itemContainer.SelectionMode = Controls.SelectionMode.Single;
break;
case SeriesSelectionMode.Multiple:
_itemContainer.SelectionMode = Controls.SelectionMode.Multiple;
break;
}
}
}
/// <summary>
/// Gets or sets the SelectedIndex property.
/// </summary>
public int SelectedIndex
{
get { return (int)GetValue(SelectedIndexProperty); }
set { SetValue(SelectedIndexProperty, value); }
}
/// <summary>
/// Identifies the SelectedIndex dependency property.
/// </summary>
public static readonly DependencyProperty SelectedIndexProperty =
DependencyProperty.Register("SelectedIndex", typeof(int), typeof(DefinitionSeries), new PropertyMetadata(-1));
/// <summary>
/// Gets or sets the SelectedItem property.
/// </summary>
public object SelectedItem
{
get { return (object)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
/// <summary>
/// Identifies the SelectedItem dependency property.
/// </summary>
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(object), typeof(DefinitionSeries), null);
/// <summary>
/// Gets the currently selected items.
/// </summary>
/// <remarks>
/// This property is meant to be used when SelectionMode is Multiple. If the selection mode is Single the correct property to use is SelectedItem.
/// </remarks>
public IList SelectedItems
{
get { return _selectedItems; }
}
/// <summary>
/// Handles the CollectionChanged event of the SelectedItems collection.
/// </summary>
/// <param name="sender">Event source.</param>
/// <param name="e">Event arguments.</param>
private void SelectedItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (!_synchronizingSelectedItems)
{
try
{
_synchronizingSelectedItems = true;
// Synchronize the SelectedItems collection
if (null != _itemContainer)
{
if (NotifyCollectionChangedAction.Reset == e.Action)
{
if (0 < _itemContainer.SelectedItems.Count)
{
_itemContainer.SelectedItems.Clear();
}
foreach (DataItem dataItem in _selectedItems.SelectMany(v => DataItems.Where(di => object.Equals(di.Value, v))))
{
_itemContainer.SelectedItems.Add(dataItem);
}
}
else
{
if (null != e.OldItems)
{
foreach (DataItem dataItem in e.OldItems.CastWrapper<object>().SelectMany(v => DataItems.Where(di => object.Equals(di.Value, v))))
{
_itemContainer.SelectedItems.Remove(dataItem);
}
}
if (null != e.NewItems)
{
foreach (DataItem dataItem in e.NewItems.CastWrapper<object>().SelectMany(v => DataItems.Where(di => object.Equals(di.Value, v))))
{
_itemContainer.SelectedItems.Add(dataItem);
}
}
}
}
}
finally
{
_synchronizingSelectedItems = false;
}
}
}
/// <summary>
/// Handles the SelectionChanged event of the ItemContainer class.
/// </summary>
/// <param name="sender">Event source.</param>
/// <param name="e">Event arguments.</param>
private void ItemContainerSelectionChanged(object sender, SelectionChangedEventArgs e)
{
DataItem[] removedDataItems = e.RemovedItems.CastWrapper<DataItem>().ToArray();
DataItem[] addedDataItems = e.AddedItems.CastWrapper<DataItem>().ToArray();
if (!_synchronizingSelectedItems)
{
try
{
_synchronizingSelectedItems = true;
// Synchronize the SelectedItems collection
foreach (object obj in removedDataItems.Select(di => di.Value))
{
_selectedItems.Remove(obj);
}
foreach (object obj in addedDataItems.Select(di => di.Value))
{
_selectedItems.Add(obj);
}
}
finally
{
_synchronizingSelectedItems = false;
}
}
// Pass the SelectionChanged event on to any listeners
IList removedItems = removedDataItems.Select(di => di.Value).ToArray();
IList addedItems = addedDataItems.Select(di => di.Value).ToArray();
#if SILVERLIGHT
SelectionChangedEventHandler handler = SelectionChanged;
if (null != handler)
{
handler(this, new SelectionChangedEventArgs(removedItems, addedItems));
}
#else
RaiseEvent(new SelectionChangedEventArgs(SelectionChangedEvent, removedItems, addedItems));
#endif
}
/// <summary>
/// Occurs when the selection of a DefinitionSeries changes.
/// </summary>
#if SILVERLIGHT
public event SelectionChangedEventHandler SelectionChanged;
#else
public event SelectionChangedEventHandler SelectionChanged
{
add { AddHandler(SelectionChangedEvent, value); }
remove { RemoveHandler(SelectionChangedEvent, value); }
}
/// <summary>
/// Identifies the SelectionChanged routed event.
/// </summary>
public static readonly RoutedEvent SelectionChangedEvent =
EventManager.RegisterRoutedEvent("SelectionChanged", RoutingStrategy.Bubble, typeof(SelectionChangedEventHandler), typeof(DefinitionSeries));
#endif
/// <summary>
/// Builds the visual tree for the control when a new template is applied.
/// </summary>
public override void OnApplyTemplate()
{
if (null != _itemContainer)
{
_itemContainer.PrepareContainerForItem = null;
_itemContainer.ClearContainerForItem = null;
_itemContainer.ItemsSource = null;
_itemContainer.Clip = null;
_itemContainer.SizeChanged -= new SizeChangedEventHandler(ItemContainerSizeChanged);
_itemContainer.SelectionChanged -= new SelectionChangedEventHandler(ItemContainerSelectionChanged);
_itemContainer.ClearValue(Selector.SelectedIndexProperty);
_itemContainer.ClearValue(Selector.SelectedItemProperty);
}
base.OnApplyTemplate();
SeriesArea = GetTemplateChild(SeriesAreaName) as Panel;
if (null != SeriesArea)
{
_seriesAreaChildrenListAdapter.TargetList = SeriesArea.Children;
_seriesAreaChildrenListAdapter.Populate();
}
_itemContainer = GetTemplateChild(ItemContainerName) as DelegatingListBox;
if (null != _itemContainer)
{
_itemContainer.PrepareContainerForItem = PrepareContainerForItem;
_itemContainer.ClearContainerForItem = ClearContainerForItem;
_itemContainer.ItemsSource = DataItems;
_itemContainer.Clip = _clipGeometry;
_itemContainer.SizeChanged += new SizeChangedEventHandler(ItemContainerSizeChanged);
_itemContainer.SelectionChanged += new SelectionChangedEventHandler(ItemContainerSelectionChanged);
_itemContainer.SetBinding(Selector.SelectedIndexProperty, new Binding("SelectedIndex") { Source = this, Mode = BindingMode.TwoWay });
_itemContainer.SetBinding(Selector.SelectedItemProperty, new Binding("SelectedItem") { Source = this, Mode = BindingMode.TwoWay, Converter = new SelectedItemToDataItemConverter(DataItems) });
}
// Synchronize selection state with new ItemContainer
OnSelectionModeChanged(SeriesSelectionMode.None, SelectionMode);
SelectedItemsCollectionChanged(null, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
/// <summary>
/// Prepares the specified element to display the specified item.
/// </summary>
/// <param name="element">The element used to display the specified item.</param>
/// <param name="item">The item to display.</param>
private void PrepareContainerForItem(DependencyObject element, object item)
{
DataItem dataItem = (DataItem)item;
DataPoint dataPoint = CreateDataPoint();
dataItem.DataPoint = dataPoint;
dataPoint.DataContext = dataItem.Value;
dataPoint.SetBinding(DataPoint.DependentValueProperty, dataItem.SeriesDefinition.DependentValueBinding);
dataPoint.SetBinding(DataPoint.IndependentValueProperty, dataItem.SeriesDefinition.IndependentValueBinding);
dataPoint.SetBinding(DataPoint.StyleProperty, new Binding("ActualDataPointStyle") { Source = dataItem.SeriesDefinition });
dataPoint.DependentValueChanged += new RoutedPropertyChangedEventHandler<IComparable>(DataPointDependentValueChanged);
dataPoint.ActualDependentValueChanged += new RoutedPropertyChangedEventHandler<IComparable>(DataPointActualDependentValueChanged);
dataPoint.IndependentValueChanged += new RoutedPropertyChangedEventHandler<object>(DataPointIndependentValueChanged);
dataPoint.ActualIndependentValueChanged += new RoutedPropertyChangedEventHandler<object>(DataPointActualIndependentValueChanged);
dataPoint.StateChanged += new RoutedPropertyChangedEventHandler<DataPointState>(DataPointStateChanged);
dataPoint.DefinitionSeriesIsSelectionEnabledHandling = true;
ContentControl container = (ContentControl)element;
dataItem.Container = container;
Binding selectionEnabledBinding = new Binding("SelectionMode") { Source = this, Converter = new SelectionModeToSelectionEnabledConverter() };
container.SetBinding(ContentControl.IsTabStopProperty, selectionEnabledBinding);
dataPoint.SetBinding(DataPoint.IsSelectionEnabledProperty, selectionEnabledBinding);
dataPoint.SetBinding(DataPoint.IsSelectedProperty, new Binding("IsSelected") { Source = container, Mode = BindingMode.TwoWay });
dataPoint.Visibility = Visibility.Collapsed;
dataPoint.State = DataPointState.Showing;
PrepareDataPoint(dataPoint);
container.Content = dataPoint;
}
/// <summary>
/// Undoes the effects of the PrepareContainerForItemOverride method.
/// </summary>
/// <param name="element">The container element.</param>
/// <param name="item">The item to display.</param>
private void ClearContainerForItem(DependencyObject element, object item)
{
DataItem dataItem = (DataItem)item;
DataPoint dataPoint = dataItem.DataPoint;
dataPoint.DependentValueChanged -= new RoutedPropertyChangedEventHandler<IComparable>(DataPointDependentValueChanged);
dataPoint.ActualDependentValueChanged -= new RoutedPropertyChangedEventHandler<IComparable>(DataPointActualDependentValueChanged);
dataPoint.IndependentValueChanged -= new RoutedPropertyChangedEventHandler<object>(DataPointIndependentValueChanged);
dataPoint.ActualIndependentValueChanged -= new RoutedPropertyChangedEventHandler<object>(DataPointActualIndependentValueChanged);
dataPoint.StateChanged -= new RoutedPropertyChangedEventHandler<DataPointState>(DataPointStateChanged);
dataPoint.ClearValue(DataPoint.DependentValueProperty);
dataPoint.ClearValue(DataPoint.IndependentValueProperty);
dataPoint.ClearValue(DataPoint.StyleProperty);
dataPoint.ClearValue(DataPoint.IsSelectionEnabledProperty);
dataPoint.ClearValue(DataPoint.IsSelectedProperty);
ContentControl container = (ContentControl)dataItem.Container;
container.ClearValue(ContentControl.IsTabStopProperty);
dataPoint.DataContext = null;
}
/// <summary>
/// Prepares a DataPoint for use.
/// </summary>
/// <param name="dataPoint">DataPoint instance.</param>
protected virtual void PrepareDataPoint(DataPoint dataPoint) { }
/// <summary>
/// Creates a DataPoint for the series.
/// </summary>
/// <returns>Series-appropriate DataPoint instance.</returns>
protected abstract DataPoint CreateDataPoint();
/// <summary>
/// Provides an internally-accessible wrapper for calling CreateDataPoint.
/// </summary>
/// <returns>Series-appropriate DataPoint instance.</returns>
internal DataPoint InternalCreateDataPoint()
{
return CreateDataPoint();
}
/// <summary>
/// Handles the SizeChanged event of the ItemContainer.
/// </summary>
/// <param name="sender">Event source.</param>
/// <param name="e">Event arguments.</param>
private void ItemContainerSizeChanged(object sender, SizeChangedEventArgs e)
{
_clipGeometry.Rect = new Rect(0, 0, e.NewSize.Width, e.NewSize.Height);
QueueUpdateDataItemPlacement(false, false, DataItems);
}
/// <summary>
/// Returns the DataItem corresponding to the specified DataPoint.
/// </summary>
/// <param name="dataPoint">Specified DataPoint.</param>
/// <returns>Corresponding DataItem.</returns>
protected DataItem DataItemFromDataPoint(DataPoint dataPoint)
{
return DataItems.Where(di => di.DataPoint == dataPoint).Single();
}
/// <summary>
/// Handles the DependentValueChanged event of a DataPoint.
/// </summary>
/// <param name="sender">Event source.</param>
/// <param name="e">Event arguments.</param>
private void DataPointDependentValueChanged(object sender, RoutedPropertyChangedEventArgs<IComparable> e)
{
DataPoint dataPoint = (DataPoint)sender;
SeriesDefinition definition = DataItemFromDataPoint(dataPoint).SeriesDefinition;
TimeSpan transitionDuration = definition.TransitionDuration;
if (0 < transitionDuration.TotalMilliseconds)
{
dataPoint.BeginAnimation(DataPoint.ActualDependentValueProperty, "ActualDependentValue", e.NewValue, definition.TransitionDuration, definition.TransitionEasingFunction);
}
else
{
dataPoint.ActualDependentValue = e.NewValue;
}
}
/// <summary>
/// Handles the ActualDependentValueChanged event of a DataPoint.
/// </summary>
/// <param name="sender">Event source.</param>
/// <param name="e">Event arguments.</param>
private void DataPointActualDependentValueChanged(object sender, RoutedPropertyChangedEventArgs<IComparable> e)
{
DataPoint dataPoint = (DataPoint)sender;
QueueUpdateDataItemPlacement(true, false, DataItems.Where(di => di.DataPoint == dataPoint));
}
/// <summary>
/// Handles the IndependentValueChanged event of a DataPoint.
/// </summary>
/// <param name="sender">Event source.</param>
/// <param name="e">Event arguments.</param>
private void DataPointIndependentValueChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
DataPoint dataPoint = (DataPoint)sender;
SeriesDefinition definition = DataItemFromDataPoint(dataPoint).SeriesDefinition;
TimeSpan transitionDuration = definition.TransitionDuration;
if (0 < transitionDuration.TotalMilliseconds)
{
dataPoint.BeginAnimation(DataPoint.ActualIndependentValueProperty, "ActualIndependentValue", e.NewValue, definition.TransitionDuration, definition.TransitionEasingFunction);
}
else
{
dataPoint.ActualIndependentValue = e.NewValue;
}
}
/// <summary>
/// Handles the ActualIndependentValueChanged event of a DataPoint.
/// </summary>
/// <param name="sender">Event source.</param>
/// <param name="e">Event arguments.</param>
private void DataPointActualIndependentValueChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
DataPoint dataPoint = (DataPoint)sender;
QueueUpdateDataItemPlacement(false, true, DataItems.Where(di => di.DataPoint == dataPoint));
}
/// <summary>
/// Handles the StateChanged event of a DataPoint.
/// </summary>
/// <param name="sender">Event source.</param>
/// <param name="e">Event arguments.</param>
private void DataPointStateChanged(object sender, RoutedPropertyChangedEventArgs<DataPointState> e)
{
DataPoint dataPoint = (DataPoint)sender;
if (DataPointState.Hidden == dataPoint.State)
{
DataItems.Remove(DataItems.Where(di => di.DataPoint == dataPoint).Single());
RemovedDataItems();
}
}
/// <summary>
/// Notifies the specified axis of changes to values plotting against it.
/// </summary>
/// <param name="axis">Specified axis.</param>
protected void NotifyAxisValuesChanged(IAxis axis)
{
if (null != axis)
{
IRangeConsumer rangeConsumer = axis as IRangeConsumer;
if (null != rangeConsumer)
{
IRangeProvider rangeProvider = (IRangeProvider)this;
rangeConsumer.RangeChanged(rangeProvider, new Range<IComparable>() /*rangeProvider.GetRange(rangeConsumer)*/);
}
IDataConsumer dataConsumer = axis as IDataConsumer;
if (null != dataConsumer)
{
IDataProvider dataProvider = (IDataProvider)this;
dataConsumer.DataChanged(dataProvider, null /*dataProvider.GetData(dataConsumer)*/);
}
}
}
/// <summary>
/// Notifies the specified axis of changes to value margins plotting against it.
/// </summary>
/// <param name="axis">Specified axis.</param>
/// <param name="valueMargins">Sequence of value margins that have changed.</param>
protected void NotifyValueMarginsChanged(IAxis axis, IEnumerable<ValueMargin> valueMargins)
{
if (null != axis)
{
IValueMarginConsumer valueMarginConsumer = axis as IValueMarginConsumer;
if (null != valueMarginConsumer)
{
IValueMarginProvider valueMarginProvider = (IValueMarginProvider)this;
valueMarginConsumer.ValueMarginsChanged(valueMarginProvider, valueMargins);
}
}
}
/// <summary>
/// Handles the CollectionChanged event of the SeriesDefinitions collection.
/// </summary>
/// <param name="sender">Event source.</param>
/// <param name="e">Event arguments.</param>
private void SeriesDefinitionsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
SeriesDefinitionsCollectionChanged(e.Action, e.OldItems, e.OldStartingIndex, e.NewItems, e.NewStartingIndex);
}
/// <summary>
/// Handles the CollectionChanged event of the SeriesDefinitions collection.
/// </summary>
/// <param name="action">Type of change.</param>
/// <param name="oldItems">Sequence of old items.</param>
/// <param name="oldStartingIndex">Starting index of old items.</param>
/// <param name="newItems">Sequence of new items.</param>
/// <param name="newStartingIndex">Starting index of new items.</param>
protected virtual void SeriesDefinitionsCollectionChanged(NotifyCollectionChangedAction action, IList oldItems, int oldStartingIndex, IList newItems, int newStartingIndex)
{
if (null != oldItems)
{
foreach (SeriesDefinition oldDefinition in oldItems.CastWrapper<SeriesDefinition>())
{
ISeries oldSeries = (ISeries)oldDefinition;
SeriesDefinitionItemsSourceChanged(oldDefinition, oldDefinition.ItemsSource, null);
_seriesDefinitionsAsISeries.Remove(oldDefinition);
_legendItems.ChildCollections.Remove(oldSeries.LegendItems);
UpdatePaletteProperties(oldDefinition);
oldSeries.SeriesHost = null;
oldDefinition.Index = -1;
}
}
if (null != newItems)
{
int index = newStartingIndex;
foreach (SeriesDefinition newDefinition in newItems.CastWrapper<SeriesDefinition>())
{
ISeries newSeries = (ISeries)newDefinition;
newSeries.SeriesHost = this;
UpdatePaletteProperties(newDefinition);
_legendItems.ChildCollections.Add(newSeries.LegendItems);
_seriesDefinitionsAsISeries.Add(newDefinition);
newDefinition.Index = index;
SeriesDefinitionItemsSourceChanged(newDefinition, null, newDefinition.ItemsSource);
index++;
}
}
}
/// <summary>
/// Updates the palette properties of the specified SeriesDefinition.
/// </summary>
/// <param name="definition">Specified SeriesDefinition.</param>
private void UpdatePaletteProperties(SeriesDefinition definition)
{
ResourceDictionary resources = null;
if (null != SeriesHost)
{
Type dataPointType = CreateDataPoint().GetType();
using (IEnumerator<ResourceDictionary> enumerator = SeriesHost.GetResourceDictionariesWhere(dictionary =>
{
Style style = dictionary["DataPointStyle"] as Style;
if (null != style)
{
return (null != style.TargetType) && (style.TargetType.IsAssignableFrom(dataPointType));
}
return false;
}))
{
if (enumerator.MoveNext())
{
resources = enumerator.Current;
}
}
}
definition.PaletteDataPointStyle = (null != resources) ? resources["DataPointStyle"] as Style : null;
definition.PaletteDataShapeStyle = (null != resources) ? resources["DataShapeStyle"] as Style : null;
definition.PaletteLegendItemStyle = (null != resources) ? resources["LegendItemStyle"] as Style : null;
}
/// <summary>
/// Handles changes to the ItemsSource of a SeriesDefinition.
/// </summary>
/// <param name="definition">SeriesDefinition owner.</param>
/// <param name="oldValue">Old value.</param>
/// <param name="newValue">New value.</param>
internal void SeriesDefinitionItemsSourceChanged(SeriesDefinition definition, IEnumerable oldValue, IEnumerable newValue)
{
if (null != oldValue)
{
foreach (DataItem dataItem in DataItems.Where(di => di.SeriesDefinition == definition).ToArray())
{
DataItems.Remove(dataItem);
}
RemovedDataItems();
}
if (null != newValue)
{
// No need to add items if SeriesHost null; setting SeriesHost will take care of that
if (null != SeriesHost)
{
AddDataItems(definition, newValue.CastWrapper<object>(), 0);
}
}
}
/// <summary>
/// Handles changes to the ItemsSource collection of a SeriesDefinition.
/// </summary>
/// <param name="definition">SeriesDefinition owner.</param>
/// <param name="action">Type of change.</param>
/// <param name="oldItems">Sequence of old items.</param>
/// <param name="oldStartingIndex">Starting index of old items.</param>
/// <param name="newItems">Sequence of new items.</param>
/// <param name="newStartingIndex">Starting index of new items.</param>
[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Linq is artificially increasing the rating.")]
internal void SeriesDefinitionItemsSourceCollectionChanged(SeriesDefinition definition, NotifyCollectionChangedAction action, IList oldItems, int oldStartingIndex, IList newItems, int newStartingIndex)
{
if (NotifyCollectionChangedAction.Replace == action)
{
// Perform in-place replacements
foreach (DataItem dataItem in DataItems.Where(di => (di.SeriesDefinition == definition) && (newStartingIndex <= di.Index) && (di.Index < newStartingIndex + newItems.Count)))
{
dataItem.Value = newItems[dataItem.Index - newStartingIndex];
}
}
else
{
if (NotifyCollectionChangedAction.Reset == action)
{
// Set up parameters to allow normal old/new item handling to be used
Debug.Assert(null == oldItems, "Reset action with non-null oldItems.");
oldItems = DataItems.Where(di => (di.SeriesDefinition == definition)).ToArray();
oldStartingIndex = 0;
newItems = definition.ItemsSource.CastWrapper<object>().ToArray();
newStartingIndex = 0;
}
if (null != oldItems)
{
// Get rid of old items
foreach (DataItem oldDataItem in DataItems.Where(di => (di.SeriesDefinition == definition) && (oldStartingIndex <= di.Index) && (di.Index < oldStartingIndex + oldItems.Count)))
{
oldDataItem.Index = -1;
if (null != oldDataItem.DataPoint)
{
oldDataItem.DataPoint.State = DataPointState.Hiding;
}
}
// Adjust index of shifted items
foreach (DataItem dataItem in DataItems.Where(di => (di.SeriesDefinition == definition) && (oldStartingIndex + oldItems.Count <= di.Index)))
{
dataItem.Index -= oldItems.Count;
}
}
if (null != newItems)
{
// Adjust index of shifted items
foreach (DataItem dataItem in DataItems.Where(di => (di.SeriesDefinition == definition) && (newStartingIndex <= di.Index)))
{
dataItem.Index += newItems.Count;
}
// Add new items
AddDataItems(definition, newItems.CastWrapper<object>(), newStartingIndex);
}
}
#if DEBUG
// Validate all DataItem index and value properties
foreach (var group in DataItems.Where(di => 0 <= di.Index).OrderBy(di => di.Index).GroupBy(di => di.SeriesDefinition))
{
object[] items = group.Key.ItemsSource.CastWrapper<object>().ToArray();
int i = 0;
foreach (DataItem dataItem in group)
{
Debug.Assert(i == dataItem.Index, "DataItem index mis-match.");
Debug.Assert(dataItem.Value.Equals(items[i]), "DataItem value mis-match.");
i++;
}
}
#endif
}
/// <summary>
/// Handles the ResourceDictionariesChanged event of the SeriesHost owner.
/// </summary>
/// <param name="sender">Event source.</param>
/// <param name="e">Event arguments.</param>
private void SeriesHostResourceDictionariesChanged(object sender, EventArgs e)
{
foreach (SeriesDefinition definition in SeriesDefinitions)
{
UpdatePaletteProperties(definition);
}
}
/// <summary>
/// Creates and adds DataItems for the specified SeriesDefinition's items.
/// </summary>
/// <param name="definition">Specified SeriesDefinition.</param>
/// <param name="items">Sequence of items.</param>
/// <param name="startingIndex">Starting index.</param>
private void AddDataItems(SeriesDefinition definition, IEnumerable<object> items, int startingIndex)
{
int index = startingIndex;
foreach (object item in items)
{
DataItems.Add(new DataItem(definition) { Value = item, Index = index });
index++;
}
// Because properties (like DependentValueBinding) may still be getting set
Dispatcher.BeginInvoke((Action)AddedDataItems);
}
/// <summary>
/// Updates the axes after DataItems have been added.
/// </summary>
private void AddedDataItems()
{
EnsureAxes(false, false, true);
}
/// <summary>
/// Notifies the axes after DataItems have been removed.
/// </summary>
private void RemovedDataItems()
{
NotifyAxisValuesChanged(ActualIndependentAxis);
NotifyAxisValuesChanged(ActualDependentAxis);
}
/// <summary>
/// Ensures that suitable axes are present and registered.
/// </summary>
/// <param name="updateDependentAxis">True if the dependent axis needs to be updated.</param>
/// <param name="updateIndependentAxis">True if the independent axis needs to be updated.</param>
/// <param name="unconditionallyNotifyAxes">True if both axis are to be notified unconditionally.</param>
private void EnsureAxes(bool updateDependentAxis, bool updateIndependentAxis, bool unconditionallyNotifyAxes)
{
foreach (SeriesDefinition definition in SeriesDefinitions)
{
if (null == definition.DependentValueBinding)
{
throw new InvalidOperationException(Properties.Resources.DefinitionSeries_EnsureAxes_MissingDependentValueBinding);
}
if (null == definition.IndependentValueBinding)
{
throw new InvalidOperationException(Properties.Resources.DefinitionSeries_EnsureAxes_MissingIndependentValueBinding);
}
}
if ((null != SeriesHost) && DataItems.Any())
{
// Ensure a dependent axis is present or updated
bool changedActualDependentAxis = false;
if (updateDependentAxis && (null != ActualDependentAxis))
{
ActualDependentAxis.RegisteredListeners.Remove(this);
ActualDependentAxis = null;
}
if (null == ActualDependentAxis)
{
ActualDependentAxis = DependentAxis ?? AcquireDependentAxis();
ActualDependentAxis.RegisteredListeners.Add(this);
if (!SeriesHost.Axes.Contains(ActualDependentAxis))
{
SeriesHost.Axes.Add(ActualDependentAxis);
}
changedActualDependentAxis = true;
}
// Ensure an independent axis is present or updated
bool changedActualIndependentAxis = false;
if (updateIndependentAxis && (null != ActualIndependentAxis))
{
ActualIndependentAxis.RegisteredListeners.Remove(this);
ActualIndependentAxis = null;
}
if (null == ActualIndependentAxis)
{
ActualIndependentAxis = IndependentAxis ?? AcquireIndependentAxis();
ActualIndependentAxis.RegisteredListeners.Add(this);
if (!SeriesHost.Axes.Contains(ActualIndependentAxis))
{
SeriesHost.Axes.Add(ActualIndependentAxis);
}
changedActualIndependentAxis = true;
}
// Queue an update if necessary or requested
if (changedActualDependentAxis || changedActualIndependentAxis || unconditionallyNotifyAxes)
{
QueueUpdateDataItemPlacement(changedActualDependentAxis || unconditionallyNotifyAxes, changedActualIndependentAxis || unconditionallyNotifyAxes, DataItems);
}
}
}
/// <summary>
/// Acquires a dependent axis suitable for use with the data values of the series.
/// </summary>
/// <returns>Axis instance.</returns>
protected abstract IAxis AcquireDependentAxis();
/// <summary>
/// Acquires an independent axis suitable for use with the data values of the series.
/// </summary>
/// <returns>Axis instance.</returns>
protected abstract IAxis AcquireIndependentAxis();
/// <summary>
/// Handles notification of the invalidation of an axis.
/// </summary>
/// <param name="axis">Invalidated axis.</param>
void IAxisListener.AxisInvalidated(IAxis axis)
{
QueueUpdateDataItemPlacement(false, false, DataItems);
}
/// <summary>
/// Queues an update of DataItem placement for the next update opportunity.
/// </summary>
/// <param name="dependentAxisValuesChanged">True if the dependent axis values have changed.</param>
/// <param name="independentAxisValuesChanged">True if the independent axis values have changed.</param>
/// <param name="dataItems">Sequence of DataItems to update.</param>
private void QueueUpdateDataItemPlacement(bool dependentAxisValuesChanged, bool independentAxisValuesChanged, IEnumerable<DataItem> dataItems)
{
_queueUpdateDataItemPlacement_DependentAxisValuesChanged |= dependentAxisValuesChanged;
_queueUpdateDataItemPlacement_IndependentAxisValuesChanged |= independentAxisValuesChanged;
_queueUpdateDataItemPlacement_DataItems.AddRange(dataItems);
InvalidateArrange();
}
/// <summary>
/// Called when the control needs to arrange its children.
/// </summary>
/// <param name="arrangeBounds">Bounds to arrange within.</param>
/// <returns>Arranged size.</returns>
/// <remarks>
/// Used as a good place to dequeue queued work.
/// </remarks>
protected override Size ArrangeOverride(Size arrangeBounds)
{
Size arrangedSize = base.ArrangeOverride(arrangeBounds);
if (_queueUpdateDataItemPlacement_DependentAxisValuesChanged)
{
NotifyAxisValuesChanged(ActualDependentAxis);
_queueUpdateDataItemPlacement_DependentAxisValuesChanged = false;
}
if (_queueUpdateDataItemPlacement_IndependentAxisValuesChanged)
{
NotifyAxisValuesChanged(ActualIndependentAxis);
_queueUpdateDataItemPlacement_IndependentAxisValuesChanged = false;
}
UpdateDataItemPlacement(_queueUpdateDataItemPlacement_DataItems.Distinct());
_queueUpdateDataItemPlacement_DataItems.Clear();
return arrangedSize;
}
/// <summary>
/// Updates the placement of the DataItems (data points) of the series.
/// </summary>
/// <param name="dataItems">DataItems in need of an update.</param>
protected abstract void UpdateDataItemPlacement(IEnumerable<DataItem> dataItems);
/// <summary>
/// Returns the range for the data points of the series.
/// </summary>
/// <param name="rangeConsumer">Consumer of the range.</param>
/// <returns>Range of values.</returns>
Range<IComparable> IRangeProvider.GetRange(IRangeConsumer rangeConsumer)
{
return IRangeProviderGetRange(rangeConsumer);
}
/// <summary>
/// Returns the range for the data points of the series.
/// </summary>
/// <param name="rangeConsumer">Consumer of the range.</param>
/// <returns>Range of values.</returns>
protected virtual Range<IComparable> IRangeProviderGetRange(IRangeConsumer rangeConsumer)
{
if (rangeConsumer == ActualIndependentAxis)
{
if (ActualIndependentAxis.CanPlot(0.0))
{
return IndependentValueGroups
.Select(g => ValueHelper.ToDouble(g.IndependentValue))
.Where(d => ValueHelper.CanGraph(d))
.DefaultIfEmpty()
.CastWrapper<IComparable>()
.GetRange();
}
else
{
return IndependentValueGroups
.Select(g => ValueHelper.ToDateTime(g.IndependentValue))
.DefaultIfEmpty()
.CastWrapper<IComparable>()
.GetRange();
}
}
throw new NotSupportedException();
}
/// <summary>
/// Returns the value margins for the data points of the series.
/// </summary>
/// <param name="valueMarginConsumer">Consumer of the value margins.</param>
/// <returns>Sequence of value margins.</returns>
IEnumerable<ValueMargin> IValueMarginProvider.GetValueMargins(IValueMarginConsumer valueMarginConsumer)
{
return IValueMarginProviderGetValueMargins(valueMarginConsumer);
}
/// <summary>
/// Returns the value margins for the data points of the series.
/// </summary>
/// <param name="valueMarginConsumer">Consumer of the value margins.</param>
/// <returns>Sequence of value margins.</returns>
protected virtual IEnumerable<ValueMargin> IValueMarginProviderGetValueMargins(IValueMarginConsumer valueMarginConsumer)
{
throw new NotImplementedException();
}
/// <summary>
/// Returns the data for the data points of the series.
/// </summary>
/// <param name="dataConsumer">Consumer of the data.</param>
/// <returns>Sequence of data.</returns>
IEnumerable<object> IDataProvider.GetData(IDataConsumer dataConsumer)
{
return IDataProviderGetData(dataConsumer);
}
/// <summary>
/// Returns the data for the data points of the series.
/// </summary>
/// <param name="dataConsumer">Consumer of the data.</param>
/// <returns>Sequence of data.</returns>
protected virtual IEnumerable<object> IDataProviderGetData(IDataConsumer dataConsumer)
{
if (dataConsumer == ActualIndependentAxis)
{
return IndependentValueGroups.Select(cg => cg.IndependentValue).Distinct();
}
throw new NotImplementedException();
}
/// <summary>
/// Gets a sequence of IndependentValueGroups.
/// </summary>
protected virtual IEnumerable<IndependentValueGroup> IndependentValueGroups
{
get
{
return DataItems
.GroupBy(di => di.ActualIndependentValue)
.Select(g => new IndependentValueGroup(g.Key, g.OrderBy(di => di.SeriesDefinition.Index)));
}
}
/// <summary>
/// Gets a sequence of IndependentValueGroups ordered by independent value.
/// </summary>
protected IEnumerable<IndependentValueGroup> IndependentValueGroupsOrderedByIndependentValue
{
get
{
return IndependentValueGroups
.OrderBy(g => g.IndependentValue);
}
}
/// <summary>
/// Gets a sequence of sequences of the dependent values associated with each independent value.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nesting reflects the actual hierarchy of the data.")]
protected IEnumerable<IEnumerable<double>> IndependentValueDependentValues
{
get
{
return IndependentValueGroups
.Select(g =>
{
g.Denominator = IsStacked100 ?
g.DataItems.Sum(di => Math.Abs(ValueHelper.ToDouble(di.ActualDependentValue))) :
1;
if (0 == g.Denominator)
{
g.Denominator = 1;
}
return g;
})
.Select(g => g.DataItems
.Select(di => ValueHelper.ToDouble(di.ActualDependentValue) * (IsStacked100 ? (100 / g.Denominator) : 1)));
}
}
/// <summary>
/// Represents an independent value and the dependent values that are associated with it.
/// </summary>
protected class IndependentValueGroup
{
/// <summary>
/// Initializes a new instance of the IndependentValueGroup class.
/// </summary>
/// <param name="independentValue">Independent value.</param>
/// <param name="dataItems">Associated DataItems.</param>
public IndependentValueGroup(object independentValue, IEnumerable<DataItem> dataItems)
{
IndependentValue = independentValue;
DataItems = dataItems;
}
/// <summary>
/// Gets the independent value.
/// </summary>
public object IndependentValue { get; private set; }
/// <summary>
/// Gets a sequence of DataItems associated with the independent value.
/// </summary>
public IEnumerable<DataItem> DataItems { get; private set; }
/// <summary>
/// Gets or sets the denominator to use when computing with this instance.
/// </summary>
/// <remarks>
/// Exists here purely to simplify the the corresponding algorithm.
/// </remarks>
public double Denominator { get; set; }
}
/// <summary>
/// Represents a single data value from a SeriesDefinition's ItemsSource.
/// </summary>
protected class DataItem
{
/// <summary>
/// Stores a reference to a shared BindingHelper instance.
/// </summary>
private static readonly BindingHelper _bindingHelper = new BindingHelper();
/// <summary>
/// Initializes a new instance of the DataItem class.
/// </summary>
/// <param name="seriesDefinition">SeriesDefinition owner.</param>
public DataItem(SeriesDefinition seriesDefinition)
{
SeriesDefinition = seriesDefinition;
CenterPoint = new Point(double.NaN, double.NaN);
}
/// <summary>
/// Gets the SeriesDefinition owner of the DataItem.
/// </summary>
public SeriesDefinition SeriesDefinition { get; private set; }
/// <summary>
/// Gets or sets the value of the DataItem.
/// </summary>
public object Value
{
get { return _value; }
set
{
_value = value;
if (null != DataPoint)
{
DataPoint.DataContext = value;
}
}
}
/// <summary>
/// Stores the value of the DataItem.
/// </summary>
private object _value;
/// <summary>
/// Gets or sets the index of the DataItem.
/// </summary>
public int Index { get; set; }
/// <summary>
/// Gets or sets the DataPoint associated with the DataItem.
/// </summary>
public DataPoint DataPoint { get; set; }
/// <summary>
/// Gets or sets the container for the DataPoint within its parent ItemsControl.
/// </summary>
public UIElement Container { get; set; }
/// <summary>
/// Gets the ActualDependentValue of the DataPoint (or its equivalent).
/// </summary>
public IComparable ActualDependentValue
{
get
{
if (null != DataPoint)
{
return DataPoint.ActualDependentValue;
}
else
{
return (IComparable)_bindingHelper.EvaluateBinding(SeriesDefinition.DependentValueBinding, Value);
}
}
}
/// <summary>
/// Gets the ActualIndependentValue of the DataPoint (or its equivalent).
/// </summary>
public object ActualIndependentValue
{
get
{
if (null != DataPoint)
{
return DataPoint.ActualIndependentValue;
}
else
{
return _bindingHelper.EvaluateBinding(SeriesDefinition.IndependentValueBinding, Value);
}
}
}
/// <summary>
/// Gets or sets the ActualDependentValue of the DataPoint after adjusting for applicable stacking.
/// </summary>
public double ActualStackedDependentValue { get; set; }
/// <summary>
/// Gets or sets the center-point of the DataPoint in plot area coordinates (if relevant).
/// </summary>
public Point CenterPoint { get; set; }
}
/// <summary>
/// Provides an easy way to evaluate a Binding against a source instance.
/// </summary>
private class BindingHelper : FrameworkElement
{
/// <summary>
/// Initializes a new instance of the BindingHelper class.
/// </summary>
public BindingHelper()
{
}
/// <summary>
/// Identifies the Result dependency property.
/// </summary>
private static readonly DependencyProperty ResultProperty =
DependencyProperty.Register("Result", typeof(object), typeof(BindingHelper), null);
/// <summary>
/// Evaluates a Binding against a source instance.
/// </summary>
/// <param name="binding">Binding to evaluate.</param>
/// <param name="instance">Source instance.</param>
/// <returns>Result of Binding on source instance.</returns>
public object EvaluateBinding(Binding binding, object instance)
{
DataContext = instance;
SetBinding(ResultProperty, binding);
object result = GetValue(ResultProperty);
ClearValue(ResultProperty);
DataContext = null;
return result;
}
}
/// <summary>
/// Converts from a selected item to the corresponding DataItem.
/// </summary>
private class SelectedItemToDataItemConverter : IValueConverter
{
/// <summary>
/// Stores a reference to the DataItem collection.
/// </summary>
private ObservableCollection<DataItem> _dataItems;
/// <summary>
/// Initializes a new instance of the SelectedItemToDataItemConverter class.
/// </summary>
/// <param name="dataItems">Collection of DataItems.</param>
public SelectedItemToDataItemConverter(ObservableCollection<DataItem> dataItems)
{
_dataItems = dataItems;
}
/// <summary>
/// Converts a value.
/// </summary>
/// <param name="value">The value produced by the binding source.</param>
/// <param name="targetType">The type of the binding target property.</param>
/// <param name="parameter">The converter parameter to use.</param>
/// <param name="culture">The culture to use in the converter.</param>
/// <returns>Converted value.</returns>
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return _dataItems.Where(di => di.Value == value).FirstOrDefault();
}
/// <summary>
/// Converts a value back.
/// </summary>
/// <param name="value">The value produced by the binding source.</param>
/// <param name="targetType">The type of the binding target property.</param>
/// <param name="parameter">The converter parameter to use.</param>
/// <param name="culture">The culture to use in the converter.</param>
/// <returns>Converted value.</returns>
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
DataItem dataItem = value as DataItem;
return (null != dataItem) ? dataItem.Value : null;
}
}
/// <summary>
/// Converts from a SeriesSelectionMode to a true/false value indicating whether selection is enabled.
/// </summary>
private class SelectionModeToSelectionEnabledConverter : IValueConverter
{
/// <summary>
/// Initializes a new instance of the SelectionModeToSelectionEnabledConverter class.
/// </summary>
public SelectionModeToSelectionEnabledConverter()
{
}
/// <summary>
/// Converts a value.
/// </summary>
/// <param name="value">The value produced by the binding source.</param>
/// <param name="targetType">The type of the binding target property.</param>
/// <param name="parameter">The converter parameter to use.</param>
/// <param name="culture">The culture to use in the converter.</param>
/// <returns>Converted value.</returns>
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool isSelectionEnabled = false;
if (value is SeriesSelectionMode)
{
isSelectionEnabled = !(SeriesSelectionMode.None == (SeriesSelectionMode)value);
}
return isSelectionEnabled;
}
/// <summary>
/// Converts a value back.
/// </summary>
/// <param name="value">The value produced by the binding source.</param>
/// <param name="targetType">The type of the binding target property.</param>
/// <param name="parameter">The converter parameter to use.</param>
/// <param name="culture">The culture to use in the converter.</param>
/// <returns>Converted value.</returns>
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
/// <summary>
/// Gets the axes for the series as a series host.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations", Justification = "Property exists as an interface requirement; implementation is unnecessary.")]
ObservableCollection<IAxis> ISeriesHost.Axes
{
get { throw new NotImplementedException(); }
}
/// <summary>
/// Gets the series for the series as a series host.
/// </summary>
ObservableCollection<ISeries> ISeriesHost.Series
{
get { return _seriesDefinitionsAsISeries; }
}
/// <summary>
/// Gets the foreground elements for the series as a series host.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations", Justification = "Property exists as an interface requirement; implementation is unnecessary.")]
ObservableCollection<UIElement> ISeriesHost.ForegroundElements
{
get { throw new NotImplementedException(); }
}
/// <summary>
/// Gets the background elements for the series as a series host.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations", Justification = "Property exists as an interface requirement; implementation is unnecessary.")]
ObservableCollection<UIElement> ISeriesHost.BackgroundElements
{
get { throw new NotImplementedException(); }
}
/// <summary>
/// Gets a IResourceDictionaryDispenser for the series as a series host.
/// </summary>
/// <param name="predicate">Predicate function.</param>
/// <returns>Sequence of ResourceDictionaries.</returns>
IEnumerator<ResourceDictionary> IResourceDictionaryDispenser.GetResourceDictionariesWhere(Func<ResourceDictionary, bool> predicate)
{
throw new NotImplementedException();
}
/// <summary>
/// Event that is triggered when the available ResourceDictionaries change.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations", Justification = "Property exists as an interface requirement; implementation is unnecessary.")]
event EventHandler IResourceDictionaryDispenser.ResourceDictionariesChanged
{
add { throw new NotImplementedException(); }
remove { throw new NotImplementedException(); }
}
}
}