diff --git a/ExtendedWPFToolkitSolution/ExtendedWPFToolkit.sln b/ExtendedWPFToolkitSolution/ExtendedWPFToolkit.sln index ffc559c3..aa629841 100644 --- a/ExtendedWPFToolkitSolution/ExtendedWPFToolkit.sln +++ b/ExtendedWPFToolkitSolution/ExtendedWPFToolkit.sln @@ -7,9 +7,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WPFToolkit.Extended.Samples EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WPFToolkit.Extended.Design", "Src\WPFToolkit.Extended.Design\WPFToolkit.Extended.Design.csproj", "{FA6645C6-7CA5-427C-91F2-916D9FECD76C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WPFToolkit.Extended.DataVisualization", "Src\WPFToolkit.Extended.DataVisualization\WPFToolkit.Extended.DataVisualization.csproj", "{C921A4BC-1781-4A14-B1D0-C41E49C7606A}" +EndProject Global GlobalSection(TeamFoundationVersionControl) = preSolution - SccNumberOfProjects = 4 + SccNumberOfProjects = 5 SccEnterpriseProvider = {4CA58AB2-18FA-4F8D-95D4-32DDF27D184C} SccTeamFoundationServer = https://tfs.codeplex.com/tfs/tfs02 SccLocalPath0 = . @@ -22,6 +24,9 @@ Global SccProjectUniqueName3 = Src\\WPFToolkit.Extended.Design\\WPFToolkit.Extended.Design.csproj SccProjectName3 = Src/WPFToolkit.Extended.Design SccLocalPath3 = Src\\WPFToolkit.Extended.Design + SccProjectUniqueName4 = Src\\WPFToolkit.Extended.DataVisualization\\WPFToolkit.Extended.DataVisualization.csproj + SccProjectName4 = Src/WPFToolkit.Extended.DataVisualization + SccLocalPath4 = Src\\WPFToolkit.Extended.DataVisualization EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -62,6 +67,16 @@ Global {FA6645C6-7CA5-427C-91F2-916D9FECD76C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {FA6645C6-7CA5-427C-91F2-916D9FECD76C}.Release|Mixed Platforms.Build.0 = Release|Any CPU {FA6645C6-7CA5-427C-91F2-916D9FECD76C}.Release|x86.ActiveCfg = Release|Any CPU + {C921A4BC-1781-4A14-B1D0-C41E49C7606A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C921A4BC-1781-4A14-B1D0-C41E49C7606A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C921A4BC-1781-4A14-B1D0-C41E49C7606A}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {C921A4BC-1781-4A14-B1D0-C41E49C7606A}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {C921A4BC-1781-4A14-B1D0-C41E49C7606A}.Debug|x86.ActiveCfg = Debug|Any CPU + {C921A4BC-1781-4A14-B1D0-C41E49C7606A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C921A4BC-1781-4A14-B1D0-C41E49C7606A}.Release|Any CPU.Build.0 = Release|Any CPU + {C921A4BC-1781-4A14-B1D0-C41E49C7606A}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {C921A4BC-1781-4A14-B1D0-C41E49C7606A}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {C921A4BC-1781-4A14-B1D0-C41E49C7606A}.Release|x86.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/AggregatedObservableCollection.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/AggregatedObservableCollection.cs new file mode 100644 index 00000000..55a5cefe --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/AggregatedObservableCollection.cs @@ -0,0 +1,175 @@ +// (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.Linq; + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// Aggregated observable collection. + /// + /// The type of the items in the observable collections. + /// + internal class AggregatedObservableCollection : ReadOnlyObservableCollection + { + /// + /// Initializes a new instance of an aggregated observable collection. + /// + public AggregatedObservableCollection() + { + this.ChildCollections = new NoResetObservableCollection(); + this.ChildCollections.CollectionChanged += new NotifyCollectionChangedEventHandler(ChildCollectionsCollectionChanged); + } + + /// + /// Rebuilds the list if a collection changes. + /// + /// The source of the event. + /// Information about the event. + private void ChildCollectionsCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + Debug.Assert(e.Action != NotifyCollectionChangedAction.Reset, "Reset is not supported."); + + if (e.Action == NotifyCollectionChangedAction.Add) + { + e.NewItems + .OfType() + .ForEachWithIndex((newCollection, index) => + { + int startingIndex = GetStartingIndexOfCollectionAtIndex(e.NewStartingIndex + index); + foreach (T item in newCollection.OfType().Reverse()) + { + this.Mutate(items => items.Insert(startingIndex, item)); + } + + INotifyCollectionChanged notifyCollectionChanged = newCollection as INotifyCollectionChanged; + if (notifyCollectionChanged != null) + { + notifyCollectionChanged.CollectionChanged += ChildCollectionCollectionChanged; + } + }); + } + else if (e.Action == NotifyCollectionChangedAction.Remove) + { + foreach (IList oldCollection in e.OldItems) + { + INotifyCollectionChanged notifyCollectionChanged = oldCollection as INotifyCollectionChanged; + if (notifyCollectionChanged != null) + { + notifyCollectionChanged.CollectionChanged -= ChildCollectionCollectionChanged; + } + + foreach (T item in oldCollection) + { + this.Mutate(items => items.Remove(item)); + } + } + } + else if (e.Action == NotifyCollectionChangedAction.Replace) + { + foreach (IList oldCollection in e.OldItems) + { + INotifyCollectionChanged notifyCollectionChanged = oldCollection as INotifyCollectionChanged; + if (notifyCollectionChanged != null) + { + notifyCollectionChanged.CollectionChanged -= ChildCollectionCollectionChanged; + } + } + + foreach (IList newCollection in e.NewItems) + { + INotifyCollectionChanged notifyCollectionChanged = newCollection as INotifyCollectionChanged; + if (notifyCollectionChanged != null) + { + notifyCollectionChanged.CollectionChanged += ChildCollectionCollectionChanged; + } + } + + Rebuild(); + } + } + + /// + /// Synchronizes the collection with changes made in a child collection. + /// + /// The source of the event. + /// Information about the event. + private void ChildCollectionCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + Debug.Assert(e.Action != NotifyCollectionChangedAction.Reset, "Reset is not supported."); + IList collectionSender = sender as IList; + + if (e.Action == NotifyCollectionChangedAction.Add) + { + int startingIndex = GetStartingIndexOfCollectionAtIndex(ChildCollections.IndexOf(collectionSender)); + e.NewItems + .OfType() + .ForEachWithIndex((item, index) => + { + this.Mutate(that => that.Insert(startingIndex + e.NewStartingIndex + index, item)); + }); + } + else if (e.Action == NotifyCollectionChangedAction.Remove) + { + foreach (T item in e.OldItems.OfType()) + { + this.Mutate(that => that.Remove(item)); + } + } + else if (e.Action == NotifyCollectionChangedAction.Replace) + { + for (int cnt = 0; cnt < e.NewItems.Count; cnt++) + { + T oldItem = (T)e.OldItems[cnt]; + T newItem = (T)e.NewItems[cnt]; + int oldItemIndex = this.IndexOf(oldItem); + this.Mutate((that) => + { + that[oldItemIndex] = newItem; + }); + } + } + } + + /// + /// Returns the starting index of a collection in the aggregate + /// collection. + /// + /// The starting index of a collection. + /// The starting index of the collection in the aggregate + /// collection. + private int GetStartingIndexOfCollectionAtIndex(int index) + { + return ChildCollections.OfType().Select(collection => collection.CastWrapper()).Take(index).SelectMany(collection => collection).Count(); + } + + /// + /// Rebuild the list in the correct order when a child collection + /// changes. + /// + private void Rebuild() + { + this.Mutate(that => that.Clear()); + this.Mutate(that => + { + IList items = ChildCollections.OfType().Select(collection => collection.CastWrapper()).SelectMany(collection => collection).ToList(); + foreach (T item in items) + { + that.Add(item); + } + }); + } + + /// + /// Gets child collections of the aggregated collection. + /// + public ObservableCollection ChildCollections { get; private set; } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/AnimationSequence.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/AnimationSequence.cs new file mode 100644 index 00000000..ca2fb6a6 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/AnimationSequence.cs @@ -0,0 +1,29 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Specifies the supported animation sequences. + /// + /// Preview + public enum AnimationSequence + { + /// + /// Animates all of the data points simultaneously. + /// + Simultaneous = 0, + + /// + /// Animates the data points from first to last. + /// + FirstToLast = 1, + + /// + /// Animates the data points from last to first. + /// + LastToFirst = 2 + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/Axis.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/Axis.cs new file mode 100644 index 00000000..cc6fbce6 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/Axis.cs @@ -0,0 +1,237 @@ +// (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; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Windows; +using System.Windows.Controls; +using System.Collections.ObjectModel; +using System.Collections.Specialized; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// An axis class used to determine the plot area coordinate of values. + /// + public abstract class Axis : Control, IAxis + { + #region public AxisLocation Location + /// + /// Gets or sets the axis location. + /// + public AxisLocation Location + { + get { return (AxisLocation)GetValue(LocationProperty); } + set { SetValue(LocationProperty, value); } + } + + /// + /// Identifies the Location dependency property. + /// + public static readonly DependencyProperty LocationProperty = + DependencyProperty.Register( + "Location", + typeof(AxisLocation), + typeof(Axis), + new PropertyMetadata(AxisLocation.Auto, OnLocationPropertyChanged)); + + /// + /// LocationProperty property changed handler. + /// + /// Axis that changed its Location. + /// Event arguments. + private static void OnLocationPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + Axis source = (Axis)d; + AxisLocation oldValue = (AxisLocation)e.OldValue; + AxisLocation newValue = (AxisLocation)e.NewValue; + source.OnLocationPropertyChanged(oldValue, newValue); + } + + /// + /// LocationProperty property changed handler. + /// + /// Old value. + /// New value. + protected virtual void OnLocationPropertyChanged(AxisLocation oldValue, AxisLocation newValue) + { + RoutedPropertyChangedEventHandler handler = this.LocationChanged; + if (handler != null) + { + handler(this, new RoutedPropertyChangedEventArgs(oldValue, newValue)); + } + } + + /// + /// This event is raised when the location property is changed. + /// + public event RoutedPropertyChangedEventHandler LocationChanged; + + #endregion public AxisLocation Location + + /// + /// Gets the list of child axes belonging to this axis. + /// + public ObservableCollection DependentAxes { get; private set; } + + #region public AxisOrientation Orientation + /// + /// Gets or sets the orientation of the axis. + /// + public AxisOrientation Orientation + { + get { return (AxisOrientation)GetValue(OrientationProperty); } + set { SetValue(OrientationProperty, value); } + } + + /// + /// Identifies the Orientation dependency property. + /// + public static readonly DependencyProperty OrientationProperty = + DependencyProperty.Register( + "Orientation", + typeof(AxisOrientation), + typeof(Axis), + new PropertyMetadata(AxisOrientation.None, OnOrientationPropertyChanged)); + + /// + /// OrientationProperty property changed handler. + /// + /// Axis that changed its Orientation. + /// Event arguments. + private static void OnOrientationPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + Axis source = (Axis)d; + AxisOrientation oldValue = (AxisOrientation)e.OldValue; + AxisOrientation newValue = (AxisOrientation)e.NewValue; + source.OnOrientationPropertyChanged(oldValue, newValue); + } + + /// + /// OrientationProperty property changed handler. + /// + /// Old value. + /// New value. + protected virtual void OnOrientationPropertyChanged(AxisOrientation oldValue, AxisOrientation newValue) + { + RoutedPropertyChangedEventHandler handler = OrientationChanged; + if (handler != null) + { + handler(this, new RoutedPropertyChangedEventArgs(oldValue, newValue)); + } + } + + /// + /// This event is raised when the Orientation property is changed. + /// + public event RoutedPropertyChangedEventHandler OrientationChanged; + + #endregion public AxisOrientation Orientation + + /// + /// Raises the invalidated event. + /// + /// Information about the event. + protected virtual void OnInvalidated(RoutedEventArgs args) + { + foreach (IAxisListener listener in RegisteredListeners) + { + listener.AxisInvalidated(this); + } + } + + /// + /// Gets or the collection of series that are using the Axis. + /// + public ObservableCollection RegisteredListeners { get; private set; } + + /// + /// Returns a value indicating whether the axis can plot a value. + /// + /// The value to plot. + /// A value indicating whether the axis can plot a value. + /// + public abstract bool CanPlot(object value); + + /// + /// The plot area coordinate of a value. + /// + /// The value for which to retrieve the plot area + /// coordinate. + /// The plot area coordinate. + public abstract UnitValue GetPlotAreaCoordinate(object value); + + /// + /// Instantiates a new instance of the Axis class. + /// + protected Axis() + { + RegisteredListeners = new UniqueObservableCollection(); + this.RegisteredListeners.CollectionChanged += RegisteredListenersCollectionChanged; + this.DependentAxes = new ObservableCollection(); + this.DependentAxes.CollectionChanged += OnChildAxesCollectionChanged; + } + + /// + /// Child axes collection changed. + /// + /// The source of the event. + /// Information about the event. + private void OnChildAxesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + this.OnDependentAxesCollectionChanged(); + } + + /// + /// Child axes collection changed. + /// + protected virtual void OnDependentAxesCollectionChanged() + { + } + + /// + /// This event is raised when the registered listeners collection is + /// changed. + /// + /// The source of the event. + /// Information about the event. + private void RegisteredListenersCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + if (e.OldItems != null) + { + foreach (IAxisListener obj in e.OldItems) + { + OnObjectUnregistered(obj); + } + } + if (e.NewItems != null) + { + foreach (IAxisListener obj in e.NewItems) + { + OnObjectRegistered(obj); + } + } + } + + /// + /// This method is invoked when a series is registered. + /// + /// The series that has been registered. + protected virtual void OnObjectRegistered(IAxisListener series) + { + } + + /// + /// This method is invoked when a series is unregistered. + /// + /// The series that has been unregistered. + protected virtual void OnObjectUnregistered(IAxisListener series) + { + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/AxisIntervalType.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/AxisIntervalType.cs new file mode 100644 index 00000000..301d3e13 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/AxisIntervalType.cs @@ -0,0 +1,64 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Specifies an interval type. + /// + /// Preview + internal enum AxisIntervalType + { + /// + /// Automatically determined by the ISeriesHost control. + /// + Auto = 0, + + /// + /// The interval type is numerical. + /// + Number = 1, + + /// + /// The interval type is years. + /// + Years = 2, + + /// + /// The interval type is months. + /// + Months = 3, + + /// + /// The interval type is weeks. + /// + Weeks = 4, + + /// + /// The interval type is days. + /// + Days = 5, + + /// + /// The interval type is hours. + /// + Hours = 6, + + /// + /// The interval type is minutes. + /// + Minutes = 7, + + /// + /// The interval type is seconds. + /// + Seconds = 8, + + /// + /// The interval type is milliseconds. + /// + Milliseconds = 9, + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/AxisLabel.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/AxisLabel.cs new file mode 100644 index 00000000..75ac6f65 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/AxisLabel.cs @@ -0,0 +1,112 @@ +// (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.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// A label used to display data in an axis. + /// + public class AxisLabel : Control + { + #region public string StringFormat + /// + /// Gets or sets the text string format. + /// + public string StringFormat + { + get { return GetValue(StringFormatProperty) as string; } + set { SetValue(StringFormatProperty, value); } + } + + /// + /// Identifies the StringFormat dependency property. + /// + public static readonly DependencyProperty StringFormatProperty = + DependencyProperty.Register( + "StringFormat", + typeof(string), + typeof(AxisLabel), + new PropertyMetadata(null, OnStringFormatPropertyChanged)); + + /// + /// StringFormatProperty property changed handler. + /// + /// AxisLabel that changed its StringFormat. + /// Event arguments. + private static void OnStringFormatPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + AxisLabel source = (AxisLabel)d; + string newValue = (string)e.NewValue; + source.OnStringFormatPropertyChanged(newValue); + } + + /// + /// StringFormatProperty property changed handler. + /// + /// New value. + protected virtual void OnStringFormatPropertyChanged(string newValue) + { + UpdateFormattedContent(); + } + #endregion public string StringFormat + + #region public string FormattedContent + /// + /// Gets the formatted content property. + /// + public string FormattedContent + { + get { return GetValue(FormattedContentProperty) as string; } + protected set { SetValue(FormattedContentProperty, value); } + } + + /// + /// Identifies the FormattedContent dependency property. + /// + public static readonly DependencyProperty FormattedContentProperty = + DependencyProperty.Register( + "FormattedContent", + typeof(string), + typeof(AxisLabel), + new PropertyMetadata(null)); + #endregion public string FormattedContent + +#if !SILVERLIGHT + /// + /// Initializes the static members of the AxisLabel class. + /// + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Dependency properties are initialized in-line.")] + static AxisLabel() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(AxisLabel), new FrameworkPropertyMetadata(typeof(AxisLabel))); + } + +#endif + /// + /// Instantiates a new instance of the AxisLabel class. + /// + public AxisLabel() + { +#if SILVERLIGHT + this.DefaultStyleKey = typeof(AxisLabel); +#endif + this.SetBinding(FormattedContentProperty, new Binding { Converter = new StringFormatConverter(), ConverterParameter = StringFormat ?? "{0}" }); + } + + /// + /// Updates the formatted text. + /// + protected virtual void UpdateFormattedContent() + { + this.SetBinding(FormattedContentProperty, new Binding { Converter = new StringFormatConverter(), ConverterParameter = StringFormat ?? "{0}" }); + } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/AxisLocation.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/AxisLocation.cs new file mode 100644 index 00000000..cc3cf6db --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/AxisLocation.cs @@ -0,0 +1,38 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Axis position. + /// + public enum AxisLocation + { + /// + /// Location is determined automatically. + /// + Auto, + + /// + /// Left in the series host area. + /// + Left, + + /// + /// Top in the series host area. + /// + Top, + + /// + /// Right in the series host area. + /// + Right, + + /// + /// Bottom of the series host area. + /// + Bottom, + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/AxisOrientation.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/AxisOrientation.cs new file mode 100644 index 00000000..063dd267 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/AxisOrientation.cs @@ -0,0 +1,31 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Specifies the orientation of an axis. + /// + /// Preview + public enum AxisOrientation + { + /// + /// Orientation is automatically set. + /// + None, + + /// + /// Indicates the axis plots along the X axis. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "X", Justification = "X is the expected terminology.")] + X, + + /// + /// Indicates the axis plots along the Y axis. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Y", Justification = "Y is the expected terminology.")] + Y, + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/CategoryAxis.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/CategoryAxis.cs new file mode 100644 index 00000000..68cc3a93 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/CategoryAxis.cs @@ -0,0 +1,363 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Windows.Shapes; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// An axis that displays categories. + /// + [StyleTypedProperty(Property = "GridLineStyle", StyleTargetType = typeof(Line))] + [StyleTypedProperty(Property = "MajorTickMarkStyle", StyleTargetType = typeof(Line))] + [StyleTypedProperty(Property = "AxisLabelStyle", StyleTargetType = typeof(AxisLabel))] + [StyleTypedProperty(Property = "TitleStyle", StyleTargetType = typeof(Title))] + [TemplatePart(Name = AxisGridName, Type = typeof(Grid))] + [TemplatePart(Name = AxisTitleName, Type = typeof(Title))] + public class CategoryAxis : DisplayAxis, ICategoryAxis + { + /// + /// A pool of major tick marks. + /// + private ObjectPool _majorTickMarkPool; + + /// + /// A pool of labels. + /// + private ObjectPool _labelPool; + + #region public CategorySortOrder SortOrder + /// + /// Gets or sets the sort order used for the categories. + /// + public CategorySortOrder SortOrder + { + get { return (CategorySortOrder)GetValue(SortOrderProperty); } + set { SetValue(SortOrderProperty, value); } + } + + /// + /// Identifies the SortOrder dependency property. + /// + public static readonly DependencyProperty SortOrderProperty = + DependencyProperty.Register( + "SortOrder", + typeof(CategorySortOrder), + typeof(CategoryAxis), + new PropertyMetadata(CategorySortOrder.None, OnSortOrderPropertyChanged)); + + /// + /// SortOrderProperty property changed handler. + /// + /// CategoryAxis that changed its SortOrder. + /// Event arguments. + private static void OnSortOrderPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + CategoryAxis source = (CategoryAxis)d; + source.OnSortOrderPropertyChanged(); + } + + /// + /// SortOrderProperty property changed handler. + /// + private void OnSortOrderPropertyChanged() + { + Invalidate(); + } + #endregion public CategorySortOrder SortOrder + + /// + /// Gets or sets a list of categories to display. + /// + private IList Categories { get; set; } + + /// + /// Gets or sets the grid line coordinates to display. + /// + [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "GridLine", Justification = "This is the expected capitalization.")] + private IList GridLineCoordinatesToDisplay { get; set; } + + /// + /// Instantiates a new instance of the CategoryAxis class. + /// + public CategoryAxis() + { + this._labelPool = new ObjectPool(() => CreateAxisLabel()); + this._majorTickMarkPool = new ObjectPool(() => CreateMajorTickMark()); + this.Categories = new List(); + this.GridLineCoordinatesToDisplay = new List(); + } + + /// + /// Updates categories when a series is registered. + /// + /// The series to be registered. + protected override void OnObjectRegistered(IAxisListener series) + { + base.OnObjectRegistered(series); + if (series is IDataProvider) + { + UpdateCategories(); + } + } + + /// + /// Updates categories when a series is unregistered. + /// + /// The series to be unregistered. + protected override void OnObjectUnregistered(IAxisListener series) + { + base.OnObjectUnregistered(series); + if (series is IDataProvider) + { + UpdateCategories(); + } + } + + /// + /// Returns range of coordinates for a given category. + /// + /// The category to return the range for. + /// The range of coordinates corresponding to the category. + /// + public Range GetPlotAreaCoordinateRange(object category) + { + if (category == null) + { + throw new ArgumentNullException("category"); + } + int index = Categories.IndexOf(category); + if (index == -1) + { + return new Range(); + } + + if (Orientation == AxisOrientation.X || Orientation == AxisOrientation.Y) + { + double maximumLength = Math.Max(ActualLength - 1, 0); + double lower = (index * maximumLength) / Categories.Count; + double upper = ((index + 1) * maximumLength) / Categories.Count; + + if (Orientation == AxisOrientation.X) + { + return new Range(new UnitValue(lower, Unit.Pixels), new UnitValue(upper, Unit.Pixels)); + } + else if (Orientation == AxisOrientation.Y) + { + return new Range(new UnitValue(maximumLength - upper, Unit.Pixels), new UnitValue(maximumLength - lower, Unit.Pixels)); + } + } + else + { + double startingAngle = 270.0; + double angleOffset = 360 / this.Categories.Count; + double halfAngleOffset = angleOffset / 2.0; + int categoryIndex = this.Categories.IndexOf(category); + double angle = startingAngle + (categoryIndex * angleOffset); + + return new Range(new UnitValue(angle - halfAngleOffset, Unit.Degrees), new UnitValue(angle + halfAngleOffset, Unit.Degrees)); + } + + return new Range(); + } + + /// + /// Returns the category at a given coordinate. + /// + /// The plot area position. + /// The category at the given plot area position. + public object GetCategoryAtPosition(UnitValue position) + { + if (this.ActualLength == 0.0 || this.Categories.Count == 0) + { + return null; + } + if (position.Unit == Unit.Pixels) + { + double coordinate = position.Value; + int index = (int)Math.Floor(coordinate / (this.ActualLength / this.Categories.Count)); + if (index >= 0 && index < this.Categories.Count) + { + if (Orientation == AxisOrientation.X) + { + return this.Categories[index]; + } + else + { + return this.Categories[(this.Categories.Count - 1) - index]; + } + } + } + else + { + throw new NotImplementedException(); + } + return null; + } + + /// + /// Updates the categories in response to an update from a registered + /// axis data provider. + /// + /// The category axis information + /// provider. + /// A sequence of categories. + public void DataChanged(IDataProvider dataProvider, IEnumerable data) + { + UpdateCategories(); + } + + /// + /// Updates the list of categories. + /// + private void UpdateCategories() + { + IEnumerable categories = + this.RegisteredListeners + .OfType() + .SelectMany(infoProvider => infoProvider.GetData(this)) + .Distinct(); + + if (SortOrder == CategorySortOrder.Ascending) + { + categories = categories.OrderBy(category => category); + } + else if (SortOrder == CategorySortOrder.Descending) + { + categories = categories.OrderByDescending(category => category); + } + + this.Categories = categories.ToList(); + + Invalidate(); + } + + /// + /// Returns the major axis grid line coordinates. + /// + /// The available size. + /// A sequence of the major grid line coordinates. + protected override IEnumerable GetMajorGridLineCoordinates(Size availableSize) + { + return GridLineCoordinatesToDisplay; + } + + /// + /// The plot area coordinate of a value. + /// + /// The value for which to retrieve the plot area + /// coordinate. + /// The plot area coordinate. + public override UnitValue GetPlotAreaCoordinate(object value) + { + if (value == null) + { + throw new ArgumentNullException("value"); + } + + Range range = GetPlotAreaCoordinateRange(value); + if (range.HasData) + { + double minimum = range.Minimum.Value; + double maximum = range.Maximum.Value; + return new UnitValue(((maximum - minimum) / 2.0) + minimum, range.Minimum.Unit); + } + else + { + return UnitValue.NaN(); + } + } + + /// + /// Creates and prepares a new axis label. + /// + /// The axis label value. + /// The axis label content control. + private Control CreateAndPrepareAxisLabel(object value) + { + Control axisLabel = _labelPool.Next(); + PrepareAxisLabel(axisLabel, value); + return axisLabel; + } + + /// + /// Renders as an oriented axis. + /// + /// The available size. + private void RenderOriented(Size availableSize) + { + _labelPool.Reset(); + _majorTickMarkPool.Reset(); + + try + { + OrientedPanel.Children.Clear(); + this.GridLineCoordinatesToDisplay.Clear(); + + if (this.Categories.Count > 0) + { + double maximumLength = Math.Max(GetLength(availableSize) - 1, 0); + + Action placeTickMarkAt = + (pos) => + { + Line tickMark = _majorTickMarkPool.Next(); + OrientedPanel.SetCenterCoordinate(tickMark, pos); + OrientedPanel.SetPriority(tickMark, 0); + this.GridLineCoordinatesToDisplay.Add(new UnitValue(pos, Unit.Pixels)); + OrientedPanel.Children.Add(tickMark); + }; + + int index = 0; + int priority = 0; + + foreach (object category in Categories) + { + Control axisLabel = CreateAndPrepareAxisLabel(category); + double lower = ((index * maximumLength) / Categories.Count) + 0.5; + double upper = (((index + 1) * maximumLength) / Categories.Count) + 0.5; + placeTickMarkAt(lower); + OrientedPanel.SetCenterCoordinate(axisLabel, (lower + upper) / 2); + OrientedPanel.SetPriority(axisLabel, priority + 1); + OrientedPanel.Children.Add(axisLabel); + index++; + priority = (priority + 1) % 2; + } + placeTickMarkAt(maximumLength + 0.5); + } + } + finally + { + _labelPool.Done(); + _majorTickMarkPool.Done(); + } + } + + /// + /// Renders the axis labels, tick marks, and other visual elements. + /// + /// The available size. + protected override void Render(Size availableSize) + { + RenderOriented(availableSize); + } + + /// + /// Returns a value indicating whether a value can be plotted on the + /// axis. + /// + /// A value which may or may not be able to be + /// plotted. + /// A value indicating whether a value can be plotted on the + /// axis. + public override bool CanPlot(object value) + { + return true; + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/CategorySortOrder.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/CategorySortOrder.cs new file mode 100644 index 00000000..2e589fd2 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/CategorySortOrder.cs @@ -0,0 +1,28 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// The sort order to use when sorting categories. + /// + public enum CategorySortOrder + { + /// + /// No sort order. + /// + None, + + /// + /// Ascending sort order. + /// + Ascending, + + /// + /// Descending sort order. + /// + Descending + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/DateTimeAxis.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/DateTimeAxis.cs new file mode 100644 index 00000000..d14e4096 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/DateTimeAxis.cs @@ -0,0 +1,1014 @@ +// (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; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Shapes; +using EF = System.Windows.Controls.DataVisualization.EnumerableFunctions; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// An axis that displays numeric values. + /// + [StyleTypedProperty(Property = "GridLineStyle", StyleTargetType = typeof(Line))] + [StyleTypedProperty(Property = "MajorTickMarkStyle", StyleTargetType = typeof(Line))] + [StyleTypedProperty(Property = "MinorTickMarkStyle", StyleTargetType = typeof(Line))] + [StyleTypedProperty(Property = "AxisLabelStyle", StyleTargetType = typeof(DateTimeAxisLabel))] + [StyleTypedProperty(Property = "TitleStyle", StyleTargetType = typeof(Title))] + [TemplatePart(Name = AxisGridName, Type = typeof(Grid))] + [TemplatePart(Name = AxisTitleName, Type = typeof(Title))] + public class DateTimeAxis : RangeAxis + { + #region public DateTime? ActualMaximum + /// + /// Gets the actual maximum value plotted on the chart. + /// + public DateTime? ActualMaximum + { + get { return (DateTime?)GetValue(ActualMaximumProperty); } + private set { SetValue(ActualMaximumProperty, value); } + } + + /// + /// Identifies the ActualMaximum dependency property. + /// + public static readonly DependencyProperty ActualMaximumProperty = + DependencyProperty.Register( + "ActualMaximum", + typeof(DateTime?), + typeof(DateTimeAxis), + null); + #endregion public DateTime? ActualMaximum + + #region public DateTime? ActualMinimum + /// + /// Gets the actual maximum value plotted on the chart. + /// + public DateTime? ActualMinimum + { + get { return (DateTime?)GetValue(ActualMinimumProperty); } + private set { SetValue(ActualMinimumProperty, value); } + } + + /// + /// Identifies the ActualMinimum dependency property. + /// + public static readonly DependencyProperty ActualMinimumProperty = + DependencyProperty.Register( + "ActualMinimum", + typeof(DateTime?), + typeof(DateTimeAxis), + null); + #endregion public DateTime? ActualMinimum + + #region public DateTime? Maximum + /// + /// Gets or sets the maximum value plotted on the axis. + /// + [TypeConverter(typeof(NullableConverter))] + public DateTime? Maximum + { + get { return (DateTime?)GetValue(MaximumProperty); } + set { SetValue(MaximumProperty, value); } + } + + /// + /// Identifies the Maximum dependency property. + /// + public static readonly DependencyProperty MaximumProperty = + DependencyProperty.Register( + "Maximum", + typeof(DateTime?), + typeof(DateTimeAxis), + new PropertyMetadata(null, OnMaximumPropertyChanged)); + + /// + /// MaximumProperty property changed handler. + /// + /// DateTimeAxis2 that changed its Maximum. + /// Event arguments. + private static void OnMaximumPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DateTimeAxis source = (DateTimeAxis)d; + DateTime? newValue = (DateTime?)e.NewValue; + source.OnMaximumPropertyChanged(newValue); + } + + /// + /// MaximumProperty property changed handler. + /// + /// New value. + private void OnMaximumPropertyChanged(DateTime? newValue) + { + this.ProtectedMaximum = newValue; + } + #endregion public DateTime? Maximum + + #region public DateTime? Minimum + /// + /// Gets or sets the minimum value to plot on the axis. + /// + [TypeConverter(typeof(NullableConverter))] + public DateTime? Minimum + { + get { return (DateTime?)GetValue(MinimumProperty); } + set { SetValue(MinimumProperty, value); } + } + + /// + /// Identifies the Minimum dependency property. + /// + public static readonly DependencyProperty MinimumProperty = + DependencyProperty.Register( + "Minimum", + typeof(DateTime?), + typeof(DateTimeAxis), + new PropertyMetadata(null, OnMinimumPropertyChanged)); + + /// + /// MinimumProperty property changed handler. + /// + /// DateTimeAxis2 that changed its Minimum. + /// Event arguments. + private static void OnMinimumPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DateTimeAxis source = (DateTimeAxis)d; + DateTime? newValue = (DateTime?)e.NewValue; + source.OnMinimumPropertyChanged(newValue); + } + + /// + /// MinimumProperty property changed handler. + /// + /// New value. + private void OnMinimumPropertyChanged(DateTime? newValue) + { + this.ProtectedMinimum = newValue; + } + #endregion public DateTime? Minimum + + #region public double? Interval + /// + /// Gets or sets the axis interval. + /// + [TypeConverter(typeof(NullableConverter))] + public double? Interval + { + get { return (double?)GetValue(IntervalProperty); } + set { SetValue(IntervalProperty, value); } + } + + /// + /// Identifies the Interval dependency property. + /// + public static readonly DependencyProperty IntervalProperty = + DependencyProperty.Register( + "Interval", + typeof(double?), + typeof(DateTimeAxis), + new PropertyMetadata(null, OnIntervalPropertyChanged)); + + /// + /// IntervalProperty property changed handler. + /// + /// DateTimeAxis2 that changed its Interval. + /// Event arguments. + private static void OnIntervalPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DateTimeAxis source = (DateTimeAxis)d; + source.OnIntervalPropertyChanged(); + } + + /// + /// IntervalProperty property changed handler. + /// + private void OnIntervalPropertyChanged() + { + Invalidate(); + } + #endregion public double? Interval + + #region public double ActualInterval + /// + /// Gets the actual interval. + /// + public double ActualInterval + { + get { return (double)GetValue(ActualIntervalProperty); } + private set { SetValue(ActualIntervalProperty, value); } + } + + /// + /// Identifies the ActualInterval dependency property. + /// + public static readonly DependencyProperty ActualIntervalProperty = + DependencyProperty.Register( + "ActualInterval", + typeof(double), + typeof(DateTimeAxis), + new PropertyMetadata(double.NaN)); + + #endregion public double ActualInterval + + #region public DateTimeIntervalType IntervalType + /// + /// Gets or sets the interval to use for the axis. + /// + public DateTimeIntervalType IntervalType + { + get { return (DateTimeIntervalType)GetValue(IntervalTypeProperty); } + set { SetValue(IntervalTypeProperty, value); } + } + + /// + /// Identifies the InternalIntervalType dependency property. + /// + public static readonly DependencyProperty IntervalTypeProperty = + DependencyProperty.Register( + "IntervalType", + typeof(DateTimeIntervalType), + typeof(DateTimeAxis), + new PropertyMetadata(DateTimeIntervalType.Auto, OnIntervalTypePropertyChanged)); + + /// + /// IntervalTypeProperty property changed handler. + /// + /// DateTimeAxis that changed its InternalIntervalType. + /// Event arguments. + private static void OnIntervalTypePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DateTimeAxis source = (DateTimeAxis)d; + DateTimeIntervalType newValue = (DateTimeIntervalType)e.NewValue; + source.OnIntervalTypePropertyChanged(newValue); + } + + /// + /// IntervalTypeProperty property changed handler. + /// + /// New value. + private void OnIntervalTypePropertyChanged(DateTimeIntervalType newValue) + { + this.ActualIntervalType = newValue; + Invalidate(); + } + #endregion public DateTimeIntervalType IntervalType + + #region public DateTimeIntervalType ActualIntervalType + /// + /// Gets or sets the actual interval type. + /// + private DateTimeIntervalType ActualIntervalType + { + get { return (DateTimeIntervalType)GetValue(ActualIntervalTypeProperty); } + set { SetValue(ActualIntervalTypeProperty, value); } + } + + /// + /// Identifies the ActualIntervalType dependency property. + /// + private static readonly DependencyProperty ActualIntervalTypeProperty = + DependencyProperty.Register( + "ActualIntervalType", + typeof(DateTimeIntervalType), + typeof(DateTimeAxis), + new PropertyMetadata(DateTimeIntervalType.Auto)); + + #endregion public DateTimeIntervalType ActualIntervalType + + /// + /// Gets the origin value on the axis. + /// + protected override IComparable Origin + { + get { return null; } + } + + /// + /// Instantiates a new instance of the DateTimeAxis2 class. + /// + public DateTimeAxis() + { + int year = DateTime.Now.Year; + this.ActualRange = new Range(new DateTime(year, 1, 1), new DateTime(year + 1, 1, 1)); + } + + /// + /// Creates a new instance of the DateTimeAxisLabel class. + /// + /// Returns a new instance of the DateTimeAxisLabel class. + /// + protected override Control CreateAxisLabel() + { + return new DateTimeAxisLabel(); + } + + /// + /// Prepares an instance of the DateTimeAxisLabel class by setting its + /// IntervalType property. + /// + /// An instance of the DateTimeAxisLabel class. + /// + /// The data context to assign to the label. + /// + protected override void PrepareAxisLabel(Control label, object dataContext) + { + DateTimeAxisLabel dateTimeAxisLabel = label as DateTimeAxisLabel; + + if (dateTimeAxisLabel != null) + { + dateTimeAxisLabel.IntervalType = ActualIntervalType; + } + base.PrepareAxisLabel(label, dataContext); + } + + /// + /// Gets the actual range of DateTime values. + /// + protected Range ActualDateTimeRange { get; private set; } + + /// + /// Updates the typed actual maximum and minimum properties when the + /// actual range changes. + /// + /// The actual range. + protected override void OnActualRangeChanged(Range range) + { + ActualDateTimeRange = range.ToDateTimeRange(); + + if (range.HasData) + { + this.ActualMaximum = (DateTime)range.Maximum; + this.ActualMinimum = (DateTime)range.Minimum; + } + else + { + this.ActualMaximum = null; + this.ActualMinimum = null; + } + + base.OnActualRangeChanged(range); + } + + /// + /// Returns a value indicating whether a value can plot. + /// + /// The value to plot. + /// A value indicating whether a value can plot. + public override bool CanPlot(object value) + { + DateTime val; + return ValueHelper.TryConvert(value, out val); + } + + /// + /// Returns the plot area coordinate of a value. + /// + /// The value to plot. + /// The length of the axis. + /// The plot area coordinate of a value. + protected override UnitValue GetPlotAreaCoordinate(object value, double length) + { + return GetPlotAreaCoordinate(value, ActualDateTimeRange, length); + } + + /// + /// Returns the plot area coordinate of a value. + /// + /// The value to plot. + /// The range to use determine the coordinate. + /// The length of the axis. + /// The plot area coordinate of a value. + protected override UnitValue GetPlotAreaCoordinate(object value, Range currentRange, double length) + { + return GetPlotAreaCoordinate(value, currentRange.ToDateTimeRange(), length); + } + + /// + /// Returns the plot area coordinate of a value. + /// + /// The value to plot. + /// The range to use determine the coordinate. + /// The length of the axis. + /// The plot area coordinate of a value. + private static UnitValue GetPlotAreaCoordinate(object value, Range currentRange, double length) + { + if (currentRange.HasData) + { + DateTime dateTimeValue = ValueHelper.ToDateTime(value); + + double rangelength = currentRange.Maximum.ToOADate() - currentRange.Minimum.ToOADate(); + double pixelLength = Math.Max(length - 1, 0); + + return new UnitValue((dateTimeValue.ToOADate() - currentRange.Minimum.ToOADate()) * (pixelLength / rangelength), Unit.Pixels); + } + + return UnitValue.NaN(); + } + + /// + /// Returns the actual interval to use to determine which values are + /// displayed in the axis. + /// + /// The available size. + /// The actual interval to use to determine which values are + /// displayed in the axis. + /// + private double CalculateActualInterval(Size availableSize) + { + if (Interval != null) + { + return Interval.Value; + } + + DateTimeIntervalType intervalType; + double interval = CalculateDateTimeInterval(ActualDateTimeRange.Minimum, ActualDateTimeRange.Maximum, out intervalType, availableSize); + ActualIntervalType = intervalType; + return interval; + } + + /// + /// Returns a sequence of major values. + /// + /// The available size. + /// A sequence of major values. + protected virtual IEnumerable GetMajorAxisValues(Size availableSize) + { + if (!ActualRange.HasData || ValueHelper.Compare(ActualRange.Minimum, ActualRange.Maximum) == 0 || GetLength(availableSize) == 0.0) + { + yield break; + } + + this.ActualInterval = CalculateActualInterval(availableSize); + DateTime date = ActualDateTimeRange.Minimum; + + DateTime start = AlignIntervalStart(date, this.ActualInterval, ActualIntervalType); + while (start < date) + { + start = IncrementDateTime(start, this.ActualInterval); + } + + IEnumerable intermediateDates = + EnumerableFunctions + .Iterate(start, next => IncrementDateTime(next, this.ActualInterval)) + .TakeWhile(current => ActualDateTimeRange.Contains(current)); + + foreach (DateTime current in intermediateDates) + { + yield return current; + } + } + + /// + /// Returns a sequence of values to create major tick marks for. + /// + /// The available size. + /// A sequence of values to create major tick marks for. + /// + protected override IEnumerable GetMajorTickMarkValues(Size availableSize) + { + return GetMajorAxisValues(availableSize).CastWrapper(); + } + + /// + /// Returns a sequence of values to plot on the axis. + /// + /// The available size. + /// A sequence of values to plot on the axis. + protected override IEnumerable GetLabelValues(Size availableSize) + { + return GetMajorAxisValues(availableSize).CastWrapper(); + } + + /// + /// This method accepts a date time and increments it. + /// + /// A date time. + /// The interval used to increment the date time. + /// + /// The new date time. + private DateTime IncrementDateTime(DateTime date, double interval) + { + DateTimeIntervalType intervalType = this.ActualIntervalType; + TimeSpan span = new TimeSpan(0); + DateTime result; + + if (intervalType == DateTimeIntervalType.Days) + { + span = TimeSpan.FromDays(interval); + } + else if (intervalType == DateTimeIntervalType.Hours) + { + span = TimeSpan.FromHours(interval); + } + else if (intervalType == DateTimeIntervalType.Milliseconds) + { + span = TimeSpan.FromMilliseconds(interval); + } + else if (intervalType == DateTimeIntervalType.Seconds) + { + span = TimeSpan.FromSeconds(interval); + } + else if (intervalType == DateTimeIntervalType.Minutes) + { + span = TimeSpan.FromMinutes(interval); + } + else if (intervalType == DateTimeIntervalType.Weeks) + { + span = TimeSpan.FromDays(7.0 * interval); + } + else if (intervalType == DateTimeIntervalType.Months) + { + // Special case handling when current date point + // to the last day of the month + bool lastMonthDay = false; + if (date.Day == DateTime.DaysInMonth(date.Year, date.Month)) + { + lastMonthDay = true; + } + + // Add specified amount of months + date = date.AddMonths((int)Math.Floor(interval)); + span = TimeSpan.FromDays(30.0 * (interval - Math.Floor(interval))); + + // Check if last month of the day was used + if (lastMonthDay && span.Ticks == 0) + { + // Make sure the last day of the month is selected + int daysInMobth = DateTime.DaysInMonth(date.Year, date.Month); + date = date.AddDays(daysInMobth - date.Day); + } + } + else if (intervalType == DateTimeIntervalType.Years) + { + date = date.AddYears((int)Math.Floor(interval)); + span = TimeSpan.FromDays(365.0 * (interval - Math.Floor(interval))); + } + + result = date.Add(span); + + return result; + } + + /// + /// Adjusts the beginning of the first interval depending on the type and size. + /// + /// Original start point. + /// Interval size. + /// Type of the interval (Month, Year, ...). + /// + /// Adjusted interval start position. + /// + private static DateTime AlignIntervalStart(DateTime start, double intervalSize, DateTimeIntervalType type) + { + // Do not adjust start position for these interval type + if (type == DateTimeIntervalType.Auto) + { + return start; + } + + // Get the beginning of the interval depending on type + DateTime newStartDate = start; + + // Adjust the months interval depending on size + if (intervalSize > 0.0 && intervalSize != 1.0) + { + if (type == DateTimeIntervalType.Months && intervalSize <= 12.0 && intervalSize > 1) + { + // Make sure that the beginning is aligned correctly for cases + // like quarters and half years + DateTime resultDate = newStartDate; + DateTime sizeAdjustedDate = new DateTime(newStartDate.Year, 1, 1, 0, 0, 0); + while (sizeAdjustedDate < newStartDate) + { + resultDate = sizeAdjustedDate; + sizeAdjustedDate = sizeAdjustedDate.AddMonths((int)intervalSize); + } + + newStartDate = resultDate; + return newStartDate; + } + } + + // Check interval type + switch (type) + { + case DateTimeIntervalType.Years: + int year = (int)((int)(newStartDate.Year / intervalSize) * intervalSize); + if (year <= 0) + { + year = 1; + } + newStartDate = new DateTime(year, 1, 1, 0, 0, 0); + break; + + case DateTimeIntervalType.Months: + int month = (int)((int)(newStartDate.Month / intervalSize) * intervalSize); + if (month <= 0) + { + month = 1; + } + newStartDate = new DateTime(newStartDate.Year, month, 1, 0, 0, 0); + break; + + case DateTimeIntervalType.Days: + int day = (int)((int)(newStartDate.Day / intervalSize) * intervalSize); + if (day <= 0) + { + day = 1; + } + newStartDate = new DateTime(newStartDate.Year, newStartDate.Month, day, 0, 0, 0); + break; + + case DateTimeIntervalType.Hours: + int hour = (int)((int)(newStartDate.Hour / intervalSize) * intervalSize); + newStartDate = new DateTime( + newStartDate.Year, + newStartDate.Month, + newStartDate.Day, + hour, + 0, + 0); + break; + + case DateTimeIntervalType.Minutes: + int minute = (int)((int)(newStartDate.Minute / intervalSize) * intervalSize); + newStartDate = new DateTime( + newStartDate.Year, + newStartDate.Month, + newStartDate.Day, + newStartDate.Hour, + minute, + 0); + break; + + case DateTimeIntervalType.Seconds: + int second = (int)((int)(newStartDate.Second / intervalSize) * intervalSize); + newStartDate = new DateTime( + newStartDate.Year, + newStartDate.Month, + newStartDate.Day, + newStartDate.Hour, + newStartDate.Minute, + second, + 0); + break; + + case DateTimeIntervalType.Milliseconds: + int milliseconds = (int)((int)(newStartDate.Millisecond / intervalSize) * intervalSize); + newStartDate = new DateTime( + newStartDate.Year, + newStartDate.Month, + newStartDate.Day, + newStartDate.Hour, + newStartDate.Minute, + newStartDate.Second, + milliseconds); + break; + + case DateTimeIntervalType.Weeks: + + // Elements that have interval set to weeks should be aligned to the + // nearest start of week no matter how many weeks is the interval. + newStartDate = new DateTime( + newStartDate.Year, + newStartDate.Month, + newStartDate.Day, + 0, + 0, + 0); + + newStartDate = newStartDate.AddDays(-((int)newStartDate.DayOfWeek)); + break; + } + + return newStartDate; + } + + /// + /// Returns the value range given a plot area coordinate. + /// + /// The position. + /// A range of values at that plot area coordinate. + protected override IComparable GetValueAtPosition(UnitValue value) + { + if (ActualRange.HasData && ActualLength != 0.0) + { + double coordinate = value.Value; + if (value.Unit == Unit.Pixels) + { + double minimumAsDouble = ActualDateTimeRange.Minimum.ToOADate(); + double rangelength = ActualDateTimeRange.Maximum.ToOADate() - minimumAsDouble; + DateTime output = DateTime.FromOADate((coordinate * (rangelength / ActualLength)) + minimumAsDouble); + + return output; + } + else + { + throw new NotImplementedException(); + } + } + + return null; + } + + /// + /// Recalculates a DateTime interval obtained from maximum and minimum. + /// + /// The minimum. + /// The maximum. + /// Date time interval type. + /// The available size. + /// Auto Interval. + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "The method should inspect all variations of time span (millisec to year) and contains long case. Otherwise is simple and readable.")] + private double CalculateDateTimeInterval(DateTime minimum, DateTime maximum, out DateTimeIntervalType type, Size availableSize) + { + DateTime dateTimeMin = minimum; + DateTime dateTimeMax = maximum; + TimeSpan timeSpan = dateTimeMax.Subtract(dateTimeMin); + + // this algorithm is designed to return close to 10 intervals. + // we need to align the time span for PrefferedNumberOfIntervals + double maxIntervals = Orientation == AxisOrientation.X ? MaximumAxisIntervalsPer200Pixels * 0.8 : MaximumAxisIntervalsPer200Pixels; + double rangeMultiplicator = GetLength(availableSize) / (200 * 10 / maxIntervals); + timeSpan = new TimeSpan((long)((double)timeSpan.Ticks / rangeMultiplicator)); + + // Minutes + double inter = timeSpan.TotalMinutes; + + // For Range less than 60 seconds interval is 5 sec + if (inter <= 1.0) + { + // Milli Seconds + double milliSeconds = timeSpan.TotalMilliseconds; + if (milliSeconds <= 10) + { + type = DateTimeIntervalType.Milliseconds; + return 1; + } + if (milliSeconds <= 50) + { + type = DateTimeIntervalType.Milliseconds; + return 4; + } + if (milliSeconds <= 200) + { + type = DateTimeIntervalType.Milliseconds; + return 20; + } + if (milliSeconds <= 500) + { + type = DateTimeIntervalType.Milliseconds; + return 50; + } + + // Seconds + double seconds = timeSpan.TotalSeconds; + + if (seconds <= 7) + { + type = DateTimeIntervalType.Seconds; + return 1; + } + else if (seconds <= 15) + { + type = DateTimeIntervalType.Seconds; + return 2; + } + else if (seconds <= 30) + { + type = DateTimeIntervalType.Seconds; + return 5; + } + else if (seconds <= 60) + { + type = DateTimeIntervalType.Seconds; + return 10; + } + } + else if (inter <= 2.0) + { + // For Range less than 120 seconds interval is 10 sec + type = DateTimeIntervalType.Seconds; + return 20; + } + else if (inter <= 3.0) + { + // For Range less than 180 seconds interval is 30 sec + type = DateTimeIntervalType.Seconds; + return 30; + } + else if (inter <= 10) + { + // For Range less than 10 minutes interval is 1 min + type = DateTimeIntervalType.Minutes; + return 1; + } + else if (inter <= 20) + { + // For Range less than 20 minutes interval is 1 min + type = DateTimeIntervalType.Minutes; + return 2; + } + else if (inter <= 60) + { + // For Range less than 60 minutes interval is 5 min + type = DateTimeIntervalType.Minutes; + return 5; + } + else if (inter <= 120) + { + // For Range less than 120 minutes interval is 10 min + type = DateTimeIntervalType.Minutes; + return 10; + } + else if (inter <= 180) + { + // For Range less than 180 minutes interval is 30 min + type = DateTimeIntervalType.Minutes; + return 30; + } + else if (inter <= 60 * 12) + { + // For Range less than 12 hours interval is 1 hour + type = DateTimeIntervalType.Hours; + return 1; + } + else if (inter <= 60 * 24) + { + // For Range less than 24 hours interval is 4 hour + type = DateTimeIntervalType.Hours; + return 4; + } + else if (inter <= 60 * 24 * 2) + { + // For Range less than 2 days interval is 6 hour + type = DateTimeIntervalType.Hours; + return 6; + } + else if (inter <= 60 * 24 * 3) + { + // For Range less than 3 days interval is 12 hour + type = DateTimeIntervalType.Hours; + return 12; + } + else if (inter <= 60 * 24 * 10) + { + // For Range less than 10 days interval is 1 day + type = DateTimeIntervalType.Days; + return 1; + } + else if (inter <= 60 * 24 * 20) + { + // For Range less than 20 days interval is 2 day + type = DateTimeIntervalType.Days; + return 2; + } + else if (inter <= 60 * 24 * 30) + { + // For Range less than 30 days interval is 3 day + type = DateTimeIntervalType.Days; + return 3; + } + else if (inter <= 60 * 24 * 30.5 * 2) + { + // For Range less than 2 months interval is 1 week + type = DateTimeIntervalType.Weeks; + return 1; + } + else if (inter <= 60 * 24 * 30.5 * 5) + { + // For Range less than 5 months interval is 2weeks + type = DateTimeIntervalType.Weeks; + return 2; + } + else if (inter <= 60 * 24 * 30.5 * 12) + { + // For Range less than 12 months interval is 1 month + type = DateTimeIntervalType.Months; + return 1; + } + else if (inter <= 60 * 24 * 30.5 * 24) + { + // For Range less than 24 months interval is 3 month + type = DateTimeIntervalType.Months; + return 3; + } + else if (inter <= 60 * 24 * 30.5 * 48) + { + // For Range less than 48 months interval is 6 months + type = DateTimeIntervalType.Months; + return 6; + } + + // For Range more than 48 months interval is year + type = DateTimeIntervalType.Years; + double years = inter / 60 / 24 / 365; + if (years < 5) + { + return 1; + } + else if (years < 10) + { + return 2; + } + + // Make a correction of the interval + return Math.Floor(years / 5); + } + + /// + /// Overrides the actual range to ensure that it is never set to an + /// empty range. + /// + /// The range to override. + /// The overridden range. + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "This method is very difficult to break up cleanly.")] + protected override Range OverrideDataRange(Range range) + { + Range overriddenActualRange = range; + + if (!overriddenActualRange.HasData) + { + int year = DateTime.Now.Year; + return new Range(new DateTime(year, 1, 1), new DateTime(year + 1, 1, 1)); + } + else if (ValueHelper.Compare(overriddenActualRange.Minimum, overriddenActualRange.Maximum) == 0) + { + DateTime minimum = ValueHelper.ToDateTime(overriddenActualRange.Minimum); + DateTime midpoint = ((DateTime.MinValue == minimum) ? DateTime.Now : minimum).Date; + return new Range(midpoint.AddMonths(-6), midpoint.AddMonths(6)); + } + + // ActualLength of 1.0 or less maps all points to the same coordinate + if (range.HasData && this.ActualLength > 1.0) + { + IList valueMargins = new List(); + foreach (ValueMargin valueMargin in + this.RegisteredListeners + .OfType() + .SelectMany(provider => provider.GetValueMargins(this))) + { + valueMargins.Add( + new ValueMarginCoordinateAndOverlap + { + ValueMargin = valueMargin, + }); + } + + if (valueMargins.Count > 0) + { + double maximumPixelMarginLength = + valueMargins + .Select(valueMargin => valueMargin.ValueMargin.LowMargin + valueMargin.ValueMargin.HighMargin) + .MaxOrNullable().Value; + + // Requested margin is larger than the axis so give up + // trying to find a range that will fit it. + if (maximumPixelMarginLength > this.ActualLength) + { + return range; + } + + Range currentRange = range.ToDateTimeRange(); + + // Ensure range is not empty. + if (currentRange.Minimum == currentRange.Maximum) + { + int year = DateTime.Now.Year; + currentRange = new Range(new DateTime(year, 1, 1), new DateTime(year + 1, 1, 1)); + } + + // priming the loop + double actualLength = this.ActualLength; + ValueMarginCoordinateAndOverlap maxLeftOverlapValueMargin; + ValueMarginCoordinateAndOverlap maxRightOverlapValueMargin; + UpdateValueMargins(valueMargins, currentRange.ToComparableRange()); + GetMaxLeftAndRightOverlap(valueMargins, out maxLeftOverlapValueMargin, out maxRightOverlapValueMargin); + + while (maxLeftOverlapValueMargin.LeftOverlap > 0 || maxRightOverlapValueMargin.RightOverlap > 0) + { + long unitOverPixels = currentRange.GetLength().Value.Ticks / ((long) actualLength); + DateTime newMinimum = new DateTime(currentRange.Minimum.Ticks - (long)((maxLeftOverlapValueMargin.LeftOverlap + 0.5) * unitOverPixels)); + DateTime newMaximum = new DateTime(currentRange.Maximum.Ticks + (long)((maxRightOverlapValueMargin.RightOverlap + 0.5) * unitOverPixels)); + + currentRange = new Range(newMinimum, newMaximum); + UpdateValueMargins(valueMargins, currentRange.ToComparableRange()); + GetMaxLeftAndRightOverlap(valueMargins, out maxLeftOverlapValueMargin, out maxRightOverlapValueMargin); + } + + return currentRange.ToComparableRange(); + } + } + + return range; + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/DateTimeAxisLabel.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/DateTimeAxisLabel.cs new file mode 100644 index 00000000..9157d83c --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/DateTimeAxisLabel.cs @@ -0,0 +1,449 @@ +// (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.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// An axis label for displaying DateTime values. + /// + public class DateTimeAxisLabel : AxisLabel + { + #region public DateTimeIntervalType IntervalType + /// + /// Gets or sets the interval type of the DateTimeAxis2. + /// + public DateTimeIntervalType IntervalType + { + get { return (DateTimeIntervalType)GetValue(IntervalTypeProperty); } + set { SetValue(IntervalTypeProperty, value); } + } + + /// + /// Identifies the IntervalType dependency property. + /// + public static readonly System.Windows.DependencyProperty IntervalTypeProperty = + System.Windows.DependencyProperty.Register( + "IntervalType", + typeof(DateTimeIntervalType), + typeof(DateTimeAxisLabel), + new System.Windows.PropertyMetadata(DateTimeIntervalType.Auto, OnIntervalTypePropertyChanged)); + + /// + /// IntervalTypeProperty property changed handler. + /// + /// DateTimeAxisLabel that changed its IntervalType. + /// Event arguments. + private static void OnIntervalTypePropertyChanged(System.Windows.DependencyObject d, System.Windows.DependencyPropertyChangedEventArgs e) + { + DateTimeAxisLabel source = (DateTimeAxisLabel)d; + DateTimeIntervalType oldValue = (DateTimeIntervalType)e.OldValue; + DateTimeIntervalType newValue = (DateTimeIntervalType)e.NewValue; + source.OnIntervalTypePropertyChanged(oldValue, newValue); + } + + /// + /// IntervalTypeProperty property changed handler. + /// + /// Old value. + /// New value. + protected virtual void OnIntervalTypePropertyChanged(DateTimeIntervalType oldValue, DateTimeIntervalType newValue) + { + UpdateFormattedContent(); + } + #endregion public DateTimeIntervalType IntervalType + + #region public string YearsIntervalStringFormat + /// + /// Gets or sets the format string to use when the interval is hours. + /// + public string YearsIntervalStringFormat + { + get { return GetValue(YearsIntervalStringFormatProperty) as string; } + set { SetValue(YearsIntervalStringFormatProperty, value); } + } + + /// + /// Identifies the YearsIntervalStringFormat dependency property. + /// + public static readonly System.Windows.DependencyProperty YearsIntervalStringFormatProperty = + System.Windows.DependencyProperty.Register( + "YearsIntervalStringFormat", + typeof(string), + typeof(DateTimeAxisLabel), + new System.Windows.PropertyMetadata(null, OnYearsIntervalStringFormatPropertyChanged)); + + /// + /// YearsIntervalStringFormatProperty property changed handler. + /// + /// DateTimeAxisLabel that changed its YearsIntervalStringFormat. + /// Event arguments. + private static void OnYearsIntervalStringFormatPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DateTimeAxisLabel source = (DateTimeAxisLabel)d; + source.OnYearsIntervalStringFormatPropertyChanged(); + } + + /// + /// YearsIntervalStringFormatProperty property changed handler. + /// + protected virtual void OnYearsIntervalStringFormatPropertyChanged() + { + UpdateFormattedContent(); + } + #endregion public string YearsIntervalStringFormat + + #region public string MonthsIntervalStringFormat + /// + /// Gets or sets the format string to use when the interval is hours. + /// + public string MonthsIntervalStringFormat + { + get { return GetValue(MonthsIntervalStringFormatProperty) as string; } + set { SetValue(MonthsIntervalStringFormatProperty, value); } + } + + /// + /// Identifies the MonthsIntervalStringFormat dependency property. + /// + public static readonly System.Windows.DependencyProperty MonthsIntervalStringFormatProperty = + System.Windows.DependencyProperty.Register( + "MonthsIntervalStringFormat", + typeof(string), + typeof(DateTimeAxisLabel), + new System.Windows.PropertyMetadata(null, OnMonthsIntervalStringFormatPropertyChanged)); + + /// + /// MonthsIntervalStringFormatProperty property changed handler. + /// + /// DateTimeAxisLabel that changed its MonthsIntervalStringFormat. + /// Event arguments. + private static void OnMonthsIntervalStringFormatPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DateTimeAxisLabel source = (DateTimeAxisLabel)d; + source.OnMonthsIntervalStringFormatPropertyChanged(); + } + + /// + /// MonthsIntervalStringFormatProperty property changed handler. + /// + protected virtual void OnMonthsIntervalStringFormatPropertyChanged() + { + UpdateFormattedContent(); + } + #endregion public string MonthsIntervalStringFormat + + #region public string WeeksIntervalStringFormat + /// + /// Gets or sets the format string to use when the interval is hours. + /// + public string WeeksIntervalStringFormat + { + get { return GetValue(WeeksIntervalStringFormatProperty) as string; } + set { SetValue(WeeksIntervalStringFormatProperty, value); } + } + + /// + /// Identifies the WeeksIntervalStringFormat dependency property. + /// + public static readonly System.Windows.DependencyProperty WeeksIntervalStringFormatProperty = + System.Windows.DependencyProperty.Register( + "WeeksIntervalStringFormat", + typeof(string), + typeof(DateTimeAxisLabel), + new System.Windows.PropertyMetadata(null, OnWeeksIntervalStringFormatPropertyChanged)); + + /// + /// WeeksIntervalStringFormatProperty property changed handler. + /// + /// DateTimeAxisLabel that changed its WeeksIntervalStringFormat. + /// Event arguments. + private static void OnWeeksIntervalStringFormatPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DateTimeAxisLabel source = (DateTimeAxisLabel)d; + source.OnWeeksIntervalStringFormatPropertyChanged(); + } + + /// + /// WeeksIntervalStringFormatProperty property changed handler. + /// + protected virtual void OnWeeksIntervalStringFormatPropertyChanged() + { + UpdateFormattedContent(); + } + #endregion public string WeeksIntervalStringFormat + + #region public string DaysIntervalStringFormat + /// + /// Gets or sets the format string to use when the interval is hours. + /// + public string DaysIntervalStringFormat + { + get { return GetValue(DaysIntervalStringFormatProperty) as string; } + set { SetValue(DaysIntervalStringFormatProperty, value); } + } + + /// + /// Identifies the DaysIntervalStringFormat dependency property. + /// + public static readonly System.Windows.DependencyProperty DaysIntervalStringFormatProperty = + System.Windows.DependencyProperty.Register( + "DaysIntervalStringFormat", + typeof(string), + typeof(DateTimeAxisLabel), + new System.Windows.PropertyMetadata(null, OnDaysIntervalStringFormatPropertyChanged)); + + /// + /// DaysIntervalStringFormatProperty property changed handler. + /// + /// DateTimeAxisLabel that changed its DaysIntervalStringFormat. + /// Event arguments. + private static void OnDaysIntervalStringFormatPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DateTimeAxisLabel source = (DateTimeAxisLabel)d; + source.OnDaysIntervalStringFormatPropertyChanged(); + } + + /// + /// DaysIntervalStringFormatProperty property changed handler. + /// + protected virtual void OnDaysIntervalStringFormatPropertyChanged() + { + UpdateFormattedContent(); + } + #endregion public string DaysIntervalStringFormat + + #region public string HoursIntervalStringFormat + /// + /// Gets or sets the format string to use when the interval is hours. + /// + public string HoursIntervalStringFormat + { + get { return GetValue(HoursIntervalStringFormatProperty) as string; } + set { SetValue(HoursIntervalStringFormatProperty, value); } + } + + /// + /// Identifies the HoursIntervalStringFormat dependency property. + /// + public static readonly System.Windows.DependencyProperty HoursIntervalStringFormatProperty = + System.Windows.DependencyProperty.Register( + "HoursIntervalStringFormat", + typeof(string), + typeof(DateTimeAxisLabel), + new System.Windows.PropertyMetadata(null, OnHoursIntervalStringFormatPropertyChanged)); + + /// + /// HoursIntervalStringFormatProperty property changed handler. + /// + /// DateTimeAxisLabel that changed its HoursIntervalStringFormat. + /// Event arguments. + private static void OnHoursIntervalStringFormatPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DateTimeAxisLabel source = (DateTimeAxisLabel)d; + source.OnHoursIntervalStringFormatPropertyChanged(); + } + + /// + /// HoursIntervalStringFormatProperty property changed handler. + /// + protected virtual void OnHoursIntervalStringFormatPropertyChanged() + { + UpdateFormattedContent(); + } + #endregion public string HoursIntervalStringFormat + + #region public string MinutesIntervalStringFormat + /// + /// Gets or sets the format string to use when the interval is hours. + /// + public string MinutesIntervalStringFormat + { + get { return GetValue(MinutesIntervalStringFormatProperty) as string; } + set { SetValue(MinutesIntervalStringFormatProperty, value); } + } + + /// + /// Identifies the MinutesIntervalStringFormat dependency property. + /// + public static readonly System.Windows.DependencyProperty MinutesIntervalStringFormatProperty = + System.Windows.DependencyProperty.Register( + "MinutesIntervalStringFormat", + typeof(string), + typeof(DateTimeAxisLabel), + new System.Windows.PropertyMetadata(null, OnMinutesIntervalStringFormatPropertyChanged)); + + /// + /// MinutesIntervalStringFormatProperty property changed handler. + /// + /// DateTimeAxisLabel that changed its MinutesIntervalStringFormat. + /// Event arguments. + private static void OnMinutesIntervalStringFormatPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DateTimeAxisLabel source = (DateTimeAxisLabel)d; + source.OnMinutesIntervalStringFormatPropertyChanged(); + } + + /// + /// MinutesIntervalStringFormatProperty property changed handler. + /// + protected virtual void OnMinutesIntervalStringFormatPropertyChanged() + { + UpdateFormattedContent(); + } + #endregion public string MinutesIntervalStringFormat + + #region public string SecondsIntervalStringFormat + /// + /// Gets or sets the format string to use when the interval is hours. + /// + public string SecondsIntervalStringFormat + { + get { return GetValue(SecondsIntervalStringFormatProperty) as string; } + set { SetValue(SecondsIntervalStringFormatProperty, value); } + } + + /// + /// Identifies the SecondsIntervalStringFormat dependency property. + /// + public static readonly System.Windows.DependencyProperty SecondsIntervalStringFormatProperty = + System.Windows.DependencyProperty.Register( + "SecondsIntervalStringFormat", + typeof(string), + typeof(DateTimeAxisLabel), + new System.Windows.PropertyMetadata(null, OnSecondsIntervalStringFormatPropertyChanged)); + + /// + /// SecondsIntervalStringFormatProperty property changed handler. + /// + /// DateTimeAxisLabel that changed its SecondsIntervalStringFormat. + /// Event arguments. + private static void OnSecondsIntervalStringFormatPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DateTimeAxisLabel source = (DateTimeAxisLabel)d; + source.OnSecondsIntervalStringFormatPropertyChanged(); + } + + /// + /// SecondsIntervalStringFormatProperty property changed handler. + /// + protected virtual void OnSecondsIntervalStringFormatPropertyChanged() + { + UpdateFormattedContent(); + } + #endregion public string SecondsIntervalStringFormat + + #region public string MillisecondsIntervalStringFormat + /// + /// Gets or sets the format string to use when the interval is hours. + /// + public string MillisecondsIntervalStringFormat + { + get { return GetValue(MillisecondsIntervalStringFormatProperty) as string; } + set { SetValue(MillisecondsIntervalStringFormatProperty, value); } + } + + /// + /// Identifies the MillisecondsIntervalStringFormat dependency property. + /// + public static readonly System.Windows.DependencyProperty MillisecondsIntervalStringFormatProperty = + System.Windows.DependencyProperty.Register( + "MillisecondsIntervalStringFormat", + typeof(string), + typeof(DateTimeAxisLabel), + new System.Windows.PropertyMetadata(null, OnMillisecondsIntervalStringFormatPropertyChanged)); + + /// + /// MillisecondsIntervalStringFormatProperty property changed handler. + /// + /// DateTimeAxisLabel that changed its MillisecondsIntervalStringFormat. + /// Event arguments. + private static void OnMillisecondsIntervalStringFormatPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DateTimeAxisLabel source = (DateTimeAxisLabel)d; + source.OnMillisecondsIntervalStringFormatPropertyChanged(); + } + + /// + /// MillisecondsIntervalStringFormatProperty property changed handler. + /// + protected virtual void OnMillisecondsIntervalStringFormatPropertyChanged() + { + UpdateFormattedContent(); + } + #endregion public string MillisecondsIntervalStringFormat + +#if !SILVERLIGHT + /// + /// Initializes the static members of the DateTimeAxisLabel class. + /// + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Dependency properties are initialized in-line.")] + static DateTimeAxisLabel() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(DateTimeAxisLabel), new FrameworkPropertyMetadata(typeof(DateTimeAxisLabel))); + } + +#endif + + /// + /// Instantiates a new instance of the DateTimeAxisLabel class. + /// + public DateTimeAxisLabel() + { +#if SILVERLIGHT + this.DefaultStyleKey = typeof(DateTimeAxisLabel); +#endif + } + + /// + /// Updates the formatted text. + /// + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Code is not overly complex.")] + protected override void UpdateFormattedContent() + { + if (StringFormat == null) + { + switch (IntervalType) + { + case DateTimeIntervalType.Years: + this.SetBinding(FormattedContentProperty, new Binding { Converter = new StringFormatConverter(), ConverterParameter = YearsIntervalStringFormat ?? StringFormat ?? "{0}" }); + break; + case DateTimeIntervalType.Months: + this.SetBinding(FormattedContentProperty, new Binding { Converter = new StringFormatConverter(), ConverterParameter = MonthsIntervalStringFormat ?? StringFormat ?? "{0}" }); + break; + case DateTimeIntervalType.Weeks: + this.SetBinding(FormattedContentProperty, new Binding { Converter = new StringFormatConverter(), ConverterParameter = WeeksIntervalStringFormat ?? StringFormat ?? "{0}" }); + break; + case DateTimeIntervalType.Days: + this.SetBinding(FormattedContentProperty, new Binding { Converter = new StringFormatConverter(), ConverterParameter = DaysIntervalStringFormat ?? StringFormat ?? "{0}" }); + break; + case DateTimeIntervalType.Hours: + this.SetBinding(FormattedContentProperty, new Binding { Converter = new StringFormatConverter(), ConverterParameter = HoursIntervalStringFormat ?? StringFormat ?? "{0}" }); + break; + case DateTimeIntervalType.Minutes: + this.SetBinding(FormattedContentProperty, new Binding { Converter = new StringFormatConverter(), ConverterParameter = MinutesIntervalStringFormat ?? StringFormat ?? "{0}" }); + break; + case DateTimeIntervalType.Seconds: + this.SetBinding(FormattedContentProperty, new Binding { Converter = new StringFormatConverter(), ConverterParameter = SecondsIntervalStringFormat ?? StringFormat ?? "{0}" }); + break; + case DateTimeIntervalType.Milliseconds: + this.SetBinding(FormattedContentProperty, new Binding { Converter = new StringFormatConverter(), ConverterParameter = MillisecondsIntervalStringFormat ?? StringFormat ?? "{0}" }); + break; + default: + base.UpdateFormattedContent(); + break; + } + } + else + { + base.UpdateFormattedContent(); + } + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/DateTimeIntervalType.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/DateTimeIntervalType.cs new file mode 100644 index 00000000..50de6754 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/DateTimeIntervalType.cs @@ -0,0 +1,58 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// A date time interval. + /// + public enum DateTimeIntervalType + { + /// + /// Automatically determine interval. + /// + Auto = 0, + + /// + /// Interval type is milliseconds. + /// + Milliseconds = 1, + + /// + /// Interval type is seconds. + /// + Seconds = 2, + + /// + /// Interval type is minutes. + /// + Minutes = 3, + + /// + /// Interval type is hours. + /// + Hours = 4, + + /// + /// Interval type is days. + /// + Days = 5, + + /// + /// Interval type is weeks. + /// + Weeks = 6, + + /// + /// Interval type is months. + /// + Months = 7, + + /// + /// Interval type is years. + /// + Years = 8, + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/DisplayAxis.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/DisplayAxis.cs new file mode 100644 index 00000000..f6fdef94 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/DisplayAxis.cs @@ -0,0 +1,798 @@ +// (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; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Windows; +using System.Windows.Media; +using System.Windows.Controls; +using System.Windows.Shapes; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// An axis that has a range. + /// + public abstract class DisplayAxis : Axis, IRequireSeriesHost + { + /// + /// Maximum intervals per 200 pixels. + /// + protected const double MaximumAxisIntervalsPer200Pixels = 8; + + /// + /// The name of the axis grid template part. + /// + protected const string AxisGridName = "AxisGrid"; + + /// + /// The name of the axis title template part. + /// + protected const string AxisTitleName = "AxisTitle"; + + #region public Style AxisLabelStyle + /// + /// Gets or sets the style used for the axis labels. + /// + public Style AxisLabelStyle + { + get { return GetValue(AxisLabelStyleProperty) as Style; } + set { SetValue(AxisLabelStyleProperty, value); } + } + + /// + /// Identifies the AxisLabelStyle dependency property. + /// + public static readonly DependencyProperty AxisLabelStyleProperty = + DependencyProperty.Register( + "AxisLabelStyle", + typeof(Style), + typeof(DisplayAxis), + new PropertyMetadata(null, OnAxisLabelStylePropertyChanged)); + + /// + /// AxisLabelStyleProperty property changed handler. + /// + /// DisplayAxis that changed its AxisLabelStyle. + /// Event arguments. + private static void OnAxisLabelStylePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DisplayAxis source = (DisplayAxis)d; + Style oldValue = (Style)e.OldValue; + Style newValue = (Style)e.NewValue; + source.OnAxisLabelStylePropertyChanged(oldValue, newValue); + } + + /// + /// AxisLabelStyleProperty property changed handler. + /// + /// Old value. + /// New value. + protected virtual void OnAxisLabelStylePropertyChanged(Style oldValue, Style newValue) + { + } + #endregion public Style AxisLabelStyle + + /// + /// Gets the actual length. + /// + protected double ActualLength + { + get + { + return GetLength(new Size(this.ActualWidth, this.ActualHeight)); + } + } + + /// + /// Returns the length of the axis given an available size. + /// + /// The available size. + /// The length of the axis given an available size. + protected double GetLength(Size availableSize) + { + if (this.ActualHeight == 0.0 && this.ActualWidth == 0.0) + { + return 0.0; + } + if (this.Orientation == AxisOrientation.X) + { + return availableSize.Width; + } + else if (this.Orientation == AxisOrientation.Y) + { + return availableSize.Height; + } + else + { + throw new InvalidOperationException(Properties.Resources.DisplayAxis_GetLength_CannotDetermineTheLengthOfAnAxisWithAnOrientationOfNone); + } + } + + #region private GridLines GridLines + + /// + /// This field stores the grid lines element. + /// + private DisplayAxisGridLines _gridLines; + + /// + /// Gets or sets the grid lines property. + /// + internal DisplayAxisGridLines GridLines + { + get { return _gridLines; } + set + { + if (value != _gridLines) + { + DisplayAxisGridLines oldValue = _gridLines; + _gridLines = value; + OnGridLinesPropertyChanged(oldValue, value); + } + } + } + + /// + /// GridLinesProperty property changed handler. + /// + /// Old value. + /// New value. + private void OnGridLinesPropertyChanged(DisplayAxisGridLines oldValue, DisplayAxisGridLines newValue) + { + if (SeriesHost != null && oldValue != null) + { + SeriesHost.BackgroundElements.Remove(oldValue); + } + if (SeriesHost != null && newValue != null) + { + SeriesHost.BackgroundElements.Add(newValue); + } + } + #endregion private GridLines GridLines + + #region public Style MajorTickMarkStyle + /// + /// Gets or sets the style applied to the Axis tick marks. + /// + /// The Style applied to the Axis tick marks. + public Style MajorTickMarkStyle + { + get { return GetValue(MajorTickMarkStyleProperty) as Style; } + set { SetValue(MajorTickMarkStyleProperty, value); } + } + + /// + /// Identifies the MajorTickMarkStyle dependency property. + /// + public static readonly DependencyProperty MajorTickMarkStyleProperty = + DependencyProperty.Register( + "MajorTickMarkStyle", + typeof(Style), + typeof(DisplayAxis), + new PropertyMetadata(null, OnMajorTickMarkStylePropertyChanged)); + + /// + /// MajorTickMarkStyleProperty property changed handler. + /// + /// DisplayAxis that changed its MajorTickMarkStyle. + /// Event arguments. + private static void OnMajorTickMarkStylePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DisplayAxis source = (DisplayAxis)d; + Style oldValue = (Style)e.OldValue; + Style newValue = (Style)e.NewValue; + source.OnMajorTickMarkStylePropertyChanged(oldValue, newValue); + } + + /// + /// MajorTickMarkStyleProperty property changed handler. + /// + /// Old value. + /// New value. + protected virtual void OnMajorTickMarkStylePropertyChanged(Style oldValue, Style newValue) + { + } + #endregion public Style MajorTickMarkStyle + + #region public object Title + /// + /// Gets or sets the title property. + /// + public object Title + { + get { return GetValue(TitleProperty) as object; } + set { SetValue(TitleProperty, value); } + } + + /// + /// Identifies the Title dependency property. + /// + public static readonly DependencyProperty TitleProperty = + DependencyProperty.Register( + "Title", + typeof(object), + typeof(DisplayAxis), + new PropertyMetadata(null, OnTitlePropertyChanged)); + + /// + /// TitleProperty property changed handler. + /// + /// DisplayAxis that changed its Title. + /// Event arguments. + private static void OnTitlePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DisplayAxis source = (DisplayAxis)d; + object oldValue = (object)e.OldValue; + object newValue = (object)e.NewValue; + source.OnTitlePropertyChanged(oldValue, newValue); + } + + /// + /// TitleProperty property changed handler. + /// + /// Old value. + /// New value. + protected virtual void OnTitlePropertyChanged(object oldValue, object newValue) + { + if (this.AxisTitle != null) + { + this.AxisTitle.Content = Title; + } + } + #endregion public object Title + + /// + /// Gets or sets the LayoutTransformControl used to rotate the title. + /// + private LayoutTransformControl TitleLayoutTransformControl { get; set; } + + #region public Style TitleStyle + /// + /// Gets or sets the style applied to the Axis title. + /// + /// The Style applied to the Axis title. + public Style TitleStyle + { + get { return GetValue(TitleStyleProperty) as Style; } + set { SetValue(TitleStyleProperty, value); } + } + + /// + /// Identifies the TitleStyle dependency property. + /// + public static readonly DependencyProperty TitleStyleProperty = + DependencyProperty.Register( + "TitleStyle", + typeof(Style), + typeof(DisplayAxis), + null); + #endregion + + #region public bool ShowGridLines + /// + /// Gets or sets a value indicating whether grid lines should be shown. + /// + [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "GridLines", Justification = "This is the expected casing.")] + public bool ShowGridLines + { + get { return (bool)GetValue(ShowGridLinesProperty); } + set { SetValue(ShowGridLinesProperty, value); } + } + + /// + /// Identifies the ShowGridLines dependency property. + /// + [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "GridLines", Justification = "This is the expected capitalization.")] + [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "GridLine", Justification = "This is the expected capitalization.")] + public static readonly DependencyProperty ShowGridLinesProperty = + DependencyProperty.Register( + "ShowGridLines", + typeof(bool), + typeof(DisplayAxis), + new PropertyMetadata(false, OnShowGridLinesPropertyChanged)); + + /// + /// ShowGridLinesProperty property changed handler. + /// + /// Axis that changed its ShowGridLines. + /// Event arguments. + private static void OnShowGridLinesPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DisplayAxis source = (DisplayAxis)d; + bool oldValue = (bool)e.OldValue; + bool newValue = (bool)e.NewValue; + source.OnShowGridLinesPropertyChanged(oldValue, newValue); + } + + /// + /// ShowGridLinesProperty property changed handler. + /// + /// Old value. + /// New value. + [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "GridLine", Justification = "This is the expected capitalization.")] + [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "GridLines", Justification = "This is the expected capitalization.")] + protected virtual void OnShowGridLinesPropertyChanged(bool oldValue, bool newValue) + { + SetShowGridLines(newValue); + } + #endregion public bool ShowGridLines + + /// + /// Creates and destroys a grid lines element based on the specified + /// value. + /// + /// A value indicating whether to display grid + /// lines or not. + private void SetShowGridLines(bool newValue) + { + if (newValue == true) + { + this.GridLines = new OrientedAxisGridLines(this); + } + else + { + this.GridLines = null; + } + } + + #region public Style GridLineStyle + /// + /// Gets or sets the Style of the Axis's gridlines. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "GridLine", Justification = "Current casing is the expected one.")] + public Style GridLineStyle + { + get { return GetValue(GridLineStyleProperty) as Style; } + set { SetValue(GridLineStyleProperty, value); } + } + + /// + /// Identifies the GridlineStyle dependency property. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "GridLine", Justification = "Current casing is the expected one.")] + public static readonly DependencyProperty GridLineStyleProperty = + DependencyProperty.Register( + "GridLineStyle", + typeof(Style), + typeof(DisplayAxis), + null); + #endregion + + /// + /// The grid used to layout the axis. + /// + private Grid _grid; + + /// + /// Gets or sets the grid used to layout the axis. + /// + private Grid AxisGrid + { + get + { + return _grid; + } + set + { + if (_grid != value) + { + if (_grid != null) + { + _grid.Children.Clear(); + } + + _grid = value; + + if (_grid != null) + { + _grid.Children.Add(this.OrientedPanel); + if (this.AxisTitle != null) + { + _grid.Children.Add(this.AxisTitle); + } + } + } + } + } + + /// + /// Gets or sets a grid to lay out the dependent axis. + /// + private Grid DependentAxisGrid { get; set; } + + /// + /// Gets the oriented panel used to layout the axis labels. + /// + internal OrientedPanel OrientedPanel { get; private set; } + + /// + /// The control used to display the axis title. + /// + private Title _axisTitle; + + /// + /// Gets or sets the title control used to display the title. + /// + private Title AxisTitle + { + get + { + return _axisTitle; + } + set + { + if (_axisTitle != value) + { + if (_axisTitle != null) + { + _axisTitle.Content = null; + } + + _axisTitle = value; + if (Title != null) + { + _axisTitle.Content = Title; + } + } + } + } + + /// + /// Creates a major axis tick mark. + /// + /// A line to used to render a tick mark. + protected virtual Line CreateMajorTickMark() + { + return CreateTickMark(MajorTickMarkStyle); + } + + /// + /// Creates a tick mark and applies a style to it. + /// + /// The style to apply. + /// The newly created tick mark. + protected Line CreateTickMark(Style style) + { + Line line = new Line(); + line.Style = style; + if (this.Orientation == AxisOrientation.Y) + { + line.Y1 = 0.5; + line.Y2 = 0.5; + } + else if (this.Orientation == AxisOrientation.X) + { + line.X1 = 0.5; + line.X2 = 0.5; + } + return line; + } + + /// + /// This method is used to share the grid line coordinates with the + /// internal grid lines control. + /// + /// A sequence of the major grid line coordinates. + internal IEnumerable InternalGetMajorGridLinePositions() + { + return GetMajorGridLineCoordinates(new Size(this.ActualWidth, this.ActualHeight)); + } + + /// + /// Returns the coordinates to use for the grid line control. + /// + /// The available size. + /// A sequence of coordinates at which to draw grid lines. + /// + [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "GridLine", Justification = "This is the expected capitalization.")] + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Returns the coordinates of the grid lines.")] + protected abstract IEnumerable GetMajorGridLineCoordinates(Size availableSize); + +#if !SILVERLIGHT + /// + /// Initializes the static members of the DisplayAxis class. + /// + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Dependency properties are initialized in-line.")] + static DisplayAxis() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(DisplayAxis), new FrameworkPropertyMetadata(typeof(DisplayAxis))); + } + +#endif + /// + /// Instantiates a new instance of the DisplayAxis class. + /// + protected DisplayAxis() + { + this.OrientedPanel = new OrientedPanel(); +#if SILVERLIGHT + this.DefaultStyleKey = typeof(DisplayAxis); + this.OrientedPanel.UseLayoutRounding = true; +#endif + + this.DependentAxisGrid = new Grid(); + + this.TitleLayoutTransformControl = new LayoutTransformControl(); + this.TitleLayoutTransformControl.HorizontalAlignment = HorizontalAlignment.Center; + this.TitleLayoutTransformControl.VerticalAlignment = VerticalAlignment.Center; + + this.SizeChanged += new SizeChangedEventHandler(DisplayAxisSizeChanged); + } + + /// + /// If display axis has just become visible, invalidate. + /// + /// The source of the event. + /// Information about the event. + private void DisplayAxisSizeChanged(object sender, SizeChangedEventArgs e) + { + if (e.PreviousSize.Width == 0.0 && e.PreviousSize.Height == 0.0) + { + Invalidate(); + } + } + + /// + /// Creates an axis label. + /// + /// The new axis label. + protected virtual Control CreateAxisLabel() + { + return new AxisLabel(); + } + + /// + /// Updates the grid lines element if a suitable dependent axis has + /// been added to a radial axis. + /// + protected override void OnDependentAxesCollectionChanged() + { + SetShowGridLines(ShowGridLines); + base.OnDependentAxesCollectionChanged(); + } + + /// + /// Prepares an axis label to be plotted. + /// + /// The axis label to prepare. + /// The data context to use for the axis + /// label. + protected virtual void PrepareAxisLabel(Control label, object dataContext) + { + label.DataContext = dataContext; + label.SetStyle(AxisLabelStyle); + } + + /// + /// Retrieves template parts and configures layout. + /// + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + this.AxisGrid = GetTemplateChild(AxisGridName) as Grid; + this.AxisTitle = GetTemplateChild(AxisTitleName) as Title; + if (this.AxisTitle != null && this.AxisGrid.Children.Contains(this.AxisTitle)) + { + this.AxisGrid.Children.Remove(this.AxisTitle); + this.TitleLayoutTransformControl.Child = this.AxisTitle; + this.AxisGrid.Children.Add(this.TitleLayoutTransformControl); + } + + ArrangeAxisGrid(); + } + + /// + /// When the size of the oriented panel changes invalidate the axis. + /// + /// The source of the event. + /// Information about the event. + private void OnOrientedPanelSizeChanged(object sender, SizeChangedEventArgs e) + { + Invalidate(); + } + + /// + /// Arranges the grid when the location property is changed. + /// + /// The old location. + /// The new location. + protected override void OnLocationPropertyChanged(AxisLocation oldValue, AxisLocation newValue) + { + ArrangeAxisGrid(); + base.OnLocationPropertyChanged(oldValue, newValue); + } + + /// + /// Arranges the elements in the axis grid. + /// + private void ArrangeAxisGrid() + { + if (this.AxisGrid != null) + { + this.AxisGrid.ColumnDefinitions.Clear(); + this.AxisGrid.RowDefinitions.Clear(); + this.AxisGrid.Children.Clear(); + + if (this.Orientation == AxisOrientation.Y) + { + this.OrientedPanel.Orientation = System.Windows.Controls.Orientation.Vertical; + this.OrientedPanel.IsReversed = true; + + if (this.Location == AxisLocation.Left || this.Location == AxisLocation.Right) + { + this.TitleLayoutTransformControl.Transform = new RotateTransform { Angle = -90.0 }; + + this.OrientedPanel.IsInverted = !(Location == AxisLocation.Right); + this.AxisGrid.ColumnDefinitions.Add(new ColumnDefinition()); + this.AxisGrid.RowDefinitions.Add(new RowDefinition()); + + int column = 0; + if (this.AxisTitle != null) + { + this.AxisGrid.ColumnDefinitions.Add(new ColumnDefinition()); + Grid.SetRow(this.TitleLayoutTransformControl, 0); + Grid.SetColumn(this.TitleLayoutTransformControl, 0); + column++; + } + Grid.SetRow(this.OrientedPanel, 0); + Grid.SetColumn(this.OrientedPanel, column); + + this.AxisGrid.Children.Add(this.TitleLayoutTransformControl); + this.AxisGrid.Children.Add(this.OrientedPanel); + + if (this.Location == AxisLocation.Right) + { + AxisGrid.Mirror(System.Windows.Controls.Orientation.Vertical); + this.TitleLayoutTransformControl.Transform = new RotateTransform { Angle = 90 }; + } + } + } + else if (this.Orientation == AxisOrientation.X) + { + this.OrientedPanel.Orientation = System.Windows.Controls.Orientation.Horizontal; + this.OrientedPanel.IsReversed = false; + + if (this.Location == AxisLocation.Top || this.Location == AxisLocation.Bottom) + { + this.OrientedPanel.IsInverted = (Location == AxisLocation.Top); + this.TitleLayoutTransformControl.Transform = new RotateTransform { Angle = 0 }; + + this.AxisGrid.ColumnDefinitions.Add(new ColumnDefinition()); + this.AxisGrid.RowDefinitions.Add(new RowDefinition()); + + if (this.AxisTitle != null) + { + this.AxisGrid.RowDefinitions.Add(new RowDefinition()); + Grid.SetColumn(this.TitleLayoutTransformControl, 0); + Grid.SetRow(this.TitleLayoutTransformControl, 1); + } + + Grid.SetColumn(this.OrientedPanel, 0); + Grid.SetRow(this.OrientedPanel, 0); + + this.AxisGrid.Children.Add(this.TitleLayoutTransformControl); + this.AxisGrid.Children.Add(this.OrientedPanel); + + if (this.Location == AxisLocation.Top) + { + AxisGrid.Mirror(System.Windows.Controls.Orientation.Horizontal); + } + } + } + + Invalidate(); + } + } + + /// + /// Renders the axis. + /// + /// The available size. + /// The required size. + protected override Size MeasureOverride(Size availableSize) + { + RenderAxis(availableSize); + return base.MeasureOverride(availableSize); + } + + /// + /// Reformulates the grid when the orientation is changed. Grid is + /// either separated into two columns or two rows. The title is + /// inserted with the outermost section from the edge and an oriented + /// panel is inserted into the innermost section. + /// + /// The old value. + /// The new value. + protected override void OnOrientationPropertyChanged(AxisOrientation oldValue, AxisOrientation newValue) + { + ArrangeAxisGrid(); + base.OnOrientationPropertyChanged(oldValue, newValue); + } + + /// + /// Updates the visual appearance of the axis when it is invalidated. + /// + /// Information for the invalidated event. + protected override void OnInvalidated(RoutedEventArgs args) + { + InvalidateMeasure(); + base.OnInvalidated(args); + } + + /// + /// Renders the axis if there is a valid value for orientation. + /// + /// The available size in which to render + /// the axis. + private void RenderAxis(Size availableSize) + { + if (Orientation != AxisOrientation.None && Location != AxisLocation.Auto) + { + Render(availableSize); + } + } + + /// + /// Renders the axis labels, tick marks, and other visual elements. + /// + /// The available size. + protected abstract void Render(Size availableSize); + + /// + /// Invalidates the axis. + /// + protected void Invalidate() + { + OnInvalidated(new RoutedEventArgs()); + } + + /// + /// The series host. + /// + private ISeriesHost _seriesHost; + + /// + /// Gets or sets the series host. + /// + public ISeriesHost SeriesHost + { + get + { + return _seriesHost; + } + set + { + if (value != _seriesHost) + { + ISeriesHost oldValue = _seriesHost; + _seriesHost = value; + OnSeriesHostPropertyChanged(oldValue, value); + } + } + } + + /// + /// This method is run when the series host property is changed. + /// + /// The old series host. + /// The new series host. + protected virtual void OnSeriesHostPropertyChanged(ISeriesHost oldValue, ISeriesHost newValue) + { + if (oldValue != null && this.GridLines != null) + { + oldValue.BackgroundElements.Remove(this.GridLines); + } + + if (newValue != null && this.GridLines != null) + { + newValue.BackgroundElements.Add(this.GridLines); + } + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/DisplayAxisGridLines.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/DisplayAxisGridLines.cs new file mode 100644 index 00000000..38715da2 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/DisplayAxisGridLines.cs @@ -0,0 +1,106 @@ +// (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; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Shapes; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// This control draws gridlines with the help of an axis. + /// + [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "GridLine", Justification = "This is the expected capitalization.")] + [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "DisplayAxisGridLines", Justification = "This is the expected capitalization.")] + internal abstract class DisplayAxisGridLines : Canvas, IAxisListener + { + #region public DisplayAxis Axis + + /// + /// The field that stores the axis that the grid lines are connected to. + /// + private DisplayAxis _axis; + + /// + /// Gets the axis that the grid lines are connected to. + /// + public DisplayAxis Axis + { + get { return _axis; } + private set + { + if (_axis != value) + { + DisplayAxis oldValue = _axis; + _axis = value; + if (oldValue != _axis) + { + OnAxisPropertyChanged(oldValue, value); + } + } + } + } + + /// + /// AxisProperty property changed handler. + /// + /// Old value. + /// New value. + private void OnAxisPropertyChanged(DisplayAxis oldValue, DisplayAxis newValue) + { + Debug.Assert(newValue != null, "Don't set the axis property to null."); + + if (newValue != null) + { + newValue.RegisteredListeners.Add(this); + } + + if (oldValue != null) + { + oldValue.RegisteredListeners.Remove(this); + } + } + #endregion public DisplayAxis Axis + + /// + /// Instantiates a new instance of the DisplayAxisGridLines class. + /// + /// The axis used by the DisplayAxisGridLines. + public DisplayAxisGridLines(DisplayAxis axis) + { + this.Axis = axis; + this.SizeChanged += new SizeChangedEventHandler(OnSizeChanged); + } + + /// + /// Redraws grid lines when the size of the control changes. + /// + /// The source of the event. + /// Information about the event. + private void OnSizeChanged(object sender, SizeChangedEventArgs e) + { + Invalidate(); + } + + /// + /// Redraws grid lines when the axis is invalidated. + /// + /// The invalidated axis. + public void AxisInvalidated(IAxis axis) + { + Invalidate(); + } + + /// + /// Draws the grid lines. + /// + protected abstract void Invalidate(); + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/IAnchoredToOrigin.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/IAnchoredToOrigin.cs new file mode 100644 index 00000000..c4a3d051 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/IAnchoredToOrigin.cs @@ -0,0 +1,24 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Range axes look for this interface on series to determine whether to + /// anchor the origin to the bottom or top of the screen where possible. + /// + /// + /// Implementing this interface ensures that value margins will not cause + /// an origin to float above the bottom or top of the screen if no + /// data exists below or above. + /// + public interface IAnchoredToOrigin + { + /// + /// Gets the axis to which the data is anchored. + /// + IRangeAxis AnchoredAxis { get; } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/IAxis.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/IAxis.cs new file mode 100644 index 00000000..4ef9c5fe --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/IAxis.cs @@ -0,0 +1,51 @@ +// (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.ObjectModel; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// An axis interface used to determine the plot area coordinate of values. + /// + public interface IAxis + { + /// + /// Gets or sets the orientation of the axis. + /// + AxisOrientation Orientation { get; set; } + + /// + /// This event is raised when the Orientation property is changed. + /// + event RoutedPropertyChangedEventHandler OrientationChanged; + + /// + /// Returns a value indicating whether the axis can plot a value. + /// + /// The value to plot. + /// A value indicating whether the axis can plot a value. + /// + bool CanPlot(object value); + + /// + /// The plot area coordinate of a value. + /// + /// The value for which to retrieve the plot area + /// coordinate. + /// The plot area coordinate. + UnitValue GetPlotAreaCoordinate(object value); + + /// + /// Gets the registered IAxisListeners. + /// + ObservableCollection RegisteredListeners { get; } + + /// + /// Gets the collection of child axes. + /// + ObservableCollection DependentAxes { get; } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/IAxisListener.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/IAxisListener.cs new file mode 100644 index 00000000..f47a530b --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/IAxisListener.cs @@ -0,0 +1,19 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// An object that listens for changes in an axis. + /// + public interface IAxisListener + { + /// + /// This method is called when the axis is invalidated. + /// + /// The axis that has been invalidated. + void AxisInvalidated(IAxis axis); + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/ICategoryAxis.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/ICategoryAxis.cs new file mode 100644 index 00000000..9b1663f1 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/ICategoryAxis.cs @@ -0,0 +1,31 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// An axis that is arranged by category. + /// + public interface ICategoryAxis : IAxis, IDataConsumer + { + /// + /// Accepts a category and returns the coordinate range of that category + /// on the axis. + /// + /// A category for which to retrieve the + /// coordinate location. + /// The coordinate range of the category on the axis. + Range GetPlotAreaCoordinateRange(object category); + + /// + /// Returns the category at a given coordinate. + /// + /// The plot are coordinate. + /// The category at the given plot area coordinate. + object GetCategoryAtPosition(UnitValue position); + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/IDataConsumer.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/IDataConsumer.cs new file mode 100644 index 00000000..507dbce7 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/IDataConsumer.cs @@ -0,0 +1,22 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// An object that consumes data. + /// + public interface IDataConsumer + { + /// + /// Supplies the consumer with data. + /// + /// The data provider. + /// The data used by the consumer. + void DataChanged(IDataProvider dataProvider, IEnumerable data); + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/IDataProvider.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/IDataProvider.cs new file mode 100644 index 00000000..da1a95f6 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/IDataProvider.cs @@ -0,0 +1,22 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Provides information to a category axis. + /// + public interface IDataProvider + { + /// + /// Retrieves the data to be plotted on the axis. + /// + /// The axis to retrieve the data for. + /// The data to plot on the axis. + IEnumerable GetData(IDataConsumer axis); + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/IRangeAxis.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/IRangeAxis.cs new file mode 100644 index 00000000..fadd4b52 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/IRangeAxis.cs @@ -0,0 +1,35 @@ +// (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; +using System.Diagnostics.CodeAnalysis; +using System.Windows; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// An axis with a range. + /// + public interface IRangeAxis : IAxis, IRangeConsumer + { + /// + /// Gets the range of values displayed on the axis. + /// + Range Range { get; } + + /// + /// The plot area coordinate of a value. + /// + /// The position at which to retrieve the plot + /// area coordinate. + /// The plot area coordinate. + IComparable GetValueAtPosition(UnitValue position); + + /// + /// Gets the origin value on the axis. + /// + IComparable Origin { get; } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/IRangeConsumer.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/IRangeConsumer.cs new file mode 100644 index 00000000..a5654172 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/IRangeConsumer.cs @@ -0,0 +1,22 @@ +// (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; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// An object that consumes a range. + /// + public interface IRangeConsumer + { + /// + /// Informs a range consumer that a provider's range has changed. + /// + /// The range provider. + /// The range of data. + void RangeChanged(IRangeProvider provider, Range range); + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/IRangeProvider.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/IRangeProvider.cs new file mode 100644 index 00000000..bed2a729 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/IRangeProvider.cs @@ -0,0 +1,24 @@ +// (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; +using System.Collections.Generic; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Provides information to a RangeConsumer. + /// + public interface IRangeProvider + { + /// + /// Returns the range of values. + /// + /// The range consumer requesting the data + /// range. + /// A data range. + Range GetRange(IRangeConsumer rangeConsumer); + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/IValueMarginConsumer.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/IValueMarginConsumer.cs new file mode 100644 index 00000000..b4446737 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/IValueMarginConsumer.cs @@ -0,0 +1,22 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Consumes value margins and uses them to lay out objects. + /// + public interface IValueMarginConsumer + { + /// + /// Updates layout to accommodate for value margins. + /// + /// A value margin provider. + /// A sequence of value margins. + void ValueMarginsChanged(IValueMarginProvider provider, IEnumerable valueMargins); + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/IValueMarginProvider.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/IValueMarginProvider.cs new file mode 100644 index 00000000..9ee706e2 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/IValueMarginProvider.cs @@ -0,0 +1,25 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Provides information about margins necessary for values. + /// + public interface IValueMarginProvider + { + /// + /// Gets the margins required for values. + /// + /// The axis to retrieve the value margins + /// for. + /// The margins required for values. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This method does a substantial amount of work.")] + IEnumerable GetValueMargins(IValueMarginConsumer consumer); + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/LinearAxis.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/LinearAxis.cs new file mode 100644 index 00000000..876b7696 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/LinearAxis.cs @@ -0,0 +1,400 @@ +// (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; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Shapes; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// An axis that displays numeric values. + /// + [StyleTypedProperty(Property = "GridLineStyle", StyleTargetType = typeof(Line))] + [StyleTypedProperty(Property = "MajorTickMarkStyle", StyleTargetType = typeof(Line))] + [StyleTypedProperty(Property = "MinorTickMarkStyle", StyleTargetType = typeof(Line))] + [StyleTypedProperty(Property = "AxisLabelStyle", StyleTargetType = typeof(NumericAxisLabel))] + [StyleTypedProperty(Property = "TitleStyle", StyleTargetType = typeof(Title))] + [TemplatePart(Name = AxisGridName, Type = typeof(Grid))] + [TemplatePart(Name = AxisTitleName, Type = typeof(Title))] + public class LinearAxis : NumericAxis + { + #region public double? Interval + /// + /// Gets or sets the axis interval. + /// + [TypeConverter(typeof(NullableConverter))] + public double? Interval + { + get { return (double?)GetValue(IntervalProperty); } + set { SetValue(IntervalProperty, value); } + } + + /// + /// Identifies the Interval dependency property. + /// + public static readonly DependencyProperty IntervalProperty = + DependencyProperty.Register( + "Interval", + typeof(double?), + typeof(LinearAxis), + new PropertyMetadata(null, OnIntervalPropertyChanged)); + + /// + /// IntervalProperty property changed handler. + /// + /// LinearAxis that changed its Interval. + /// Event arguments. + private static void OnIntervalPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + LinearAxis source = (LinearAxis)d; + source.OnIntervalPropertyChanged(); + } + + /// + /// IntervalProperty property changed handler. + /// + private void OnIntervalPropertyChanged() + { + OnInvalidated(new RoutedEventArgs()); + } + #endregion public double? Interval + + #region public double ActualInterval + /// + /// Gets the actual interval of the axis. + /// + public double ActualInterval + { + get { return (double)GetValue(ActualIntervalProperty); } + private set { SetValue(ActualIntervalProperty, value); } + } + + /// + /// Identifies the ActualInterval dependency property. + /// + public static readonly DependencyProperty ActualIntervalProperty = + DependencyProperty.Register( + "ActualInterval", + typeof(double), + typeof(LinearAxis), + new PropertyMetadata(double.NaN)); + #endregion public double ActualInterval + + /// + /// Instantiates a new instance of the LinearAxis class. + /// + public LinearAxis() + { + this.ActualRange = new Range(0.0, 1.0); + } + + /// + /// Gets the actual range of double values. + /// + protected Range ActualDoubleRange { get; private set; } + + /// + /// Updates ActualDoubleRange when ActualRange changes. + /// + /// New ActualRange value. + protected override void OnActualRangeChanged(Range range) + { + ActualDoubleRange = range.ToDoubleRange(); + base.OnActualRangeChanged(range); + } + + /// + /// Returns the plot area coordinate of a value. + /// + /// The value to plot. + /// The length of axis. + /// The plot area coordinate of a value. + protected override UnitValue GetPlotAreaCoordinate(object value, double length) + { + return GetPlotAreaCoordinate(value, ActualDoubleRange, length); + } + + /// + /// Returns the plot area coordinate of a value. + /// + /// The value to plot. + /// The range of values. + /// The length of axis. + /// The plot area coordinate of a value. + protected override UnitValue GetPlotAreaCoordinate(object value, Range currentRange, double length) + { + return GetPlotAreaCoordinate(value, currentRange.ToDoubleRange(), length); + } + + /// + /// Returns the plot area coordinate of a value. + /// + /// The value to plot. + /// The range of values. + /// The length of axis. + /// The plot area coordinate of a value. + private static UnitValue GetPlotAreaCoordinate(object value, Range currentRange, double length) + { + if (currentRange.HasData) + { + double doubleValue = ValueHelper.ToDouble(value); + + double pixelLength = Math.Max(length - 1, 0); + double rangelength = currentRange.Maximum - currentRange.Minimum; + + return new UnitValue((doubleValue - currentRange.Minimum) * (pixelLength / rangelength), Unit.Pixels); + } + + return UnitValue.NaN(); + } + + /// + /// Returns the actual interval to use to determine which values are + /// displayed in the axis. + /// + /// The available size. + /// Actual interval to use to determine which values are + /// displayed in the axis. + /// + protected virtual double CalculateActualInterval(Size availableSize) + { + if (Interval != null) + { + return Interval.Value; + } + + // Adjust maximum interval count adjusted for current axis + double adjustedMaximumIntervalsPer200Pixels = (Orientation == AxisOrientation.X ? 0.8 : 1.0) * MaximumAxisIntervalsPer200Pixels; + // Calculate maximum interval count for current space + double maximumIntervalCount = Math.Max(GetLength(availableSize) * adjustedMaximumIntervalsPer200Pixels / 200.0, 1.0); + // Calculate range + double range = ActualDoubleRange.Maximum - ActualDoubleRange.Minimum; + // Calculate largest acceptable interval + double bestInterval = range / maximumIntervalCount; + // Calculate mimimum ideal interval (ideal => something that gives nice axis values) + double minimumIdealInterval = Math.Pow(10, Math.Floor(Math.Log10(bestInterval))); + // Walk the list of ideal multipliers + foreach (int idealMultiplier in new int[] { 10, 5, 2, 1 }) + { + // Check the current ideal multiplier against the maximum count + double currentIdealInterval = minimumIdealInterval * idealMultiplier; + if (maximumIntervalCount < (range / currentIdealInterval)) + { + // Went too far, break out + break; + } + // Update the best interval + bestInterval = currentIdealInterval; + } + // Return best interval + return bestInterval; + } + + /// + /// Returns a sequence of values to create major tick marks for. + /// + /// The available size. + /// A sequence of values to create major tick marks for. + /// + protected override IEnumerable GetMajorTickMarkValues(Size availableSize) + { + return GetMajorValues(availableSize).CastWrapper(); + } + + /// + /// Returns a sequence of major axis values. + /// + /// The available size. + /// A sequence of major axis values. + /// + private IEnumerable GetMajorValues(Size availableSize) + { + if (!ActualRange.HasData || ValueHelper.Compare(ActualRange.Minimum, ActualRange.Maximum) == 0 || GetLength(availableSize) == 0.0) + { + yield break; + } + this.ActualInterval = CalculateActualInterval(availableSize); + double startValue = AlignToInterval(ActualDoubleRange.Minimum, this.ActualInterval); + if (startValue < ActualDoubleRange.Minimum) + { + startValue = AlignToInterval(ActualDoubleRange.Minimum + this.ActualInterval, this.ActualInterval); + } + double nextValue = startValue; + for (int counter = 1; nextValue <= ActualDoubleRange.Maximum; counter++) + { + yield return nextValue; + nextValue = startValue + (counter * this.ActualInterval); + } + } + + /// + /// Returns a sequence of values to plot on the axis. + /// + /// The available size. + /// A sequence of values to plot on the axis. + protected override IEnumerable GetLabelValues(Size availableSize) + { + return GetMajorValues(availableSize).CastWrapper(); + } + + /// + /// Aligns a value to the provided interval value. The aligned value + /// should always be smaller than or equal to than the provided value. + /// + /// The value to align to the interval. + /// The interval to align to. + /// The aligned value. + private static double AlignToInterval(double value, double interval) + { + double typedInterval = (double)interval; + double typedValue = (double)value; + return ValueHelper.RemoveNoiseFromDoubleMath(ValueHelper.RemoveNoiseFromDoubleMath(Math.Floor(typedValue / typedInterval)) * typedInterval); + } + + /// + /// Returns the value range given a plot area coordinate. + /// + /// The plot area position. + /// The value at that plot area coordinate. + protected override IComparable GetValueAtPosition(UnitValue value) + { + if (ActualRange.HasData && ActualLength != 0.0) + { + if (value.Unit == Unit.Pixels) + { + double coordinate = value.Value; + + double rangelength = ActualDoubleRange.Maximum - ActualDoubleRange.Minimum; + double output = ((coordinate * (rangelength / ActualLength)) + ActualDoubleRange.Minimum); + + return output; + } + else + { + throw new NotImplementedException(); + } + } + + return null; + } + + /// + /// Function that uses the mid point of all the data values + /// in the value margins to convert a length into a range + /// of data with the mid point as the center of that range. + /// + /// The mid point of the range. + /// The length of the range. + /// The range object. + private static Range LengthToRange(double midPoint, double length) + { + double halfLength = length / 2.0; + return new Range(midPoint - halfLength, midPoint + halfLength); + } + + /// + /// Overrides the actual range to ensure that it is never set to an + /// empty range. + /// + /// The range to override. + /// Returns the overridden range. + protected override Range OverrideDataRange(Range range) + { + range = base.OverrideDataRange(range); + if (!range.HasData) + { + return new Range(0.0, 1.0); + } + else if (ValueHelper.Compare(range.Minimum, range.Maximum) == 0) + { + Range outputRange = new Range((ValueHelper.ToDouble(range.Minimum)) - 1, (ValueHelper.ToDouble(range.Maximum)) + 1); + return outputRange; + } + + // ActualLength of 1.0 or less maps all points to the same coordinate + if (range.HasData && this.ActualLength > 1.0) + { + bool isDataAnchoredToOrigin = false; + IList valueMargins = new List(); + foreach (IValueMarginProvider valueMarginProvider in this.RegisteredListeners.OfType()) + { + foreach (ValueMargin valueMargin in valueMarginProvider.GetValueMargins(this)) + { + IAnchoredToOrigin dataAnchoredToOrigin = valueMarginProvider as IAnchoredToOrigin; + isDataAnchoredToOrigin = (dataAnchoredToOrigin != null && dataAnchoredToOrigin.AnchoredAxis == this); + + valueMargins.Add( + new ValueMarginCoordinateAndOverlap + { + ValueMargin = valueMargin, + }); + } + } + + if (valueMargins.Count > 0) + { + double maximumPixelMarginLength = + valueMargins + .Select(valueMargin => valueMargin.ValueMargin.LowMargin + valueMargin.ValueMargin.HighMargin) + .MaxOrNullable().Value; + + // Requested margin is larger than the axis so give up + // trying to find a range that will fit it. + if (maximumPixelMarginLength > this.ActualLength) + { + return range; + } + + Range originalRange = range.ToDoubleRange(); + Range currentRange = range.ToDoubleRange(); + + // Ensure range is not empty. + if (currentRange.Minimum == currentRange.Maximum) + { + currentRange = new Range(currentRange.Maximum - 1, currentRange.Maximum + 1); + } + + // priming the loop + double actualLength = this.ActualLength; + ValueMarginCoordinateAndOverlap maxLeftOverlapValueMargin; + ValueMarginCoordinateAndOverlap maxRightOverlapValueMargin; + UpdateValueMargins(valueMargins, currentRange.ToComparableRange()); + GetMaxLeftAndRightOverlap(valueMargins, out maxLeftOverlapValueMargin, out maxRightOverlapValueMargin); + + while (maxLeftOverlapValueMargin.LeftOverlap > 0 || maxRightOverlapValueMargin.RightOverlap > 0) + { + double unitOverPixels = currentRange.GetLength().Value / actualLength; + double newMinimum = currentRange.Minimum - ((maxLeftOverlapValueMargin.LeftOverlap + 0.5) * unitOverPixels); + double newMaximum = currentRange.Maximum + ((maxRightOverlapValueMargin.RightOverlap + 0.5) * unitOverPixels); + + currentRange = new Range(newMinimum, newMaximum); + UpdateValueMargins(valueMargins, currentRange.ToComparableRange()); + GetMaxLeftAndRightOverlap(valueMargins, out maxLeftOverlapValueMargin, out maxRightOverlapValueMargin); + } + + if (isDataAnchoredToOrigin) + { + if (originalRange.Minimum >= 0 && currentRange.Minimum < 0) + { + currentRange = new Range(0, currentRange.Maximum); + } + else if (originalRange.Maximum <= 0 && currentRange.Maximum > 0) + { + currentRange = new Range(currentRange.Minimum, 0); + } + } + + return currentRange.ToComparableRange(); + } + } + return range; + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/NullableConverter.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/NullableConverter.cs new file mode 100644 index 00000000..e9495d9d --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/NullableConverter.cs @@ -0,0 +1,125 @@ +// (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; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Globalization; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using ResxResources = System.Windows.Controls.DataVisualization.Properties.Resources; +using System.ComponentModel; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Converts a string or base value to a value. + /// + /// The type should be value type. + /// Preview + public class NullableConverter : TypeConverter where T : struct + { + /// + /// Returns whether the type converter can convert an object from the + /// specified type to the type of this converter. + /// + /// An object that provides a format context. + /// + /// The type you want to convert from. + /// + /// Returns true if this converter can perform the conversion; + /// otherwise, false. + /// + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + if (sourceType == typeof(T)) + { + return true; + } + else if (sourceType == typeof(string)) + { + return true; + } + return false; + } + + /// + /// Returns whether the type converter can convert an object from the + /// specified type to the type of this converter. + /// + /// An object that provides a format context. + /// + /// The type you want to convert to. + /// + /// + /// Returns true if this converter can perform the conversion; + /// otherwise, false. + /// + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + { + return (destinationType == typeof(T)); + } + + /// + /// Converts from the specified value to the type of this converter. + /// + /// An object that provides a format context. + /// + /// The + /// to use as the + /// current culture. + /// The value to convert to the type of this + /// converter. + /// The converted value. + /// + /// The conversion cannot be performed. + /// + public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) + { + string stringValue = value as string; + if (value is T) + { + return new Nullable((T)value); + } + else if (string.IsNullOrEmpty(stringValue) || String.Equals(stringValue, "Auto", StringComparison.OrdinalIgnoreCase)) + { + return new Nullable(); + } + return new Nullable((T)Convert.ChangeType(value, typeof(T), culture)); + } + + /// + /// Converts from the specified value to the a specified type from the + /// type of this converter. + /// + /// An object that provides a format context. + /// + /// The + /// to use as the + /// current culture. + /// The value to convert to the type of this + /// converter. + /// The type of convert the value to + /// . + /// The converted value. + /// + /// The conversion cannot be performed. + /// + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + if (value == null) + { + return string.Empty; + } + else if (destinationType == typeof(string)) + { + return value.ToString(); + } + return base.ConvertTo(context, culture, value, destinationType); + } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/NumericAxis.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/NumericAxis.cs new file mode 100644 index 00000000..a3675d5a --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/NumericAxis.cs @@ -0,0 +1,289 @@ +// (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; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Shapes; +using EF = System.Windows.Controls.DataVisualization.EnumerableFunctions; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// An axis that displays numeric values. + /// + public abstract class NumericAxis : RangeAxis + { + #region public double? ActualMaximum + /// + /// Gets the actual maximum value plotted on the chart. + /// + public double? ActualMaximum + { + get { return (double?)GetValue(ActualMaximumProperty); } + private set { SetValue(ActualMaximumProperty, value); } + } + + /// + /// Identifies the ActualMaximum dependency property. + /// + public static readonly DependencyProperty ActualMaximumProperty = + DependencyProperty.Register( + "ActualMaximum", + typeof(double?), + typeof(NumericAxis), + null); + + #endregion public double? ActualMaximum + + #region public double? ActualMinimum + /// + /// Gets the actual maximum value plotted on the chart. + /// + public double? ActualMinimum + { + get { return (double?)GetValue(ActualMinimumProperty); } + private set { SetValue(ActualMinimumProperty, value); } + } + + /// + /// Identifies the ActualMinimum dependency property. + /// + public static readonly DependencyProperty ActualMinimumProperty = + DependencyProperty.Register( + "ActualMinimum", + typeof(double?), + typeof(NumericAxis), + null); + + #endregion public double? ActualMinimum + + #region public double? Maximum + /// + /// Gets or sets the maximum value plotted on the axis. + /// + [TypeConverter(typeof(NullableConverter))] + public double? Maximum + { + get { return (double?)GetValue(MaximumProperty); } + set { SetValue(MaximumProperty, value); } + } + + /// + /// Identifies the Maximum dependency property. + /// + public static readonly DependencyProperty MaximumProperty = + DependencyProperty.Register( + "Maximum", + typeof(double?), + typeof(NumericAxis), + new PropertyMetadata(null, OnMaximumPropertyChanged)); + + /// + /// MaximumProperty property changed handler. + /// + /// NumericAxis that changed its Maximum. + /// Event arguments. + private static void OnMaximumPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + NumericAxis source = (NumericAxis)d; + double? newValue = (double?)e.NewValue; + source.OnMaximumPropertyChanged(newValue); + } + + /// + /// MaximumProperty property changed handler. + /// + /// New value. + protected virtual void OnMaximumPropertyChanged(double? newValue) + { + this.ProtectedMaximum = newValue; + } + #endregion public double? Maximum + + #region public double? Minimum + /// + /// Gets or sets the minimum value to plot on the axis. + /// + [TypeConverter(typeof(NullableConverter))] + public double? Minimum + { + get { return (double?)GetValue(MinimumProperty); } + set { SetValue(MinimumProperty, value); } + } + + /// + /// Identifies the Minimum dependency property. + /// + public static readonly DependencyProperty MinimumProperty = + DependencyProperty.Register( + "Minimum", + typeof(double?), + typeof(NumericAxis), + new PropertyMetadata(null, OnMinimumPropertyChanged)); + + /// + /// MinimumProperty property changed handler. + /// + /// NumericAxis that changed its Minimum. + /// Event arguments. + private static void OnMinimumPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + NumericAxis source = (NumericAxis)d; + double? newValue = (double?)e.NewValue; + source.OnMinimumPropertyChanged(newValue); + } + + /// + /// MinimumProperty property changed handler. + /// + /// New value. + protected virtual void OnMinimumPropertyChanged(double? newValue) + { + this.ProtectedMinimum = newValue; + } + #endregion public double? Minimum + + #region public bool ExtendRangeToOrigin + /// + /// Gets or sets a value indicating whether to always show the origin. + /// + public bool ExtendRangeToOrigin + { + get { return (bool)GetValue(ExtendRangeToOriginProperty); } + set { SetValue(ExtendRangeToOriginProperty, value); } + } + + /// + /// Identifies the ExtendRangeToOrigin dependency property. + /// + public static readonly DependencyProperty ExtendRangeToOriginProperty = + DependencyProperty.Register( + "ExtendRangeToOrigin", + typeof(bool), + typeof(NumericAxis), + new PropertyMetadata(false, OnExtendRangeToOriginPropertyChanged)); + + /// + /// ExtendRangeToOriginProperty property changed handler. + /// + /// NumericAxis that changed its ExtendRangeToOrigin. + /// Event arguments. + private static void OnExtendRangeToOriginPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + NumericAxis source = (NumericAxis)d; + bool oldValue = (bool)e.OldValue; + bool newValue = (bool)e.NewValue; + source.OnExtendRangeToOriginPropertyChanged(oldValue, newValue); + } + + /// + /// ExtendRangeToOriginProperty property changed handler. + /// + /// Old value. + /// New value. + protected virtual void OnExtendRangeToOriginPropertyChanged(bool oldValue, bool newValue) + { + this.ActualRange = this.OverrideDataRange(this.ActualRange); + } + #endregion public bool ExtendRangeToOrigin + + /// + /// Gets the origin value on the axis. + /// + protected override IComparable Origin + { + get { return 0.0; } + } + + /// + /// Instantiates a new instance of the NumericAxis class. + /// + protected NumericAxis() + { + } + + /// + /// Updates the typed actual maximum and minimum properties when the + /// actual range changes. + /// + /// The actual range. + protected override void OnActualRangeChanged(Range range) + { + if (range.HasData) + { + this.ActualMaximum = (double)range.Maximum; + this.ActualMinimum = (double)range.Minimum; + } + else + { + this.ActualMaximum = null; + this.ActualMinimum = null; + } + + base.OnActualRangeChanged(range); + } + + /// + /// Returns a value indicating whether a value can plot. + /// + /// The value to plot. + /// A value indicating whether a value can plot. + public override bool CanPlot(object value) + { + double val; + return ValueHelper.TryConvert(value, out val); + } + + /// + /// Returns a numeric axis label. + /// + /// A numeric axis label. + protected override Control CreateAxisLabel() + { + return new NumericAxisLabel(); + } + + /// + /// Overrides the data value range and returns a range that takes the + /// margins of the values into account. + /// + /// The range of data values. + /// A range that can store both the data values and their + /// margins. + protected override Range OverrideDataRange(Range range) + { + range = base.OverrideDataRange(range); + + if (ExtendRangeToOrigin) + { + Range adjustedRange = range.ToDoubleRange(); + + if (!adjustedRange.HasData) + { + return new Range(0.0, 0.0); + } + else + { + double minimum = adjustedRange.Minimum; + double maximum = adjustedRange.Maximum; + if (minimum > 0.0) + { + minimum = 0.0; + } + else if (maximum < 0.0) + { + maximum = 0.0; + } + return new Range(minimum, maximum); + } + } + return range; + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/NumericAxisLabel.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/NumericAxisLabel.cs new file mode 100644 index 00000000..9a7dce6b --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/NumericAxisLabel.cs @@ -0,0 +1,36 @@ +// (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.Windows; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// A label used to display numeric axis values. + /// + public class NumericAxisLabel : AxisLabel + { +#if !SILVERLIGHT + /// + /// Initializes the static members of the NumericAxisLabel class. + /// + static NumericAxisLabel() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericAxisLabel), new FrameworkPropertyMetadata(typeof(NumericAxisLabel))); + } + +#endif + + /// + /// Instantiates a new instance of the NumericAxisLabel class. + /// + public NumericAxisLabel() + { +#if SILVERLIGHT + this.DefaultStyleKey = typeof(NumericAxisLabel); +#endif + } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/OrientedAxisGridLines.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/OrientedAxisGridLines.cs new file mode 100644 index 00000000..2ffd5369 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/OrientedAxisGridLines.cs @@ -0,0 +1,90 @@ +// (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; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Shapes; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// This control draws gridlines with the help of an axis. + /// + [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "GridLine", Justification = "This is the expected capitalization.")] + [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "GridLines", Justification = "This is the expected capitalization.")] + internal class OrientedAxisGridLines : DisplayAxisGridLines + { + /// + /// A pool of grid lines. + /// + private ObjectPool _gridLinePool; + + /// + /// Initializes a new instance of the OrientedAxisGridLines class. + /// + /// The axis to draw grid lines for. + public OrientedAxisGridLines(DisplayAxis displayAxis) + : base(displayAxis) + { + _gridLinePool = new ObjectPool(() => new Line { Style = Axis.GridLineStyle }); + } + + /// + /// Draws the grid lines. + /// + protected override void Invalidate() + { + _gridLinePool.Reset(); + + try + { + IList intervals = Axis.InternalGetMajorGridLinePositions().ToList(); + + this.Children.Clear(); + + double maximumHeight = Math.Max(Math.Round(ActualHeight - 1), 0); + double maximumWidth = Math.Max(Math.Round(ActualWidth - 1), 0); + for (int index = 0; index < intervals.Count; index++) + { + double currentValue = intervals[index].Value; + + double position = currentValue; + if (!double.IsNaN(position)) + { + Line line = _gridLinePool.Next(); + if (Axis.Orientation == AxisOrientation.Y) + { + line.Y1 = line.Y2 = maximumHeight - Math.Round(position - (line.StrokeThickness / 2)); + line.X1 = 0.0; + line.X2 = maximumWidth; + } + else if (Axis.Orientation == AxisOrientation.X) + { + line.X1 = line.X2 = Math.Round(position - (line.StrokeThickness / 2)); + line.Y1 = 0.0; + line.Y2 = maximumHeight; + } + // workaround for '1px line thickness issue' + if (line.StrokeThickness % 2 > 0) + { + line.SetValue(Canvas.LeftProperty, 0.5); + line.SetValue(Canvas.TopProperty, 0.5); + } + this.Children.Add(line); + } + } + } + finally + { + _gridLinePool.Done(); + } + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/RangeAxis.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/RangeAxis.cs new file mode 100644 index 00000000..88f75ac6 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/RangeAxis.cs @@ -0,0 +1,622 @@ +// (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; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Windows; +using System.Windows.Media; +using System.Windows.Controls; +using System.Windows.Shapes; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// An axis that has a range. + /// + public abstract class RangeAxis : DisplayAxis, IRangeAxis, IValueMarginConsumer + { + /// + /// A pool of major tick marks. + /// + private ObjectPool _majorTickMarkPool; + + /// + /// A pool of major tick marks. + /// + private ObjectPool _minorTickMarkPool; + + /// + /// A pool of labels. + /// + private ObjectPool _labelPool; + + #region public Style MinorTickMarkStyle + /// + /// Gets or sets the minor tick mark style. + /// + public Style MinorTickMarkStyle + { + get { return GetValue(MinorTickMarkStyleProperty) as Style; } + set { SetValue(MinorTickMarkStyleProperty, value); } + } + + /// + /// Identifies the MinorTickMarkStyle dependency property. + /// + public static readonly DependencyProperty MinorTickMarkStyleProperty = + DependencyProperty.Register( + "MinorTickMarkStyle", + typeof(Style), + typeof(RangeAxis), + new PropertyMetadata(null)); + + #endregion public Style MinorTickMarkStyle + + /// + /// The actual range of values. + /// + private Range _actualRange; + + /// + /// Gets or sets the actual range of values. + /// + protected Range ActualRange + { + get + { + return _actualRange; + } + set + { + Range oldValue = _actualRange; + Range minMaxEnforcedValue = EnforceMaximumAndMinimum(value); + + if (!oldValue.Equals(minMaxEnforcedValue)) + { + _actualRange = minMaxEnforcedValue; + OnActualRangeChanged(minMaxEnforcedValue); + } + } + } + + /// + /// The maximum value displayed in the range axis. + /// + private IComparable _protectedMaximum; + + /// + /// Gets or sets the maximum value displayed in the range axis. + /// + protected IComparable ProtectedMaximum + { + get + { + return _protectedMaximum; + } + set + { + if (value != null && ProtectedMinimum != null && ValueHelper.Compare(ProtectedMinimum, value) > 0) + { + throw new InvalidOperationException(Properties.Resources.RangeAxis_MaximumValueMustBeLargerThanOrEqualToMinimumValue); + } + if (!object.ReferenceEquals(_protectedMaximum, value) && !object.Equals(_protectedMaximum, value)) + { + _protectedMaximum = value; + UpdateActualRange(); + } + } + } + + /// + /// The minimum value displayed in the range axis. + /// + private IComparable _protectedMinimum; + + /// + /// Gets or sets the minimum value displayed in the range axis. + /// + protected IComparable ProtectedMinimum + { + get + { + return _protectedMinimum; + } + set + { + if (value != null && ProtectedMaximum != null && ValueHelper.Compare(value, ProtectedMaximum) > 0) + { + throw new InvalidOperationException(Properties.Resources.RangeAxis_MinimumValueMustBeLargerThanOrEqualToMaximumValue); + } + if (!object.ReferenceEquals(_protectedMinimum, value) && !object.Equals(_protectedMinimum, value)) + { + _protectedMinimum = value; + UpdateActualRange(); + } + } + } + +#if !SILVERLIGHT + /// + /// Initializes the static members of the RangeAxis class. + /// + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Dependency properties are initialized in-line.")] + static RangeAxis() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(RangeAxis), new FrameworkPropertyMetadata(typeof(RangeAxis))); + } + +#endif + /// + /// Instantiates a new instance of the RangeAxis class. + /// + protected RangeAxis() + { +#if SILVERLIGHT + this.DefaultStyleKey = typeof(RangeAxis); +#endif + this._labelPool = new ObjectPool(() => CreateAxisLabel()); + this._majorTickMarkPool = new ObjectPool(() => CreateMajorTickMark()); + this._minorTickMarkPool = new ObjectPool(() => CreateMinorTickMark()); + + // Update actual range when size changes for the first time. This + // is necessary because the value margins may have changed after + // the first layout pass. + SizeChangedEventHandler handler = null; + handler = delegate + { + SizeChanged -= handler; + UpdateActualRange(); + }; + SizeChanged += handler; + } + + /// + /// Creates a minor axis tick mark. + /// + /// A line to used to render a tick mark. + protected Line CreateMinorTickMark() + { + return CreateTickMark(MinorTickMarkStyle); + } + + /// + /// Invalidates axis when the actual range changes. + /// + /// The new actual range. + protected virtual void OnActualRangeChanged(Range range) + { + Invalidate(); + } + + /// + /// Returns the plot area coordinate of a given value. + /// + /// The value to return the plot area coordinate for. + /// The plot area coordinate of the given value. + public override UnitValue GetPlotAreaCoordinate(object value) + { + if (value == null) + { + throw new ArgumentNullException("value"); + } + + return GetPlotAreaCoordinate(value, ActualLength); + } + + /// + /// Returns the plot area coordinate of a given value. + /// + /// The value to return the plot area coordinate for. + /// The length of the axis. + /// The plot area coordinate of the given value. + protected abstract UnitValue GetPlotAreaCoordinate(object value, double length); + + /// + /// Returns the plot area coordinate of a given value. + /// + /// The value to return the plot area coordinate for. + /// The value range to use when calculating the plot area coordinate. + /// The length of the axis. + /// The plot area coordinate of the given value. + protected abstract UnitValue GetPlotAreaCoordinate(object value, Range currentRange, double length); + + /// + /// Overrides the data range. + /// + /// The range to potentially override. + /// The overridden range. + protected virtual Range OverrideDataRange(Range range) + { + return range; + } + + /// + /// Modifies a range to respect the minimum and maximum axis values. + /// + /// The range of data. + /// A range modified to respect the minimum and maximum axis + /// values. + private Range EnforceMaximumAndMinimum(Range range) + { + if (range.HasData) + { + IComparable minimum = ProtectedMinimum ?? range.Minimum; + IComparable maximum = ProtectedMaximum ?? range.Maximum; + + if (ValueHelper.Compare(minimum, maximum) > 0) + { + IComparable temp = maximum; + maximum = minimum; + minimum = temp; + } + + return new Range(minimum, maximum); + } + else + { + IComparable minimum = ProtectedMinimum; + IComparable maximum = ProtectedMaximum; + if (ProtectedMinimum != null && ProtectedMaximum == null) + { + maximum = minimum; + } + else if (ProtectedMaximum != null && ProtectedMinimum == null) + { + minimum = maximum; + } + else + { + return range; + } + return new Range(minimum, maximum); + } + } + + /// + /// Updates the actual range displayed on the axis. + /// + private void UpdateActualRange() + { + Action action = () => + { + Range dataRange; + if (ProtectedMaximum == null || ProtectedMinimum == null) + { + if (Orientation == AxisOrientation.None) + { + if (ProtectedMinimum != null) + { + this.ActualRange = OverrideDataRange(new Range(ProtectedMinimum, ProtectedMinimum)); + } + else + { + this.ActualRange = OverrideDataRange(new Range(ProtectedMaximum, ProtectedMaximum)); + } + } + else + { + dataRange = + this.RegisteredListeners + .OfType() + .Select(rangeProvider => rangeProvider.GetRange(this)) + .Sum(); + + this.ActualRange = OverrideDataRange(dataRange); + } + } + else + { + this.ActualRange = new Range(ProtectedMinimum, ProtectedMaximum); + } + }; + + // Repeat this after layout pass. + if (this.ActualLength == 0.0) + { + this.Dispatcher.BeginInvoke(action); + } + + action(); + } + + /// + /// Renders the axis as an oriented axis. + /// + /// The available size. + private void RenderOriented(Size availableSize) + { + _minorTickMarkPool.Reset(); + _majorTickMarkPool.Reset(); + _labelPool.Reset(); + + double length = GetLength(availableSize); + try + { + OrientedPanel.Children.Clear(); + if (ActualRange.HasData && !Object.Equals(ActualRange.Minimum, ActualRange.Maximum)) + { + foreach (IComparable axisValue in GetMajorTickMarkValues(availableSize)) + { + UnitValue coordinate = GetPlotAreaCoordinate(axisValue, length); + if (ValueHelper.CanGraph(coordinate.Value)) + { + Line line = _majorTickMarkPool.Next(); + OrientedPanel.SetCenterCoordinate(line, coordinate.Value); + OrientedPanel.SetPriority(line, 0); + OrientedPanel.Children.Add(line); + } + } + + foreach (IComparable axisValue in GetMinorTickMarkValues(availableSize)) + { + UnitValue coordinate = GetPlotAreaCoordinate(axisValue, length); + if (ValueHelper.CanGraph(coordinate.Value)) + { + Line line = _minorTickMarkPool.Next(); + OrientedPanel.SetCenterCoordinate(line, coordinate.Value); + OrientedPanel.SetPriority(line, 0); + OrientedPanel.Children.Add(line); + } + } + + int count = 0; + foreach (IComparable axisValue in GetLabelValues(availableSize)) + { + UnitValue coordinate = GetPlotAreaCoordinate(axisValue, length); + if (ValueHelper.CanGraph(coordinate.Value)) + { + Control axisLabel = _labelPool.Next(); + PrepareAxisLabel(axisLabel, axisValue); + OrientedPanel.SetCenterCoordinate(axisLabel, coordinate.Value); + OrientedPanel.SetPriority(axisLabel, count + 1); + OrientedPanel.Children.Add(axisLabel); + count = (count + 1) % 2; + } + } + } + } + finally + { + _minorTickMarkPool.Done(); + _majorTickMarkPool.Done(); + _labelPool.Done(); + } + } + + /// + /// Renders the axis labels, tick marks, and other visual elements. + /// + /// The available size. + protected override void Render(Size availableSize) + { + RenderOriented(availableSize); + } + + /// + /// Returns a sequence of the major grid line coordinates. + /// + /// The available size. + /// A sequence of the major grid line coordinates. + protected override IEnumerable GetMajorGridLineCoordinates(Size availableSize) + { + return GetMajorTickMarkValues(availableSize).Select(value => GetPlotAreaCoordinate(value)).Where(value => ValueHelper.CanGraph(value.Value)); + } + + /// + /// Returns a sequence of the values at which to plot major grid lines. + /// + /// The available size. + /// A sequence of the values at which to plot major grid lines. + /// + [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "GridLine", Justification = "This is the expected capitalization.")] + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This method may do a lot of work and is therefore not a suitable candidate for a property.")] + protected virtual IEnumerable GetMajorGridLineValues(Size availableSize) + { + return GetMajorTickMarkValues(availableSize); + } + + /// + /// Returns a sequence of values to plot on the axis. + /// + /// The available size. + /// A sequence of values to plot on the axis. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This method may do a lot of work and is therefore not a suitable candidate for a property.")] + protected abstract IEnumerable GetMajorTickMarkValues(Size availableSize); + + /// + /// Returns a sequence of values to plot on the axis. + /// + /// The available size. + /// A sequence of values to plot on the axis. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This method may do a lot of work and is therefore not a suitable candidate for a property.")] + protected virtual IEnumerable GetMinorTickMarkValues(Size availableSize) + { + yield break; + } + + /// + /// Returns a sequence of values to plot on the axis. + /// + /// The available size. + /// A sequence of values to plot on the axis. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This method may do a lot of work and is therefore not a suitable candidate for a property.")] + protected abstract IEnumerable GetLabelValues(Size availableSize); + + /// + /// Returns the value range given a plot area coordinate. + /// + /// The plot area coordinate. + /// A range of values at that plot area coordinate. + protected abstract IComparable GetValueAtPosition(UnitValue value); + + /// + /// Gets the actual maximum value. + /// + Range IRangeAxis.Range + { + get { return ActualRange; } + } + + /// + /// Returns the value range given a plot area coordinate. + /// + /// The plot area coordinate. + /// A range of values at that plot area coordinate. + IComparable IRangeAxis.GetValueAtPosition(UnitValue value) + { + return GetValueAtPosition(value); + } + + /// + /// Updates the axis with information about a provider's data range. + /// + /// The information provider. + /// The range of data in the information provider. + /// + void IRangeConsumer.RangeChanged(IRangeProvider usesRangeAxis, Range range) + { + UpdateActualRange(); + } + + /// + /// Updates the layout of the axis to accommodate a sequence of value + /// margins. + /// + /// A value margin provider. + /// A sequence of value margins. + void IValueMarginConsumer.ValueMarginsChanged(IValueMarginProvider provider, IEnumerable valueMargins) + { + Action action = () => + { + if (this.Orientation != AxisOrientation.None) + { + // Determine if any of the value margins are outside the axis + // area. If so update range. + bool updateRange = + valueMargins + .Select( + valueMargin => + { + double coordinate = GetPlotAreaCoordinate(valueMargin.Value).Value; + return new Range(coordinate - valueMargin.LowMargin, coordinate + valueMargin.HighMargin); + }) + .Where(range => range.Minimum < 0 || range.Maximum > this.ActualLength) + .Any(); + + if (updateRange) + { + UpdateActualRange(); + } + } + }; + + // Repeat this after layout pass. + if (this.ActualLength == 0) + { + this.Dispatcher.BeginInvoke(action); + } + else + { + action(); + } + } + + /// + /// If a new range provider is registered, update actual range. + /// + /// The axis listener being registered. + protected override void OnObjectRegistered(IAxisListener series) + { + base.OnObjectRegistered(series); + if (series is IRangeProvider || series is IValueMarginProvider) + { + UpdateActualRange(); + } + } + + /// + /// If a range provider is unregistered, update actual range. + /// + /// The axis listener being unregistered. + protected override void OnObjectUnregistered(IAxisListener series) + { + base.OnObjectUnregistered(series); + if (series is IRangeProvider || series is IValueMarginProvider) + { + UpdateActualRange(); + } + } + + /// + /// Create function that when given a range will return the + /// amount in pixels by which the value margin range + /// overlaps. Positive numbers represent values outside the + /// range. + /// + /// The list of value margins, coordinates, and overlaps. + /// The new range to use to calculate coordinates. + internal void UpdateValueMargins(IList valueMargins, Range comparableRange) + { + double actualLength = this.ActualLength; + int valueMarginsCount = valueMargins.Count; + for (int count = 0; count < valueMarginsCount; count++) + { + ValueMarginCoordinateAndOverlap item = valueMargins[count]; + item.Coordinate = GetPlotAreaCoordinate(item.ValueMargin.Value, comparableRange, actualLength).Value; + item.LeftOverlap = -(item.Coordinate - item.ValueMargin.LowMargin); + item.RightOverlap = (item.Coordinate + item.ValueMargin.HighMargin) - actualLength; + } + } + + /// + /// Returns the value margin, coordinate, and overlap triples that have the largest left and right overlap. + /// + /// The list of value margin, coordinate, and + /// overlap triples. + /// The value margin, + /// coordinate, and overlap triple that has the largest left overlap. + /// + /// The value margin, + /// coordinate, and overlap triple that has the largest right overlap. + /// + internal static void GetMaxLeftAndRightOverlap(IList valueMargins, out ValueMarginCoordinateAndOverlap maxLeftOverlapValueMargin, out ValueMarginCoordinateAndOverlap maxRightOverlapValueMargin) + { + maxLeftOverlapValueMargin = new ValueMarginCoordinateAndOverlap(); + maxRightOverlapValueMargin = new ValueMarginCoordinateAndOverlap(); + double maxLeftOverlap = double.MinValue; + double maxRightOverlap = double.MinValue; + int valueMarginsCount = valueMargins.Count; + for (int cnt = 0; cnt < valueMarginsCount; cnt++) + { + ValueMarginCoordinateAndOverlap valueMargin = valueMargins[cnt]; + double leftOverlap = valueMargin.LeftOverlap; + if (leftOverlap > maxLeftOverlap) + { + maxLeftOverlap = leftOverlap; + maxLeftOverlapValueMargin = valueMargin; + } + double rightOverlap = valueMargin.RightOverlap; + if (rightOverlap > maxRightOverlap) + { + maxRightOverlap = rightOverlap; + maxRightOverlapValueMargin = valueMargin; + } + } + } + + /// + /// Gets the origin value on the axis. + /// + IComparable IRangeAxis.Origin { get { return this.Origin; } } + + /// + /// Gets the origin value on the axis. + /// + protected abstract IComparable Origin { get; } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/ValueMargin.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/ValueMargin.cs new file mode 100644 index 00000000..8fe7a5ae --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Axis/ValueMargin.cs @@ -0,0 +1,90 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// A margin specified for a given value. + /// + public struct ValueMargin + { + /// + /// Gets the value that the margin is associated with. + /// + public object Value { get; private set; } + + /// + /// Gets the low margin for a value. + /// + public double LowMargin { get; private set; } + + /// + /// Gets the high margin for a value. + /// + public double HighMargin { get; private set; } + + /// + /// Initializes a new instance of the ValueMargin class. + /// + /// The value the margin is associated with. + /// The lower margin. + /// The higher margin. + public ValueMargin(object value, double lowMargin, double highMargin) : this() + { + Value = value; + LowMargin = lowMargin; + HighMargin = highMargin; + } + + /// + /// Determines whether two value margins are equal. + /// + /// The value margin to compare with this one. + /// A value indicating whether the two value margins are equal. + /// + public override bool Equals(object obj) + { + if (obj is ValueMargin) + { + ValueMargin valueMargin = (ValueMargin)obj; + return this.Value.Equals(valueMargin.Value) && this.LowMargin.Equals(valueMargin.LowMargin) && this.HighMargin.Equals(valueMargin.HighMargin); + } + return false; + } + + /// + /// Determines whether two unit value objects are equal. + /// + /// The left value margin. + /// The right value margin. + /// A value indicating whether two value margins objects are + /// equal. + public static bool operator ==(ValueMargin left, ValueMargin right) + { + return left.Equals(right); + } + + /// + /// Determines whether two value margin objects are not equal. + /// + /// The left value margin. + /// The right value margin. + /// A value indicating whether two value margin objects are not + /// equal. + public static bool operator !=(ValueMargin left, ValueMargin right) + { + return !left.Equals(right); + } + + /// + /// Returns the hash code of the value margin object. + /// + /// The hash code. + public override int GetHashCode() + { + return this.Value.GetHashCode() ^ this.LowMargin.GetHashCode() ^ this.HighMargin.GetHashCode(); + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Chart/Chart.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Chart/Chart.cs new file mode 100644 index 00000000..c2bef500 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Chart/Chart.cs @@ -0,0 +1,747 @@ +// (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.CodeAnalysis; +using System.Linq; +using System.Windows.Controls.DataVisualization.Charting.Primitives; +using System.Windows.Markup; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Represents a control that displays a Chart. + /// + /// Preview + [TemplatePart(Name = Chart.ChartAreaName, Type = typeof(EdgePanel))] + [TemplatePart(Name = Chart.LegendName, Type = typeof(Legend))] + [StyleTypedProperty(Property = "TitleStyle", StyleTargetType = typeof(Title))] + [StyleTypedProperty(Property = "LegendStyle", StyleTargetType = typeof(Legend))] + [StyleTypedProperty(Property = "ChartAreaStyle", StyleTargetType = typeof(EdgePanel))] + [StyleTypedProperty(Property = "PlotAreaStyle", StyleTargetType = typeof(Grid))] + [ContentProperty("Series")] + public partial class Chart : Control, ISeriesHost + { + /// + /// Specifies the name of the ChartArea TemplatePart. + /// + private const string ChartAreaName = "ChartArea"; + + /// + /// Specifies the name of the legend TemplatePart. + /// + private const string LegendName = "Legend"; + + /// + /// Gets or sets the chart area children collection. + /// + private AggregatedObservableCollection ChartAreaChildren { get; set; } + + /// + /// An adapter that synchronizes changes to the ChartAreaChildren + /// property to the ChartArea panel's children collection. + /// + private ObservableCollectionListAdapter _chartAreaChildrenListAdapter = new ObservableCollectionListAdapter(); + + /// + /// Gets or sets a collection of Axes in the Chart. + /// + [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 Axes + { + get + { + return _axes; + } + set + { + throw new NotSupportedException(Properties.Resources.Chart_Axes_SetterNotSupported); + } + } + + /// + /// Stores the collection of Axes in the Chart. + /// + private Collection _axes; + + /// + /// The collection of foreground elements. + /// + private ObservableCollection _foregroundElements = new NoResetObservableCollection(); + + /// + /// The collection of background elements. + /// + private ObservableCollection _backgroundElements = new NoResetObservableCollection(); + + /// + /// Gets the collection of foreground elements. + /// + ObservableCollection ISeriesHost.ForegroundElements { get { return ForegroundElements; } } + + /// + /// Gets the collection of foreground elements. + /// + protected ObservableCollection ForegroundElements { get { return _foregroundElements; } } + + /// + /// Gets the collection of background elements. + /// + ObservableCollection ISeriesHost.BackgroundElements { get { return BackgroundElements; } } + + /// + /// Gets the collection of background elements. + /// + protected ObservableCollection BackgroundElements { get { return _backgroundElements; } } + + /// + /// Axes arranged along the edges. + /// + private ObservableCollection _edgeAxes = new NoResetObservableCollection(); + + /// + /// Gets or sets the axes that are currently in the chart. + /// + private IList InternalActualAxes { get; set; } + + /// + /// Gets the actual axes displayed in the chart. + /// + public ReadOnlyCollection ActualAxes { get; private set; } + + /// + /// Gets or sets the reference to the template's ChartArea. + /// + private EdgePanel ChartArea { get; set; } + + /// + /// Gets or sets the reference to the Chart's Legend. + /// + private Legend Legend { get; set; } + + /// + /// Gets or sets the collection of Series displayed by the Chart. + /// + [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 Series + { + get + { + return _series; + } + set + { + throw new NotSupportedException(Properties.Resources.Chart_Series_SetterNotSupported); + } + } + + /// + /// Stores the collection of Series displayed by the Chart. + /// + private Collection _series; + + #region public Style ChartAreaStyle + /// + /// Gets or sets the Style of the ISeriesHost's ChartArea. + /// + public Style ChartAreaStyle + { + get { return GetValue(ChartAreaStyleProperty) as Style; } + set { SetValue(ChartAreaStyleProperty, value); } + } + + /// + /// Identifies the ChartAreaStyle dependency property. + /// + public static readonly DependencyProperty ChartAreaStyleProperty = + DependencyProperty.Register( + "ChartAreaStyle", + typeof(Style), + typeof(Chart), + null); + #endregion public Style ChartAreaStyle + + /// + /// Gets the collection of legend items. + /// + public Collection LegendItems { get; private set; } + + #region public Style LegendStyle + /// + /// Gets or sets the Style of the ISeriesHost's Legend. + /// + public Style LegendStyle + { + get { return GetValue(LegendStyleProperty) as Style; } + set { SetValue(LegendStyleProperty, value); } + } + + /// + /// Identifies the LegendStyle dependency property. + /// + public static readonly DependencyProperty LegendStyleProperty = + DependencyProperty.Register( + "LegendStyle", + typeof(Style), + typeof(Chart), + null); + #endregion public Style LegendStyle + + #region public object LegendTitle + /// + /// Gets or sets the Title content of the Legend. + /// + public object LegendTitle + { + get { return GetValue(LegendTitleProperty); } + set { SetValue(LegendTitleProperty, value); } + } + + /// + /// Identifies the LegendTitle dependency property. + /// + public static readonly DependencyProperty LegendTitleProperty = + DependencyProperty.Register( + "LegendTitle", + typeof(object), + typeof(Chart), + null); + #endregion public object LegendTitle + + #region public Style PlotAreaStyle + /// + /// Gets or sets the Style of the ISeriesHost's PlotArea. + /// + public Style PlotAreaStyle + { + get { return GetValue(PlotAreaStyleProperty) as Style; } + set { SetValue(PlotAreaStyleProperty, value); } + } + + /// + /// Identifies the PlotAreaStyle dependency property. + /// + public static readonly DependencyProperty PlotAreaStyleProperty = + DependencyProperty.Register( + "PlotAreaStyle", + typeof(Style), + typeof(Chart), + null); + #endregion public Style PlotAreaStyle + + #region public Collection Palette + /// + /// Gets or sets a palette of ResourceDictionaries used by the children of the Chart. + /// + [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "Want to allow this to be set from XAML.")] + public Collection Palette + { + get { return GetValue(PaletteProperty) as Collection; } + set { SetValue(PaletteProperty, value); } + } + + /// + /// Identifies the Palette dependency property. + /// + public static readonly DependencyProperty PaletteProperty = + DependencyProperty.Register( + "Palette", + typeof(Collection), + typeof(Chart), + new PropertyMetadata(OnPalettePropertyChanged)); + + /// + /// Called when the value of the Palette property is changed. + /// + /// Chart that contains the changed Palette. + /// + /// Event arguments. + private static void OnPalettePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + Chart source = (Chart) d; + Collection newValue = (Collection)e.NewValue; + source.OnPalettePropertyChanged(newValue); + } + + /// + /// Called when the value of the Palette property is changed. + /// + /// The new value for the Palette. + private void OnPalettePropertyChanged(Collection newValue) + { + ResourceDictionaryDispenser.ResourceDictionaries = newValue; + } + #endregion public Collection Palette + + /// + /// Gets or sets an object that rotates through the palette. + /// + private ResourceDictionaryDispenser ResourceDictionaryDispenser { get; set; } + + /// + /// Event that is invoked when the ResourceDictionaryDispenser's collection has changed. + /// + public event EventHandler ResourceDictionariesChanged; + + #region public object Title + /// + /// Gets or sets the title displayed for the Chart. + /// + public object Title + { + get { return GetValue(TitleProperty); } + set { SetValue(TitleProperty, value); } + } + + /// + /// Identifies the Title dependency property. + /// + public static readonly DependencyProperty TitleProperty = + DependencyProperty.Register( + "Title", + typeof(object), + typeof(Chart), + null); + #endregion + + #region public Style TitleStyle + /// + /// Gets or sets the Style of the ISeriesHost's Title. + /// + public Style TitleStyle + { + get { return GetValue(TitleStyleProperty) as Style; } + set { SetValue(TitleStyleProperty, value); } + } + + /// + /// Identifies the TitleStyle dependency property. + /// + public static readonly DependencyProperty TitleStyleProperty = + DependencyProperty.Register( + "TitleStyle", + typeof(Style), + typeof(Chart), + null); + #endregion public Style TitleStyle + +#if !SILVERLIGHT + /// + /// Initializes the static members of the Chart class. + /// + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Dependency properties are initialized in-line.")] + static Chart() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(Chart), new FrameworkPropertyMetadata(typeof(Chart))); + } + +#endif + /// + /// Initializes a new instance of the Chart class. + /// + public Chart() + { +#if SILVERLIGHT + DefaultStyleKey = typeof(Chart); +#endif + // Create the backing collection for Series + UniqueObservableCollection series = new UniqueObservableCollection(); + series.CollectionChanged += new NotifyCollectionChangedEventHandler(SeriesCollectionChanged); + _series = series; + + // Create the backing collection for Axes + UniqueObservableCollection axes = new UniqueObservableCollection(); + _axes = axes; + + ObservableCollection actualAxes = new SeriesHostAxesCollection(this, axes); + actualAxes.CollectionChanged += ActualAxesCollectionChanged; + this.InternalActualAxes = actualAxes; + this.ActualAxes = new ReadOnlyCollection(InternalActualAxes); + + // Create collection for LegendItems + LegendItems = new AggregatedObservableCollection(); + + ChartAreaChildren = new AggregatedObservableCollection(); + ChartAreaChildren.ChildCollections.Add(_edgeAxes); + ChartAreaChildren.ChildCollections.Add(_backgroundElements); + ChartAreaChildren.ChildCollections.Add(Series); + ChartAreaChildren.ChildCollections.Add(_foregroundElements); + + _chartAreaChildrenListAdapter.Collection = ChartAreaChildren; + + // Create a dispenser + ResourceDictionaryDispenser = new ResourceDictionaryDispenser(); + ResourceDictionaryDispenser.ResourceDictionariesChanged += delegate + { + OnResourceDictionariesChanged(EventArgs.Empty); + }; + } + + /// + /// Invokes the ResourceDictionariesChanged event. + /// + /// Event arguments. + private void OnResourceDictionariesChanged(EventArgs e) + { + // Forward event on to listeners + EventHandler handler = ResourceDictionariesChanged; + if (null != handler) + { + handler.Invoke(this, e); + } + } + + /// + /// Determines the location of an axis based on the existing axes in + /// the chart. + /// + /// The axis to determine the location of. + /// The location of the axis. + private AxisLocation GetAutoAxisLocation(Axis axis) + { + if (axis.Orientation == AxisOrientation.X) + { + int numberOfTopAxes = InternalActualAxes.OfType().Where(currentAxis => currentAxis.Location == AxisLocation.Top).Count(); + int numberOfBottomAxes = InternalActualAxes.OfType().Where(currentAxis => currentAxis.Location == AxisLocation.Bottom).Count(); + return (numberOfBottomAxes > numberOfTopAxes) ? AxisLocation.Top : AxisLocation.Bottom; + } + else if (axis.Orientation == AxisOrientation.Y) + { + int numberOfLeftAxes = InternalActualAxes.OfType().Where(currentAxis => currentAxis.Location == AxisLocation.Left).Count(); + int numberOfRightAxes = InternalActualAxes.OfType().Where(currentAxis => currentAxis.Location == AxisLocation.Right).Count(); + return (numberOfLeftAxes > numberOfRightAxes) ? AxisLocation.Right : AxisLocation.Left; + } + else + { + return AxisLocation.Auto; + } + } + + /// + /// Adds an axis to the ISeriesHost area. + /// + /// The axis to add to the ISeriesHost area. + private void AddAxisToChartArea(Axis axis) + { + IRequireSeriesHost requiresSeriesHost = axis as IRequireSeriesHost; + if (requiresSeriesHost != null) + { + requiresSeriesHost.SeriesHost = this; + } + + if (axis.Location == AxisLocation.Auto) + { + axis.Location = GetAutoAxisLocation(axis); + } + + SetEdge(axis); + + axis.LocationChanged += AxisLocationChanged; + axis.OrientationChanged += AxisOrientationChanged; + + if (axis.Location != AxisLocation.Auto) + { + _edgeAxes.Add(axis); + } + } + + /// + /// Rebuilds the chart area if an axis orientation is changed. + /// + /// The source of the event. + /// Information about the event. + private void AxisOrientationChanged(object sender, RoutedPropertyChangedEventArgs args) + { + Axis axis = (Axis)sender; + + axis.Location = GetAutoAxisLocation(axis); + } + + /// + /// Sets the Edge property of an axis based on its location and + /// orientation. + /// + /// The axis to set the edge property of. + private static void SetEdge(Axis axis) + { + switch (axis.Location) + { + case AxisLocation.Bottom: + EdgePanel.SetEdge(axis, Edge.Bottom); + break; + case AxisLocation.Top: + EdgePanel.SetEdge(axis, Edge.Top); + break; + case AxisLocation.Left: + EdgePanel.SetEdge(axis, Edge.Left); + break; + case AxisLocation.Right: + EdgePanel.SetEdge(axis, Edge.Right); + break; + } + } + + /// + /// Rebuild the chart area if an axis location is changed. + /// + /// The source of the event. + /// Information about the event. + private void AxisLocationChanged(object sender, RoutedPropertyChangedEventArgs args) + { + Axis axis = (Axis)sender; + + if (args.NewValue == AxisLocation.Auto) + { + throw new InvalidOperationException(Properties.Resources.Chart_AxisLocationChanged_CantBeChangedToAutoWhenHostedInsideOfASeriesHost); + } + + SetEdge(axis); + + _edgeAxes.Remove(axis); + _edgeAxes.Add(axis); + } + + /// + /// Adds a series to the plot area and injects chart services. + /// + /// The series to add to the plot area. + private void AddSeriesToPlotArea(ISeries series) + { + series.SeriesHost = this; + + AggregatedObservableCollection chartLegendItems = this.LegendItems as AggregatedObservableCollection; + int indexOfSeries = this.Series.IndexOf(series); + chartLegendItems.ChildCollections.Insert(indexOfSeries, series.LegendItems); + } + + /// + /// Builds the visual tree for the Chart control when a new template + /// is applied. + /// + public override void OnApplyTemplate() + { + // Call base implementation + base.OnApplyTemplate(); + + // Unhook events from former template parts + if (null != ChartArea) + { + ChartArea.Children.Clear(); + } + + if (null != Legend) + { + Legend.ItemsSource = null; + } + + // Access new template parts + ChartArea = GetTemplateChild(ChartAreaName) as EdgePanel; + + Legend = GetTemplateChild(LegendName) as Legend; + + if (ChartArea != null) + { + _chartAreaChildrenListAdapter.TargetList = ChartArea.Children; + _chartAreaChildrenListAdapter.Populate(); + } + + if (Legend != null) + { + Legend.ItemsSource = this.LegendItems; + } + } + + /// + /// Ensures that ISeriesHost is in a consistent state when axes collection is + /// changed. + /// + /// Event source. + /// Event arguments. + private void ActualAxesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (e.NewItems != null) + { + foreach (Axis axis in e.NewItems.OfType()) + { + AddAxisToChartArea(axis); + } + } + if (e.OldItems != null) + { + foreach (Axis axis in e.OldItems.OfType()) + { + RemoveAxisFromChartArea(axis); + } + } + } + + /// + /// Removes an axis from the Chart area. + /// + /// The axis to remove from the ISeriesHost area. + private void RemoveAxisFromChartArea(Axis axis) + { + axis.LocationChanged -= AxisLocationChanged; + axis.OrientationChanged -= AxisOrientationChanged; + IRequireSeriesHost requiresSeriesHost = axis as IRequireSeriesHost; + if (requiresSeriesHost != null) + { + requiresSeriesHost.SeriesHost = null; + } + + _edgeAxes.Remove(axis); + } + + /// + /// Removes a series from the plot area. + /// + /// The series to remove from the plot area. + /// + private void RemoveSeriesFromPlotArea(ISeries series) + { + AggregatedObservableCollection legendItemsList = LegendItems as AggregatedObservableCollection; + legendItemsList.ChildCollections.Remove(series.LegendItems); + + series.SeriesHost = null; + } + + /// + /// Called when the ObservableCollection.CollectionChanged property + /// changes. + /// + /// The object that raised the event. + /// The event data. + private void SeriesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + // Clear ISeriesHost property of old Series + if (null != e.OldItems) + { + foreach (ISeries series in e.OldItems) + { + ISeriesHost host = series as ISeriesHost; + if (host != null) + { + foreach (IRequireGlobalSeriesIndex tracksGlobalIndex in host.GetDescendentSeries().OfType()) + { + tracksGlobalIndex.GlobalSeriesIndexChanged(null); + } + host.Series.CollectionChanged -= new NotifyCollectionChangedEventHandler(ChildSeriesCollectionChanged); + } + IRequireGlobalSeriesIndex require = series as IRequireGlobalSeriesIndex; + if (require != null) + { + require.GlobalSeriesIndexChanged(null); + } + + RemoveSeriesFromPlotArea(series); + } + } + + // Set ISeriesHost property of new Series + if (null != e.NewItems) + { + foreach (ISeries series in e.NewItems) + { + ISeriesHost host = series as ISeriesHost; + if (null != host) + { + host.Series.CollectionChanged += new NotifyCollectionChangedEventHandler(ChildSeriesCollectionChanged); + } + AddSeriesToPlotArea(series); + } + } + + if (e.Action != NotifyCollectionChangedAction.Replace) + { + OnGlobalSeriesIndexesInvalidated(this, new RoutedEventArgs()); + } + } + + /// + /// Handles changes to the collections of child ISeries implementing ISeriesHost. + /// + /// Event source. + /// Event arguments. + private void ChildSeriesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + OnGlobalSeriesIndexesInvalidated(this, new RoutedEventArgs()); + } + + /// + /// Returns a rotating enumerator of ResourceDictionary objects that coordinates + /// with the dispenser object to ensure that no two enumerators are on the same + /// item. If the dispenser is reset or its collection is changed then the + /// enumerators are also reset. + /// + /// A predicate that returns a value indicating + /// whether to return an item. + /// An enumerator of ResourceDictionaries. + public IEnumerator GetResourceDictionariesWhere(Func predicate) + { + return ResourceDictionaryDispenser.GetResourceDictionariesWhere(predicate); + } + + /// + /// Updates the global indexes of all descendents that require a global + /// index. + /// + /// The source of the event. + /// The event data. + private void OnGlobalSeriesIndexesInvalidated(object sender, RoutedEventArgs args) + { + UpdateGlobalIndexes(); + } + + /// + /// Updates the global index property of all Series that track their + /// global index. + /// + private void UpdateGlobalIndexes() + { + (this as ISeriesHost).GetDescendentSeries().OfType().ForEachWithIndex( + (seriesThatTracksGlobalIndex, index) => + { + seriesThatTracksGlobalIndex.GlobalSeriesIndexChanged(index); + }); + } + + /// + /// Gets or sets the Series host of the chart. + /// + /// This will always return null. + ISeriesHost IRequireSeriesHost.SeriesHost + { + get { return SeriesHost; } + set { SeriesHost = value; } + } + + /// + /// Gets or sets the Series host of the chart. + /// + /// This will always return null. + protected ISeriesHost SeriesHost { get; set; } + + /// + /// Gets the axes collection of the chart. + /// + ObservableCollection ISeriesHost.Axes + { + get { return InternalActualAxes as ObservableCollection; } + } + + /// + /// Gets the Series collection of the chart. + /// + ObservableCollection ISeriesHost.Series + { + get { return (ObservableCollection)Series; } + } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Chart/SeriesHostAxesCollection.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Chart/SeriesHostAxesCollection.cs new file mode 100644 index 00000000..8322a112 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Chart/SeriesHostAxesCollection.cs @@ -0,0 +1,152 @@ +// (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; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Linq; +using System.Windows; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// An axes collection used by a series host. + /// + internal class SeriesHostAxesCollection : UniqueObservableCollection + { + /// + /// Gets or sets the series host field. + /// + private ISeriesHost SeriesHost { get; set; } + + /// + /// Gets or sets a collection of axes cannot be removed under any + /// circumstances. + /// + private UniqueObservableCollection PersistentAxes { get; set; } + + /// + /// Instantiates a new instance of the SeriesHostAxesCollection class. + /// + /// The series host. + internal SeriesHostAxesCollection(ISeriesHost seriesHost) + { + this.SeriesHost = seriesHost; + this.PersistentAxes = new UniqueObservableCollection(); + this.CollectionChanged += ThisCollectionChanged; + } + + /// + /// Instantiates a new instance of the SeriesHostAxesCollection class. + /// + /// The series host. + /// A collection of axes that can never be + /// removed from the chart. + internal SeriesHostAxesCollection(ISeriesHost seriesHost, UniqueObservableCollection persistentAxes) + : this(seriesHost) + { + Debug.Assert(persistentAxes != null, "Persistent axes collection cannot be null."); + this.SeriesHost = seriesHost; + this.PersistentAxes = persistentAxes; + this.PersistentAxes.CollectionChanged += PersistentAxesCollectionChanged; + } + + /// + /// A method that attaches and removes listeners to axes added to this + /// collection. + /// + /// This object. + /// Information about the event. + private void ThisCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (e.NewItems != null) + { + foreach (IAxis axis in e.NewItems) + { + axis.RegisteredListeners.CollectionChanged += AxisRegisteredListenersCollectionChanged; + } + } + if (e.OldItems != null) + { + foreach (IAxis axis in e.OldItems) + { + axis.RegisteredListeners.CollectionChanged -= AxisRegisteredListenersCollectionChanged; + } + } + } + + /// + /// Remove an axis from the collection if it is no longer used. + /// + /// The axis that has had its registered + /// listeners collection changed. + /// Information about the event. + private void AxisRegisteredListenersCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + IAxis axis = this.Where(currentAxis => currentAxis.RegisteredListeners == sender).First(); + + if (e.OldItems != null) + { + if (!PersistentAxes.Contains(axis) && !SeriesHost.IsUsedByASeries(axis)) + { + this.Remove(axis); + } + } + } + + /// + /// This method synchronizes the collection with the persistent axes + /// collection when it is changed. + /// + /// The source of the event. + /// Information about the event. + public void PersistentAxesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (e.NewItems != null) + { + foreach (IAxis axis in e.NewItems) + { + if (!this.Contains(axis)) + { + this.Add(axis); + } + } + } + if (e.OldItems != null) + { + foreach (IAxis axis in e.OldItems) + { + if (this.Contains(axis) && !SeriesHost.IsUsedByASeries(axis)) + { + this.Remove(axis); + } + } + } + } + + /// + /// Removes an item from the axes collection but throws an exception + /// if a series in the series host is listening to it. + /// + /// The index of the item being removed. + protected override void RemoveItem(int index) + { + IAxis axis = this[index]; + + if (SeriesHost.IsUsedByASeries(axis)) + { + throw new InvalidOperationException(Properties.Resources.SeriesHostAxesCollection_RemoveItem_AxisCannotBeRemovedFromASeriesHostWhenOneOrMoreSeriesAreListeningToIt); + } + else if (PersistentAxes.Contains(axis)) + { + throw new InvalidOperationException(Properties.Resources.SeriesHostAxesCollection_InvalidAttemptToRemovePermanentAxisFromSeriesHost); + } + else + { + base.RemoveItem(index); + } + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/DataPoint/AreaDataPoint.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/DataPoint/AreaDataPoint.cs new file mode 100644 index 00000000..41e5d64c --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/DataPoint/AreaDataPoint.cs @@ -0,0 +1,40 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Represents a data point used for an area series. + /// + /// Preview + [TemplateVisualState(Name = DataPoint.StateCommonNormal, GroupName = DataPoint.GroupCommonStates)] + [TemplateVisualState(Name = DataPoint.StateCommonMouseOver, GroupName = DataPoint.GroupCommonStates)] + [TemplateVisualState(Name = DataPoint.StateSelectionUnselected, GroupName = DataPoint.GroupSelectionStates)] + [TemplateVisualState(Name = DataPoint.StateSelectionSelected, GroupName = DataPoint.GroupSelectionStates)] + [TemplateVisualState(Name = DataPoint.StateRevealShown, GroupName = DataPoint.GroupRevealStates)] + [TemplateVisualState(Name = DataPoint.StateRevealHidden, GroupName = DataPoint.GroupRevealStates)] + public partial class AreaDataPoint : DataPoint + { +#if !SILVERLIGHT + /// + /// Initializes the static members of the AreaDataPoint class. + /// + static AreaDataPoint() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(AreaDataPoint), new FrameworkPropertyMetadata(typeof(AreaDataPoint))); + } + +#endif + /// + /// Initializes a new instance of the AreaDataPoint class. + /// + public AreaDataPoint() + { +#if SILVERLIGHT + this.DefaultStyleKey = typeof(AreaDataPoint); +#endif + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/DataPoint/BarDataPoint.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/DataPoint/BarDataPoint.cs new file mode 100644 index 00000000..fecacedc --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/DataPoint/BarDataPoint.cs @@ -0,0 +1,40 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Represents a data point used for a bar series. + /// + /// Preview + [TemplateVisualState(Name = DataPoint.StateCommonNormal, GroupName = DataPoint.GroupCommonStates)] + [TemplateVisualState(Name = DataPoint.StateCommonMouseOver, GroupName = DataPoint.GroupCommonStates)] + [TemplateVisualState(Name = DataPoint.StateSelectionUnselected, GroupName = DataPoint.GroupSelectionStates)] + [TemplateVisualState(Name = DataPoint.StateSelectionSelected, GroupName = DataPoint.GroupSelectionStates)] + [TemplateVisualState(Name = DataPoint.StateRevealShown, GroupName = DataPoint.GroupRevealStates)] + [TemplateVisualState(Name = DataPoint.StateRevealHidden, GroupName = DataPoint.GroupRevealStates)] + public partial class BarDataPoint : DataPoint + { +#if !SILVERLIGHT + /// + /// Initializes the static members of the BarDataPoint class. + /// + static BarDataPoint() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(BarDataPoint), new FrameworkPropertyMetadata(typeof(BarDataPoint))); + } + +#endif + /// + /// Initializes a new instance of the BarDataPoint class. + /// + public BarDataPoint() + { +#if SILVERLIGHT + this.DefaultStyleKey = typeof(BarDataPoint); +#endif + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/DataPoint/BubbleDataPoint.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/DataPoint/BubbleDataPoint.cs new file mode 100644 index 00000000..3279ac8e --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/DataPoint/BubbleDataPoint.cs @@ -0,0 +1,156 @@ +// (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.Diagnostics.CodeAnalysis; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Represents a data point used for a bubble series. + /// + /// Preview + [TemplateVisualState(Name = DataPoint.StateCommonNormal, GroupName = DataPoint.GroupCommonStates)] + [TemplateVisualState(Name = DataPoint.StateCommonMouseOver, GroupName = DataPoint.GroupCommonStates)] + [TemplateVisualState(Name = DataPoint.StateSelectionUnselected, GroupName = DataPoint.GroupSelectionStates)] + [TemplateVisualState(Name = DataPoint.StateSelectionSelected, GroupName = DataPoint.GroupSelectionStates)] + [TemplateVisualState(Name = DataPoint.StateRevealShown, GroupName = DataPoint.GroupRevealStates)] + [TemplateVisualState(Name = DataPoint.StateRevealHidden, GroupName = DataPoint.GroupRevealStates)] + public class BubbleDataPoint : DataPoint + { + #region public double Size + /// + /// Gets or sets the size value of the bubble data point. + /// + public double Size + { + get { return (double)GetValue(SizeProperty); } + set { SetValue(SizeProperty, value); } + } + + /// + /// Identifies the Size dependency property. + /// + public static readonly DependencyProperty SizeProperty = + DependencyProperty.Register( + "Size", + typeof(double), + typeof(BubbleDataPoint), + new PropertyMetadata(0.0, OnSizePropertyChanged)); + + /// + /// SizeProperty property changed handler. + /// + /// BubbleDataPoint that changed its Size. + /// Event arguments. + private static void OnSizePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + BubbleDataPoint source = (BubbleDataPoint)d; + double oldValue = (double)e.OldValue; + double newValue = (double)e.NewValue; + source.OnSizePropertyChanged(oldValue, newValue); + } + + /// + /// SizeProperty property changed handler. + /// + /// Old value. + /// New value. + private void OnSizePropertyChanged(double oldValue, double newValue) + { + RoutedPropertyChangedEventHandler handler = SizePropertyChanged; + if (handler != null) + { + handler(this, new RoutedPropertyChangedEventArgs(oldValue, newValue)); + } + + if (this.State == DataPointState.Created) + { + this.ActualSize = newValue; + } + } + + /// + /// This event is raised when the size property is changed. + /// + internal event RoutedPropertyChangedEventHandler SizePropertyChanged; + + #endregion public double Size + + #region public double ActualSize + /// + /// Gets or sets the actual size of the bubble data point. + /// + public double ActualSize + { + get { return (double)GetValue(ActualSizeProperty); } + set { SetValue(ActualSizeProperty, value); } + } + + /// + /// Identifies the ActualSize dependency property. + /// + public static readonly DependencyProperty ActualSizeProperty = + DependencyProperty.Register( + "ActualSize", + typeof(double), + typeof(BubbleDataPoint), + new PropertyMetadata(0.0, OnActualSizePropertyChanged)); + + /// + /// ActualSizeProperty property changed handler. + /// + /// BubbleDataPoint that changed its ActualSize. + /// Event arguments. + private static void OnActualSizePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + BubbleDataPoint source = (BubbleDataPoint)d; + double oldValue = (double)e.OldValue; + double newValue = (double)e.NewValue; + source.OnActualSizePropertyChanged(oldValue, newValue); + } + + /// + /// ActualSizeProperty property changed handler. + /// + /// Old value. + /// New value. + private void OnActualSizePropertyChanged(double oldValue, double newValue) + { + RoutedPropertyChangedEventHandler handler = ActualSizePropertyChanged; + if (handler != null) + { + handler(this, new RoutedPropertyChangedEventArgs(oldValue, newValue)); + } + } + + /// + /// This event is raised when the actual size property is changed. + /// + internal event RoutedPropertyChangedEventHandler ActualSizePropertyChanged; + + #endregion public double ActualSize + +#if !SILVERLIGHT + /// + /// Initializes the static members of the BubbleDataPoint class. + /// + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Dependency properties are initialized in-line.")] + static BubbleDataPoint() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(BubbleDataPoint), new FrameworkPropertyMetadata(typeof(BubbleDataPoint))); + } + +#endif + /// + /// Initializes a new instance of the bubble data point. + /// + public BubbleDataPoint() + { +#if SILVERLIGHT + this.DefaultStyleKey = typeof(BubbleDataPoint); +#endif + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/DataPoint/ColumnDataPoint.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/DataPoint/ColumnDataPoint.cs new file mode 100644 index 00000000..4e127fe1 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/DataPoint/ColumnDataPoint.cs @@ -0,0 +1,40 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Represents a data point used for a column series. + /// + /// Preview + [TemplateVisualState(Name = DataPoint.StateCommonNormal, GroupName = DataPoint.GroupCommonStates)] + [TemplateVisualState(Name = DataPoint.StateCommonMouseOver, GroupName = DataPoint.GroupCommonStates)] + [TemplateVisualState(Name = DataPoint.StateSelectionUnselected, GroupName = DataPoint.GroupSelectionStates)] + [TemplateVisualState(Name = DataPoint.StateSelectionSelected, GroupName = DataPoint.GroupSelectionStates)] + [TemplateVisualState(Name = DataPoint.StateRevealShown, GroupName = DataPoint.GroupRevealStates)] + [TemplateVisualState(Name = DataPoint.StateRevealHidden, GroupName = DataPoint.GroupRevealStates)] + public partial class ColumnDataPoint : DataPoint + { +#if !SILVERLIGHT + /// + /// Initializes the static members of the ColumnDataPoint class. + /// + static ColumnDataPoint() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(ColumnDataPoint), new FrameworkPropertyMetadata(typeof(ColumnDataPoint))); + } + +#endif + /// + /// Initializes a new instance of the ColumnDataPoint class. + /// + public ColumnDataPoint() + { +#if SILVERLIGHT + this.DefaultStyleKey = typeof(ColumnDataPoint); +#endif + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/DataPoint/DataPoint.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/DataPoint/DataPoint.cs new file mode 100644 index 00000000..1645bdb4 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/DataPoint/DataPoint.cs @@ -0,0 +1,996 @@ +// (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; +using System.Globalization; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Threading; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Represents a control that displays a data point. + /// + /// Preview + [TemplateVisualState(Name = DataPoint.StateCommonNormal, GroupName = DataPoint.GroupCommonStates)] + [TemplateVisualState(Name = DataPoint.StateCommonMouseOver, GroupName = DataPoint.GroupCommonStates)] + [TemplateVisualState(Name = DataPoint.StateSelectionUnselected, GroupName = DataPoint.GroupSelectionStates)] + [TemplateVisualState(Name = DataPoint.StateSelectionSelected, GroupName = DataPoint.GroupSelectionStates)] + [TemplateVisualState(Name = DataPoint.StateRevealShown, GroupName = DataPoint.GroupRevealStates)] + [TemplateVisualState(Name = DataPoint.StateRevealHidden, GroupName = DataPoint.GroupRevealStates)] + public abstract partial class DataPoint : Control + { + #region CommonStates + /// + /// Common state group. + /// + internal const string GroupCommonStates = "CommonStates"; + + /// + /// Normal state of the Common group. + /// + internal const string StateCommonNormal = "Normal"; + + /// + /// MouseOver state of the Common group. + /// + internal const string StateCommonMouseOver = "MouseOver"; + #endregion CommonStates + + #region SelectionStates + /// + /// Selection state group. + /// + internal const string GroupSelectionStates = "SelectionStates"; + + /// + /// Unselected state of the Selection group. + /// + internal const string StateSelectionUnselected = "Unselected"; + + /// + /// Selected state of the Selection group. + /// + internal const string StateSelectionSelected = "Selected"; + #endregion SelectionStates + + #region GroupRevealStates + /// + /// Reveal state group. + /// + internal const string GroupRevealStates = "RevealStates"; + + /// + /// Shown state of the Reveal group. + /// + internal const string StateRevealShown = "Shown"; + + /// + /// Hidden state of the Reveal group. + /// + internal const string StateRevealHidden = "Hidden"; + #endregion GroupRevealStates + + #region public bool IsSelectionEnabled + /// + /// Gets or sets a value indicating whether selection is enabled. + /// + 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(DataPoint), + new PropertyMetadata(false, OnIsSelectionEnabledPropertyChanged)); + + /// + /// IsSelectionEnabledProperty property changed handler. + /// + /// Control that changed its IsSelectionEnabled. + /// Event arguments. + private static void OnIsSelectionEnabledPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DataPoint source = (DataPoint)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) + { + if (newValue == false) + { + IsSelected = false; + IsHovered = false; + } + } + #endregion public bool IsSelectionEnabled + + /// + /// Gets a value indicating whether the data point is active. + /// + internal bool IsActive { get; private set; } + + /// + /// An event raised when the IsSelected property is changed. + /// + internal event RoutedPropertyChangedEventHandler IsSelectedChanged; + + /// + /// A value indicating whether the mouse is hovering over the data + /// point. + /// + private bool _isHovered; + + /// + /// Gets a value indicating whether the mouse is hovering over + /// the data point. + /// + protected bool IsHovered + { + get { return _isHovered; } + private set + { + bool oldValue = _isHovered; + _isHovered = value; + if (oldValue != _isHovered) + { + OnIsHoveredPropertyChanged(oldValue, value); + } + } + } + + /// + /// IsHoveredProperty property changed handler. + /// + /// Old value. + /// New value. + protected virtual void OnIsHoveredPropertyChanged(bool oldValue, bool newValue) + { + VisualStateManager.GoToState(this, (newValue == true) ? StateCommonMouseOver : StateCommonNormal, true); + } + + #region internal bool IsSelected + + /// + /// Gets or sets a value indicating whether the data point is selected. + /// + internal bool IsSelected + { + get { return (bool)GetValue(IsSelectedProperty); } + set { SetValue(IsSelectedProperty, value); } + } + + /// + /// Identifies the IsSelected dependency property. + /// + internal static readonly DependencyProperty IsSelectedProperty = + DependencyProperty.Register( + "IsSelected", + typeof(bool), + typeof(DataPoint), + new PropertyMetadata(false, OnIsSelectedPropertyChanged)); + + /// + /// IsSelectedProperty property changed handler. + /// + /// Control that changed its IsSelected. + /// Event arguments. + private static void OnIsSelectedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DataPoint source = (DataPoint)d; + bool oldValue = (bool)e.OldValue; + bool newValue = (bool)e.NewValue; + source.OnIsSelectedPropertyChanged(oldValue, newValue); + } + + /// + /// IsSelectedProperty property changed handler. + /// + /// The value to be replaced. + /// The new value. + protected virtual void OnIsSelectedPropertyChanged(bool oldValue, bool newValue) + { + VisualStateManager.GoToState(this, newValue ? StateSelectionSelected : StateSelectionUnselected, true); + + RoutedPropertyChangedEventHandler handler = this.IsSelectedChanged; + if (handler != null) + { + handler(this, new RoutedPropertyChangedEventArgs(oldValue, newValue)); + } + } + #endregion internal bool IsSelected + + /// + /// Event raised when the actual dependent value of the data point is changed. + /// + internal event RoutedPropertyChangedEventHandler ActualDependentValueChanged; + + #region public IComparable ActualDependentValue + /// + /// Gets or sets the actual dependent value displayed in the chart. + /// + public IComparable ActualDependentValue + { + get { return (IComparable)GetValue(ActualDependentValueProperty); } + set { SetValue(ActualDependentValueProperty, value); } + } + + /// + /// Identifies the ActualDependentValue dependency property. + /// + public static readonly System.Windows.DependencyProperty ActualDependentValueProperty = + System.Windows.DependencyProperty.Register( + "ActualDependentValue", + typeof(IComparable), + typeof(DataPoint), + new System.Windows.PropertyMetadata(0.0, OnActualDependentValuePropertyChanged)); + + /// + /// Called when the value of the ActualDependentValue property changes. + /// + /// Control that changed its ActualDependentValue. + /// Event arguments. + private static void OnActualDependentValuePropertyChanged(System.Windows.DependencyObject d, System.Windows.DependencyPropertyChangedEventArgs e) + { + DataPoint source = (DataPoint)d; + IComparable oldValue = (IComparable)e.OldValue; + IComparable newValue = (IComparable)e.NewValue; + source.OnActualDependentValuePropertyChanged(oldValue, newValue); + } + + /// + /// A value indicating whether the actual independent value is being + /// coerced. + /// + private bool _isCoercingActualDependentValue; + + /// + /// The preserved previous actual dependent value before coercion. + /// + private IComparable _oldActualDependentValueBeforeCoercion; + + /// + /// Called when the value of the ActualDependentValue property changes. + /// + /// The value to be replaced. + /// The new value. + protected virtual void OnActualDependentValuePropertyChanged(IComparable oldValue, IComparable newValue) + { + double coercedValue = 0.0; + if (!(newValue is double) && ValueHelper.TryConvert(newValue, out coercedValue)) + { + _isCoercingActualDependentValue = true; + _oldActualDependentValueBeforeCoercion = oldValue; + } + + if (!_isCoercingActualDependentValue) + { + if (_oldActualDependentValueBeforeCoercion != null) + { + oldValue = _oldActualDependentValueBeforeCoercion; + _oldActualDependentValueBeforeCoercion = null; + } + + RoutedPropertyChangedEventHandler handler = this.ActualDependentValueChanged; + if (handler != null) + { + handler(this, new RoutedPropertyChangedEventArgs(oldValue, newValue)); + } + } + + if (_isCoercingActualDependentValue) + { + _isCoercingActualDependentValue = false; + this.ActualDependentValue = coercedValue; + } + } + #endregion public IComparable ActualDependentValue + + /// + /// This event is raised when the dependent value of the data point is + /// changed. + /// + internal event RoutedPropertyChangedEventHandler DependentValueChanged; + + #region public IComparable DependentValue + /// + /// Gets or sets the dependent value of the Control. + /// + public IComparable DependentValue + { + get { return (IComparable) GetValue(DependentValueProperty); } + set { SetValue(DependentValueProperty, value); } + } + + /// + /// Identifies the DependentValue dependency property. + /// + public static readonly DependencyProperty DependentValueProperty = + DependencyProperty.Register( + "DependentValue", + typeof(IComparable), + typeof(DataPoint), + new PropertyMetadata(null, OnDependentValuePropertyChanged)); + + /// + /// Called when the DependentValue property changes. + /// + /// Control that changed its DependentValue. + /// Event arguments. + private static void OnDependentValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DataPoint source = (DataPoint)d; + IComparable oldValue = (IComparable) e.OldValue; + IComparable newValue = (IComparable) e.NewValue; + source.OnDependentValuePropertyChanged(oldValue, newValue); + } + + /// + /// Called when the DependentValue property changes. + /// + /// The value to be replaced. + /// The new value. + protected virtual void OnDependentValuePropertyChanged(IComparable oldValue, IComparable newValue) + { + SetFormattedProperty(FormattedDependentValueProperty, DependentValueStringFormat, newValue); + RoutedPropertyChangedEventHandler handler = this.DependentValueChanged; + if (handler != null) + { + handler(this, new RoutedPropertyChangedEventArgs(oldValue, newValue)); + } + + if (this.State == DataPointState.Created) + { + // Prefer setting the value as a double... + double coercedNewValue; + if (ValueHelper.TryConvert(newValue, out coercedNewValue)) + { + ActualDependentValue = coercedNewValue; + } + else + { + // ... but fall back otherwise + ActualDependentValue = newValue; + } + } + } + #endregion public IComparable DependentValue + + #region public string DependentValueStringFormat + /// + /// Gets or sets the format string for the FormattedDependentValue property. + /// + public string DependentValueStringFormat + { + get { return GetValue(DependentValueStringFormatProperty) as string; } + set { SetValue(DependentValueStringFormatProperty, value); } + } + + /// + /// Identifies the DependentValueStringFormat dependency property. + /// + public static readonly DependencyProperty DependentValueStringFormatProperty = + DependencyProperty.Register( + "DependentValueStringFormat", + typeof(string), + typeof(DataPoint), + new PropertyMetadata(null, OnDependentValueStringFormatPropertyChanged)); + + /// + /// Called when DependentValueStringFormat property changes. + /// + /// Control that changed its DependentValueStringFormat. + /// Event arguments. + private static void OnDependentValueStringFormatPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DataPoint source = d as DataPoint; + string oldValue = e.OldValue as string; + string newValue = e.NewValue as string; + source.OnDependentValueStringFormatPropertyChanged(oldValue, newValue); + } + + /// + /// Called when DependentValueStringFormat property changes. + /// + /// The value to be replaced. + /// The new value. + protected virtual void OnDependentValueStringFormatPropertyChanged(string oldValue, string newValue) + { + SetFormattedProperty(FormattedDependentValueProperty, newValue, DependentValue); + } + #endregion public string DependentValueStringFormat + + #region public string FormattedDependentValue + /// + /// Gets the DependentValue as formatted by the DependentValueStringFormat property. + /// + public string FormattedDependentValue + { + get { return GetValue(FormattedDependentValueProperty) as string; } + } + + /// + /// Identifies the FormattedDependentValue dependency property. + /// + public static readonly DependencyProperty FormattedDependentValueProperty = + DependencyProperty.Register( + "FormattedDependentValue", + typeof(string), + typeof(DataPoint), + null); + #endregion public string FormattedDependentValue + + #region public string FormattedIndependentValue + /// + /// Gets the IndependentValue as formatted by the IndependentValueStringFormat property. + /// + public string FormattedIndependentValue + { + get { return GetValue(FormattedIndependentValueProperty) as string; } + } + + /// + /// Identifies the FormattedIndependentValue dependency property. + /// + public static readonly DependencyProperty FormattedIndependentValueProperty = + DependencyProperty.Register( + "FormattedIndependentValue", + typeof(string), + typeof(DataPoint), + null); + #endregion public string FormattedIndependentValue + + /// + /// Called when the independent value of the data point is changed. + /// + internal event RoutedPropertyChangedEventHandler IndependentValueChanged; + + #region public object IndependentValue + /// + /// Gets or sets the independent value. + /// + public object IndependentValue + { + get { return GetValue(IndependentValueProperty); } + set { SetValue(IndependentValueProperty, value); } + } + + /// + /// Identifies the IndependentValue dependency property. + /// + public static readonly DependencyProperty IndependentValueProperty = + DependencyProperty.Register( + "IndependentValue", + typeof(object), + typeof(DataPoint), + new PropertyMetadata(null, OnIndependentValuePropertyChanged)); + + /// + /// Called when the IndependentValue property changes. + /// + /// Control that changed its IndependentValue. + /// Event arguments. + private static void OnIndependentValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DataPoint source = (DataPoint)d; + object oldValue = e.OldValue; + object newValue = e.NewValue; + source.OnIndependentValuePropertyChanged(oldValue, newValue); + } + + /// + /// Called when the IndependentValue property changes. + /// + /// The old value. + /// The new value. + protected virtual void OnIndependentValuePropertyChanged(object oldValue, object newValue) + { + SetFormattedProperty(FormattedIndependentValueProperty, IndependentValueStringFormat, newValue); + RoutedPropertyChangedEventHandler handler = this.IndependentValueChanged; + if (handler != null) + { + handler(this, new RoutedPropertyChangedEventArgs(oldValue, newValue)); + } + + if (this.State == DataPointState.Created) + { + // Prefer setting the value as a double... + double coercedNewValue; + if (ValueHelper.TryConvert(newValue, out coercedNewValue)) + { + ActualIndependentValue = coercedNewValue; + } + else + { + // ... but fall back otherwise + ActualIndependentValue = newValue; + } + } + } + #endregion public object IndependentValue + + #region public string IndependentValueStringFormat + /// + /// Gets or sets the format string for the FormattedIndependentValue property. + /// + public string IndependentValueStringFormat + { + get { return GetValue(IndependentValueStringFormatProperty) as string; } + set { SetValue(IndependentValueStringFormatProperty, value); } + } + + /// + /// Identifies the IndependentValueStringFormat dependency property. + /// + public static readonly DependencyProperty IndependentValueStringFormatProperty = + DependencyProperty.Register( + "IndependentValueStringFormat", + typeof(string), + typeof(DataPoint), + new PropertyMetadata(null, OnIndependentValueStringFormatPropertyChanged)); + + /// + /// Called when the value of the IndependentValueStringFormat property changes. + /// + /// Control that changed its IndependentValueStringFormat. + /// Event arguments. + private static void OnIndependentValueStringFormatPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DataPoint source = d as DataPoint; + string oldValue = e.OldValue as string; + string newValue = e.NewValue as string; + source.OnIndependentValueStringFormatPropertyChanged(oldValue, newValue); + } + + /// + /// Called when the value of the IndependentValueStringFormat property changes. + /// + /// The value to be replaced. + /// The new value. + protected virtual void OnIndependentValueStringFormatPropertyChanged(string oldValue, string newValue) + { + SetFormattedProperty(FormattedIndependentValueProperty, newValue, IndependentValue); + } + #endregion public string IndependentValueStringFormat + + /// + /// Occurs when the actual independent value of the data point is + /// changed. + /// + internal event RoutedPropertyChangedEventHandler ActualIndependentValueChanged; + + #region public object ActualIndependentValue + /// + /// Gets or sets the actual independent value. + /// + public object ActualIndependentValue + { + get { return (object)GetValue(ActualIndependentValueProperty); } + set { SetValue(ActualIndependentValueProperty, value); } + } + + /// + /// A value indicating whether the actual independent value is being + /// coerced. + /// + private bool _isCoercingActualIndependentValue; + + /// + /// The preserved previous actual dependent value before coercion. + /// + private object _oldActualIndependentValueBeforeCoercion; + + /// + /// Identifies the ActualIndependentValue dependency property. + /// + public static readonly DependencyProperty ActualIndependentValueProperty = + DependencyProperty.Register( + "ActualIndependentValue", + typeof(object), + typeof(DataPoint), + new PropertyMetadata(OnActualIndependentValuePropertyChanged)); + + /// + /// Called when the ActualIndependentValue property changes. + /// + /// Control that changed its ActualIndependentValue. + /// Event arguments. + private static void OnActualIndependentValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DataPoint source = (DataPoint)d; + object oldValue = (object)e.OldValue; + object newValue = (object)e.NewValue; + source.OnActualIndependentValuePropertyChanged(oldValue, newValue); + } + + /// + /// Called when the ActualIndependentValue property changes. + /// + /// The value to be replaced. + /// The new value. + protected virtual void OnActualIndependentValuePropertyChanged(object oldValue, object newValue) + { + double coercedValue = 0.0; + if (!(newValue is double) && ValueHelper.TryConvert(newValue, out coercedValue)) + { + _isCoercingActualIndependentValue = true; + _oldActualIndependentValueBeforeCoercion = oldValue; + } + + if (!_isCoercingActualIndependentValue) + { + if (_oldActualIndependentValueBeforeCoercion != null) + { + oldValue = _oldActualIndependentValueBeforeCoercion; + _oldActualIndependentValueBeforeCoercion = null; + } + + RoutedPropertyChangedEventHandler handler = this.ActualIndependentValueChanged; + if (handler != null) + { + handler(this, new RoutedPropertyChangedEventArgs(oldValue, newValue)); + } + } + + if (_isCoercingActualIndependentValue) + { + _isCoercingActualIndependentValue = false; + this.ActualIndependentValue = coercedValue; + } + } + #endregion public object ActualIndependentValue + + /// + /// Occurs when the state of a data point is changed. + /// + internal event RoutedPropertyChangedEventHandler StateChanged; + + #region public DataPointState State + /// + /// Gets or sets a value indicating whether the State property is being + /// coerced to its previous value. + /// + private bool IsCoercingState { get; set; } + + /// + /// Gets or sets the state of the data point. + /// + internal DataPointState State + { + get { return (DataPointState)GetValue(StateProperty); } + set { SetValue(StateProperty, value); } + } + + /// + /// Identifies the State dependency property. + /// + internal static readonly DependencyProperty StateProperty = + DependencyProperty.Register( + "State", + typeof(DataPointState), + typeof(DataPoint), + new PropertyMetadata(DataPointState.Created, OnStatePropertyChanged)); + + /// + /// Called when the value of the State property changes. + /// + /// Control that changed its State. + /// Event arguments. + private static void OnStatePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DataPoint source = (DataPoint)d; + DataPointState oldValue = (DataPointState)e.OldValue; + DataPointState newValue = (DataPointState)e.NewValue; + source.OnStatePropertyChanged(oldValue, newValue); + } + + /// + /// Called when the value of the State property changes. + /// + /// The value to be replaced. + /// The new value. + protected virtual void OnStatePropertyChanged(DataPointState oldValue, DataPointState newValue) + { + if (!IsCoercingState) + { + // If state ever goes to or past PendingRemoval, the DataPoint is no longer active + if (DataPointState.PendingRemoval <= newValue) + { + IsActive = false; + } + + if (newValue < oldValue) + { + // If we've somehow gone backwards in the life cycle (other + // than when we go back to normal from updating) coerce to + // old value. + IsCoercingState = true; + this.State = oldValue; + IsCoercingState = false; + } + else + { + // Update selection + if (newValue > DataPointState.Normal) + { + this.IsSelectionEnabled = false; + } + + // Start state transition + bool transitionStarted = false; + switch (newValue) + { + case DataPointState.Showing: + case DataPointState.Hiding: + transitionStarted = GoToCurrentRevealState(); + break; + } + + // Fire Changed event + RoutedPropertyChangedEventHandler handler = this.StateChanged; + if (handler != null) + { + handler(this, new RoutedPropertyChangedEventArgs(oldValue, newValue)); + } + + // Change state if no transition started + if (!transitionStarted && _templateApplied) + { + switch (newValue) + { + case DataPointState.Showing: + State = DataPointState.Normal; + break; + case DataPointState.Hiding: + State = DataPointState.Hidden; + break; + } + } + } + } + } + #endregion internal DataPointState State + + /// + /// Gets the implementation root of the Control. + /// + /// + /// Implements Silverlight's corresponding internal property on Control. + /// + private FrameworkElement ImplementationRoot + { + get + { + return (1 == VisualTreeHelper.GetChildrenCount(this)) ? VisualTreeHelper.GetChild(this, 0) as FrameworkElement : null; + } + } + + /// + /// Tracks whether the Reveal/Shown VisualState is available. + /// + private bool _haveStateRevealShown; + + /// + /// Tracks whether the Reveal/Hidden VisualState is available. + /// + private bool _haveStateRevealHidden; + + /// + /// Tracks whether the template has been applied yet. + /// + private bool _templateApplied; + + /// + /// Initializes a new instance of the DataPoint class. + /// + protected DataPoint() + { + Loaded += new RoutedEventHandler(OnLoaded); + IsActive = true; + } + + /// + /// Updates the Control's visuals to reflect the current state(s). + /// + /// True if a state transition was started. + private bool GoToCurrentRevealState() + { + bool transitionStarted = false; + string stateName = null; + switch (State) + { + case DataPointState.Showing: + if (_haveStateRevealShown) + { + stateName = StateRevealShown; + } + break; + case DataPointState.Hiding: + if (_haveStateRevealHidden) + { + stateName = StateRevealHidden; + } + break; + } + if (null != stateName) + { + if (!DesignerProperties.GetIsInDesignMode(this)) + { + // The use of Dispatcher.BeginInvoke here is necessary to + // work around the StackOverflowException Silverlight throws + // when it tries to play too many VSM animations. + Dispatcher.BeginInvoke(new Action(() => VisualStateManager.GoToState(this, stateName, true))); + } + else + { + VisualStateManager.GoToState(this, stateName, false); + } + transitionStarted = true; + } + return transitionStarted; + } + + /// + /// Builds the visual tree for the DataPoint when a new template is applied. + /// + public override void OnApplyTemplate() + { + // Unhook CurrentStateChanged handler + VisualStateGroup groupReveal = VisualStateManager.GetVisualStateGroups(ImplementationRoot).CastWrapper().Where(group => GroupRevealStates == group.Name).FirstOrDefault(); + if (null != groupReveal) + { + groupReveal.CurrentStateChanged -= new EventHandler(OnCurrentStateChanged); + } + + base.OnApplyTemplate(); + + // Hook CurrentStateChanged handler + _haveStateRevealShown = false; + _haveStateRevealHidden = false; + groupReveal = VisualStateManager.GetVisualStateGroups(ImplementationRoot).CastWrapper().Where(group => GroupRevealStates == group.Name).FirstOrDefault(); + if (null != groupReveal) + { + groupReveal.CurrentStateChanged += new EventHandler(OnCurrentStateChanged); + _haveStateRevealShown = groupReveal.States.CastWrapper().Where(state => StateRevealShown == state.Name).Any(); + _haveStateRevealHidden = groupReveal.States.CastWrapper().Where(state => StateRevealHidden == state.Name).Any(); + } + + _templateApplied = true; + + // Go to current state(s) + GoToCurrentRevealState(); + + if (DesignerProperties.GetIsInDesignMode(this)) + { + // Transition to Showing state in design mode so DataPoint will be visible + State = DataPointState.Showing; + } + } + + /// + /// Changes the DataPoint object's state after one of the VSM state animations completes. + /// + /// Event source. + /// Event arguments. + private void OnCurrentStateChanged(object sender, VisualStateChangedEventArgs e) + { + switch (e.NewState.Name) + { + case StateRevealShown: + if (State == DataPointState.Showing) + { + State = DataPointState.Normal; + } + break; + case StateRevealHidden: + if (State == DataPointState.Hiding) + { + State = DataPointState.Hidden; + } + break; + } + } + + /// + /// Handles the Control's Loaded event. + /// + /// The Control. + /// Event arguments. + private void OnLoaded(object sender, RoutedEventArgs e) + { + GoToCurrentRevealState(); + } + + /// + /// Provides handling for the MouseEnter event. + /// + /// Event arguments. + protected override void OnMouseEnter(MouseEventArgs e) + { + base.OnMouseEnter(e); + + if (IsSelectionEnabled) + { + IsHovered = true; + } + } + + /// + /// Provides handling for the MouseLeave event. + /// + /// Event arguments. + protected override void OnMouseLeave(MouseEventArgs e) + { + base.OnMouseLeave(e); + if (IsSelectionEnabled) + { + IsHovered = false; + } + } + + /// + /// Provides handling for the MouseLeftButtonDown event. + /// + /// Event arguments. + protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) + { + if (DefinitionSeriesIsSelectionEnabledHandling) + { + // DefinitionSeries-compatible handling + if (!IsSelectionEnabled) + { + // Prevents clicks from bubbling to background, but necessary + // to avoid letting ListBoxItem select the item + e.Handled = true; + } + base.OnMouseLeftButtonDown(e); + } + else + { + // Traditional handling + base.OnMouseLeftButtonDown(e); + if (IsSelectionEnabled) + { + IsSelected = (ModifierKeys.None == (ModifierKeys.Control & Keyboard.Modifiers)); + e.Handled = true; + } + } + } + + /// + /// Gets or sets a value indicating whether to handle IsSelectionEnabled in the DefinitionSeries manner. + /// + internal bool DefinitionSeriesIsSelectionEnabledHandling { get; set; } + + /// + /// Sets a dependency property with the specified format. + /// + /// The DependencyProperty to set. + /// The Format string to apply to the value. + /// The value of the dependency property to be formatted. + internal void SetFormattedProperty(DependencyProperty property, string format, object value) + { + SetValue(property, string.Format(CultureInfo.CurrentCulture, format ?? "{0}", value)); + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/DataPoint/DataPointState.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/DataPoint/DataPointState.cs new file mode 100644 index 00000000..89717f73 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/DataPoint/DataPointState.cs @@ -0,0 +1,43 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Describes the state a data point is in. + /// + public enum DataPointState + { + /// + /// Data point has been created. + /// + Created, + + /// + /// Data point is in the process of being revealed. + /// + Showing, + + /// + /// Data point is visible in the plot area. + /// + Normal, + + /// + /// Data point is in the process of being removed from the plot area. + /// + PendingRemoval, + + /// + /// Data point is in the process of being hidden. + /// + Hiding, + + /// + /// Data point is hidden. + /// + Hidden, + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/DataPoint/LineDataPoint.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/DataPoint/LineDataPoint.cs new file mode 100644 index 00000000..4e8c5228 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/DataPoint/LineDataPoint.cs @@ -0,0 +1,40 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Represents a data point used for a line series. + /// + /// Preview + [TemplateVisualState(Name = DataPoint.StateCommonNormal, GroupName = DataPoint.GroupCommonStates)] + [TemplateVisualState(Name = DataPoint.StateCommonMouseOver, GroupName = DataPoint.GroupCommonStates)] + [TemplateVisualState(Name = DataPoint.StateSelectionUnselected, GroupName = DataPoint.GroupSelectionStates)] + [TemplateVisualState(Name = DataPoint.StateSelectionSelected, GroupName = DataPoint.GroupSelectionStates)] + [TemplateVisualState(Name = DataPoint.StateRevealShown, GroupName = DataPoint.GroupRevealStates)] + [TemplateVisualState(Name = DataPoint.StateRevealHidden, GroupName = DataPoint.GroupRevealStates)] + public partial class LineDataPoint : DataPoint + { +#if !SILVERLIGHT + /// + /// Initializes the static members of the LineDataPoint class. + /// + static LineDataPoint() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(LineDataPoint), new FrameworkPropertyMetadata(typeof(LineDataPoint))); + } + +#endif + /// + /// Initializes a new instance of the LineDataPoint class. + /// + public LineDataPoint() + { +#if SILVERLIGHT + this.DefaultStyleKey = typeof(LineDataPoint); +#endif + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/DataPoint/PieDataPoint.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/DataPoint/PieDataPoint.cs new file mode 100644 index 00000000..d1f9dce6 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/DataPoint/PieDataPoint.cs @@ -0,0 +1,576 @@ +// (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; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Windows; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Animation; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Represents a data point used for a pie series. + /// + /// Preview + [TemplatePart(Name = SliceName, Type = typeof(UIElement))] + [TemplateVisualState(Name = DataPoint.StateCommonNormal, GroupName = DataPoint.GroupCommonStates)] + [TemplateVisualState(Name = DataPoint.StateCommonMouseOver, GroupName = DataPoint.GroupCommonStates)] + [TemplateVisualState(Name = DataPoint.StateSelectionUnselected, GroupName = DataPoint.GroupSelectionStates)] + [TemplateVisualState(Name = DataPoint.StateSelectionSelected, GroupName = DataPoint.GroupSelectionStates)] + [TemplateVisualState(Name = DataPoint.StateRevealShown, GroupName = DataPoint.GroupRevealStates)] + [TemplateVisualState(Name = DataPoint.StateRevealHidden, GroupName = DataPoint.GroupRevealStates)] + public class PieDataPoint : DataPoint + { + /// + /// The name of the slice template part. + /// + private const string SliceName = "Slice"; + + /// + /// Name of the ActualDataPointStyle property. + /// + internal const string ActualDataPointStyleName = "ActualDataPointStyle"; + + #region public Geometry Geometry + /// + /// Gets or sets the Geometry property which defines the shape of the + /// data point. + /// + public Geometry Geometry + { + get { return GetValue(GeometryProperty) as Geometry; } + set { SetValue(GeometryProperty, value); } + } + + /// + /// Identifies the Geometry dependency property. + /// + public static readonly DependencyProperty GeometryProperty = + DependencyProperty.Register( + "Geometry", + typeof(Geometry), + typeof(PieDataPoint), + null); + #endregion public Geometry Geometry + + // GeometrySelection and GeometryHighlight exist on Silverlight because + // a single Geometry object can not be the target of multiple + // TemplateBindings - yet the default template has 3 Paths that bind. + + #region public Geometry GeometrySelection + /// + /// Gets or sets the Geometry which defines the shape of a point. The + /// GeometrySelection property is a copy of the Geometry property. + /// + public Geometry GeometrySelection + { + get { return GetValue(GeometrySelectionProperty) as Geometry; } + set { SetValue(GeometrySelectionProperty, value); } + } + + /// + /// Identifies the GeometrySelection dependency property. + /// + public static readonly DependencyProperty GeometrySelectionProperty = + DependencyProperty.Register( + "GeometrySelection", + typeof(Geometry), + typeof(PieDataPoint), + null); + #endregion public Geometry GeometrySelection + + #region public Geometry GeometryHighlight + /// + /// Gets or sets the GeometryHighlight property which is a clone of the + /// Geometry property. + /// + public Geometry GeometryHighlight + { + get { return GetValue(GeometryHighlightProperty) as Geometry; } + set { SetValue(GeometryHighlightProperty, value); } + } + + /// + /// Identifies the GeometryHighlight dependency property. + /// + public static readonly DependencyProperty GeometryHighlightProperty = + DependencyProperty.Register( + "GeometryHighlight", + typeof(Geometry), + typeof(PieDataPoint), + null); + #endregion public Geometry GeometryHighlight + + /// + /// Occurs when the actual offset ratio of the pie data point changes. + /// + internal event RoutedPropertyChangedEventHandler ActualOffsetRatioChanged; + + #region public double ActualOffsetRatio + /// + /// Gets or sets the offset ratio that is displayed on the screen. + /// + public double ActualOffsetRatio + { + get { return (double)GetValue(ActualOffsetRatioProperty); } + set { SetValue(ActualOffsetRatioProperty, value); } + } + + /// + /// Identifies the ActualOffsetRatio dependency property. + /// + public static readonly DependencyProperty ActualOffsetRatioProperty = + DependencyProperty.Register( + "ActualOffsetRatio", + typeof(double), + typeof(PieDataPoint), + new PropertyMetadata(OnActualOffsetRatioPropertyChanged)); + + /// + /// Called when the value of the ActualOffsetRatioProperty property changes. + /// + /// PieDataPoint that changed its ActualOffsetRatio. + /// Event arguments. + private static void OnActualOffsetRatioPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + PieDataPoint source = (PieDataPoint)d; + double oldValue = (double)e.OldValue; + double newValue = (double)e.NewValue; + source.OnActualOffsetRatioPropertyChanged(oldValue, newValue); + } + + /// + /// Called when the value of the ActualOffsetRatioProperty property changes. + /// + /// The value to be replaced. + /// The new value. + private void OnActualOffsetRatioPropertyChanged(double oldValue, double newValue) + { + RoutedPropertyChangedEventHandler handler = this.ActualOffsetRatioChanged; + if (handler != null) + { + handler(this, new RoutedPropertyChangedEventArgs(oldValue, newValue)); + } + + if (DesignerProperties.GetIsInDesignMode(this)) + { + PieSeries.UpdatePieDataPointGeometry(this, ActualWidth, ActualHeight); + } + } + #endregion public double ActualOffsetRatio + + /// + /// An event raised when the actual ratio of the pie data point is + /// changed. + /// + internal event RoutedPropertyChangedEventHandler ActualRatioChanged; + + #region public double ActualRatio + /// + /// Gets or sets the ratio displayed on the screen. + /// + public double ActualRatio + { + get { return (double)GetValue(ActualRatioProperty); } + set { SetValue(ActualRatioProperty, value); } + } + + /// + /// Identifies the ActualRatio dependency property. + /// + public static readonly DependencyProperty ActualRatioProperty = + DependencyProperty.Register( + "ActualRatio", + typeof(double), + typeof(PieDataPoint), + new PropertyMetadata(OnActualRatioPropertyChanged)); + + /// + /// Called when the value of the ActualRatioProperty property changes. + /// + /// PieDataPoint that changed its ActualRatio. + /// Event arguments. + private static void OnActualRatioPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + PieDataPoint source = (PieDataPoint)d; + double oldValue = (double)e.OldValue; + double newValue = (double)e.NewValue; + source.OnActualRatioPropertyChanged(oldValue, newValue); + } + + /// + /// Called when the value of the ActualRatioProperty property changes. + /// + /// The value to be replaced. + /// The new value. + private void OnActualRatioPropertyChanged(double oldValue, double newValue) + { + if (ValueHelper.CanGraph(newValue)) + { + RoutedPropertyChangedEventHandler handler = this.ActualRatioChanged; + if (handler != null) + { + handler(this, new RoutedPropertyChangedEventArgs(oldValue, newValue)); + } + } + else + { + this.ActualRatio = 0.0; + } + + if (DesignerProperties.GetIsInDesignMode(this)) + { + PieSeries.UpdatePieDataPointGeometry(this, ActualWidth, ActualHeight); + } + } + #endregion public double ActualRatio + + #region public string FormattedRatio + /// + /// Gets the Ratio with the value of the RatioStringFormat property applied. + /// + public string FormattedRatio + { + get { return GetValue(FormattedRatioProperty) as string; } + } + + /// + /// Identifies the FormattedRatio dependency property. + /// + public static readonly DependencyProperty FormattedRatioProperty = + DependencyProperty.Register( + "FormattedRatio", + typeof(string), + typeof(PieDataPoint), + null); + #endregion public string FormattedRatio + + /// + /// An event raised when the offset ratio of the pie data point is + /// changed. + /// + internal event RoutedPropertyChangedEventHandler OffsetRatioChanged; + + #region public double OffsetRatio + /// + /// Gets or sets the offset ratio of the pie data point. + /// + public double OffsetRatio + { + get { return (double)GetValue(OffsetRatioProperty); } + set { SetValue(OffsetRatioProperty, value); } + } + + /// + /// Identifies the OffsetRatio dependency property. + /// + public static readonly DependencyProperty OffsetRatioProperty = + DependencyProperty.Register( + "OffsetRatio", + typeof(double), + typeof(PieDataPoint), + new PropertyMetadata(OnOffsetRatioPropertyChanged)); + + /// + /// Called when the value of the OffsetRatioProperty property changes. + /// + /// PieDataPoint that changed its OffsetRatio. + /// Event arguments. + private static void OnOffsetRatioPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + PieDataPoint source = (PieDataPoint)d; + double oldValue = (double)e.OldValue; + double newValue = (double)e.NewValue; + source.OnOffsetRatioPropertyChanged(oldValue, newValue); + } + + /// + /// Called when the value of the OffsetRatioProperty property changes. + /// + /// The value to be replaced. + /// The new value. + private void OnOffsetRatioPropertyChanged(double oldValue, double newValue) + { + if (ValueHelper.CanGraph(newValue)) + { + RoutedPropertyChangedEventHandler handler = this.OffsetRatioChanged; + if (handler != null) + { + handler(this, new RoutedPropertyChangedEventArgs(oldValue, newValue)); + } + if (this.State == DataPointState.Created) + { + ActualOffsetRatio = newValue; + } + } + else + { + this.OffsetRatio = 0.0; + } + } + #endregion public double OffsetRatio + + /// + /// An event raised when the ratio of the pie data point is + /// changed. + /// + internal event RoutedPropertyChangedEventHandler RatioChanged; + + #region public double Ratio + /// + /// Gets or sets the ratio of the total that the data point + /// represents. + /// + public double Ratio + { + get { return (double)GetValue(RatioProperty); } + set { SetValue(RatioProperty, value); } + } + + /// + /// Identifies the Ratio dependency property. + /// + public static readonly DependencyProperty RatioProperty = + DependencyProperty.Register( + "Ratio", + typeof(double), + typeof(PieDataPoint), + new PropertyMetadata(OnRatioPropertyChanged)); + + /// + /// Called when the value of the RatioProperty property changes. + /// + /// PieDataPoint that changed its Ratio. + /// Event arguments. + private static void OnRatioPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + PieDataPoint source = (PieDataPoint)d; + double oldValue = (double)e.OldValue; + double newValue = (double)e.NewValue; + source.OnRatioPropertyChanged(oldValue, newValue); + } + + /// + /// Called when the value of the RatioProperty property changes. + /// + /// The value to be replaced. + /// The new value. + private void OnRatioPropertyChanged(double oldValue, double newValue) + { + if (ValueHelper.CanGraph(newValue)) + { + SetFormattedProperty(FormattedRatioProperty, RatioStringFormat, newValue); + RoutedPropertyChangedEventHandler handler = this.RatioChanged; + if (handler != null) + { + handler(this, new RoutedPropertyChangedEventArgs(oldValue, newValue)); + } + + if (this.State == DataPointState.Created) + { + ActualRatio = newValue; + } + } + else + { + this.Ratio = 0.0; + } + } + #endregion public double Ratio + + #region public string RatioStringFormat + /// + /// Gets or sets the format string for the FormattedRatio property. + /// + public string RatioStringFormat + { + get { return GetValue(RatioStringFormatProperty) as string; } + set { SetValue(RatioStringFormatProperty, value); } + } + + /// + /// Identifies the RatioStringFormat dependency property. + /// + public static readonly DependencyProperty RatioStringFormatProperty = + DependencyProperty.Register( + "RatioStringFormat", + typeof(string), + typeof(PieDataPoint), + new PropertyMetadata(null, OnRatioStringFormatPropertyChanged)); + + /// + /// Called when the value of the RatioStringFormatProperty property changes. + /// + /// PieDataPoint that changed its RatioStringFormat. + /// Event arguments. + private static void OnRatioStringFormatPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + PieDataPoint source = d as PieDataPoint; + string newValue = e.NewValue as string; + source.OnRatioStringFormatPropertyChanged(newValue); + } + + /// + /// Called when the value of the RatioStringFormatProperty property changes. + /// + /// The new value. + private void OnRatioStringFormatPropertyChanged(string newValue) + { + SetFormattedProperty(FormattedRatioProperty, newValue, Ratio); + } + #endregion public string RatioStringFormat + + #region internal Style ActualDataPointStyle + /// + /// Gets or sets the actual style used for the data points. + /// + internal Style ActualDataPointStyle + { + get { return GetValue(ActualDataPointStyleProperty) as Style; } + set { SetValue(ActualDataPointStyleProperty, value); } + } + + /// + /// Identifies the ActualDataPointStyle dependency property. + /// + internal static readonly DependencyProperty ActualDataPointStyleProperty = + DependencyProperty.Register( + ActualDataPointStyleName, + typeof(Style), + typeof(PieDataPoint), + null); + #endregion internal Style ActualDataPointStyle + + #region internal Style ActualLegendItemStyle + /// + /// Gets or sets the actual style used for the legend item. + /// + internal Style ActualLegendItemStyle + { + get { return GetValue(ActualLegendItemStyleProperty) as Style; } + set { SetValue(ActualLegendItemStyleProperty, value); } + } + + /// + /// Identifies the ActualLegendItemStyle dependency property. + /// + internal static readonly DependencyProperty ActualLegendItemStyleProperty = + DependencyProperty.Register( + DataPointSeries.ActualLegendItemStyleName, + typeof(Style), + typeof(PieDataPoint), + null); + #endregion protected Style ActualLegendItemStyle + + /// + /// Gets the Palette-dispensed ResourceDictionary for the Series. + /// + protected internal ResourceDictionary PaletteResources { get; internal set; } + + /// + /// Gets or sets the element that represents the pie slice. + /// + private UIElement SliceElement { get; set; } + +#if !SILVERLIGHT + /// + /// Initializes the static members of the PieDataPoint class. + /// + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Dependency properties are initialized in-line.")] + static PieDataPoint() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(PieDataPoint), new FrameworkPropertyMetadata(typeof(PieDataPoint))); + } + +#endif + /// + /// Initializes a new instance of the PieDataPoint class. + /// + public PieDataPoint() + { +#if SILVERLIGHT + DefaultStyleKey = typeof(PieDataPoint); +#endif + + if (DesignerProperties.GetIsInDesignMode(this)) + { + // Create default design-mode-friendly settings + ActualRatio = 0.2; + SizeChanged += delegate(object sender, SizeChangedEventArgs e) + { + // Handle SizeChanged event to update Geometry dynamically + PieSeries.UpdatePieDataPointGeometry(this, e.NewSize.Width, e.NewSize.Height); + }; + } + } + + /// + /// Builds the visual tree for the PieDataPoint when a new template is applied. + /// + public override void OnApplyTemplate() + { + if (null != SliceElement) + { + SliceElement.MouseEnter -= new MouseEventHandler(SliceElement_MouseEnter); + SliceElement.MouseLeave -= new MouseEventHandler(SliceElement_MouseLeave); + } + + base.OnApplyTemplate(); + + SliceElement = GetTemplateChild(SliceName) as UIElement; + + if (null != SliceElement) + { + SliceElement.MouseEnter += new MouseEventHandler(SliceElement_MouseEnter); + SliceElement.MouseLeave += new MouseEventHandler(SliceElement_MouseLeave); + } + } + + /// + /// Provides handling for the MouseEnter event. + /// + /// The event data. + protected override void OnMouseEnter(MouseEventArgs e) + { + // Do nothing because PieDataPoint handles SliceElement.MouseEnter instead + } + + /// + /// Provides handling for the MouseLeave event. + /// + /// The event data. + protected override void OnMouseLeave(MouseEventArgs e) + { + // Do nothing because PieDataPoint handles SliceElement.MouseLeave instead + } + + /// + /// Provides handling for the MouseEnter event. + /// + /// Event source. + /// The event data. + private void SliceElement_MouseEnter(object sender, MouseEventArgs e) + { + // Defer to Control's default MouseEnter handling + base.OnMouseEnter(e); + } + + /// + /// Provides handling for the MouseLeave event. + /// + /// Event source. + /// The event data. + private void SliceElement_MouseLeave(object sender, MouseEventArgs e) + { + // Defer to Control's default MouseLeave handling + base.OnMouseLeave(e); + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/DataPoint/ScatterDataPoint.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/DataPoint/ScatterDataPoint.cs new file mode 100644 index 00000000..e27ccee6 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/DataPoint/ScatterDataPoint.cs @@ -0,0 +1,40 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Represents a data point used for a scatter series. + /// + /// Preview + [TemplateVisualState(Name = DataPoint.StateCommonNormal, GroupName = DataPoint.GroupCommonStates)] + [TemplateVisualState(Name = DataPoint.StateCommonMouseOver, GroupName = DataPoint.GroupCommonStates)] + [TemplateVisualState(Name = DataPoint.StateSelectionUnselected, GroupName = DataPoint.GroupSelectionStates)] + [TemplateVisualState(Name = DataPoint.StateSelectionSelected, GroupName = DataPoint.GroupSelectionStates)] + [TemplateVisualState(Name = DataPoint.StateRevealShown, GroupName = DataPoint.GroupRevealStates)] + [TemplateVisualState(Name = DataPoint.StateRevealHidden, GroupName = DataPoint.GroupRevealStates)] + public partial class ScatterDataPoint : DataPoint + { +#if !SILVERLIGHT + /// + /// Initializes the static members of the ScatterDataPoint class. + /// + static ScatterDataPoint() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(ScatterDataPoint), new FrameworkPropertyMetadata(typeof(ScatterDataPoint))); + } + +#endif + /// + /// Initializes a new instance of the ScatterDataPoint class. + /// + public ScatterDataPoint() + { +#if SILVERLIGHT + this.DefaultStyleKey = typeof(ScatterDataPoint); +#endif + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/FrameworkElementExtensions.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/FrameworkElementExtensions.cs new file mode 100644 index 00000000..dc65802b --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/FrameworkElementExtensions.cs @@ -0,0 +1,57 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// A set of extension methods for the DataPoint class. + /// + internal static class FrameworkElementExtensions + { + /// + /// Returns the actual margin for a given framework element and axis. + /// + /// The framework element. + /// The axis along which to return the margin. + /// + /// The margin for a given framework element and axis. + /// + public static double GetActualMargin(this FrameworkElement element, IAxis axis) + { + double length = 0.0; + if (axis.Orientation == AxisOrientation.X) + { + length = element.ActualWidth; + } + else if (axis.Orientation == AxisOrientation.Y) + { + length = element.ActualHeight; + } + return length / 2.0; + } + + /// + /// Returns the margin for a given framework element and axis. + /// + /// The framework element. + /// The axis along which to return the margin. + /// + /// The margin for a given framework element and axis. + /// + public static double GetMargin(this FrameworkElement element, IAxis axis) + { + double length = 0.0; + if (axis.Orientation == AxisOrientation.X) + { + length = !double.IsNaN(element.Width) ? element.Width : element.ActualWidth; + } + else if (axis.Orientation == AxisOrientation.Y) + { + length = !double.IsNaN(element.Height) ? element.Height : element.ActualHeight; + } + return length / 2.0; + } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/IRequireSeriesHost.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/IRequireSeriesHost.cs new file mode 100644 index 00000000..41561d7a --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/IRequireSeriesHost.cs @@ -0,0 +1,18 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// An object that implements this interface requires a series host. + /// + public interface IRequireSeriesHost + { + /// + /// Gets or sets the series host. + /// + ISeriesHost SeriesHost { get; set; } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/ISeriesHost.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/ISeriesHost.cs new file mode 100644 index 00000000..88d8593f --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/ISeriesHost.cs @@ -0,0 +1,40 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Windows; +using System.Windows.Controls; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Defines properties, methods and events for classes that host a + /// collection of Series objects. + /// + /// Preview + public interface ISeriesHost : IRequireSeriesHost, IResourceDictionaryDispenser + { + /// + /// Gets the collection of axes the series host has available. + /// + ObservableCollection Axes { get; } + + /// + /// Gets the collection of series the series host has available. + /// + ObservableCollection Series { get; } + + /// + /// Gets the foreground elements. + /// + ObservableCollection ForegroundElements { get; } + + /// + /// Gets the background elements. + /// + ObservableCollection BackgroundElements { get; } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/ISeriesHostExtensions.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/ISeriesHostExtensions.cs new file mode 100644 index 00000000..f2c2fe42 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/ISeriesHostExtensions.cs @@ -0,0 +1,54 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; +using System.Linq; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Extension methods for series hosts. + /// + internal static class ISeriesHostExtensions + { + /// + /// Gets all series that track their global indexes recursively. + /// + /// The root series host. + /// A sequence of series. + public static IEnumerable GetDescendentSeries(this ISeriesHost rootSeriesHost) + { + Queue series = new Queue(rootSeriesHost.Series); + while (series.Count != 0) + { + ISeries currentSeries = series.Dequeue(); + yield return currentSeries; + + ISeriesHost seriesHost = currentSeries as ISeriesHost; + if (seriesHost != null) + { + foreach (ISeries childSeries in seriesHost.Series) + { + series.Enqueue(childSeries); + } + } + } + } + + /// + /// Gets a value indicating whether an axis is in use by the series + /// host. + /// + /// The series host. + /// The axis that may or may not be used by a + /// series. + /// A value indicating whether an axis is in use by the series + /// host. + public static bool IsUsedByASeries(this ISeriesHost that, IAxis axis) + { + return axis.RegisteredListeners.OfType().Intersect(that.Series).Any(); + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Primitives/DelegatingListBox.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Primitives/DelegatingListBox.cs new file mode 100644 index 00000000..af6a5586 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Primitives/DelegatingListBox.cs @@ -0,0 +1,111 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization.Charting.Primitives +{ + /// + /// Subclasses ListBox to provide an easy way for a consumer of + /// ListBox to hook into the four standard ListBox *Container* + /// overrides. + /// + /// Preview + public class DelegatingListBox : ListBox + { + /// + /// Gets or sets a function to call when the + /// IsItemItsOwnContainerOverride method executes. + /// + public Func IsItemItsOwnContainer { get; set; } + + /// + /// Gets or sets a function to call when the + /// GetContainerForItem method executes. + /// + public Func GetContainerForItem { get; set; } + + /// + /// Gets or sets an action to call when the + /// PrepareContainerForItem method executes. + /// + public Action PrepareContainerForItem { get; set; } + + /// + /// Gets or sets an action to call when the + /// ClearContainerForItem method executes. + /// + public Action ClearContainerForItem { get; set; } + +#if !SILVERLIGHT + /// + /// Initializes static members of the DelegatingListBox class. + /// + static DelegatingListBox() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(DelegatingListBox), new FrameworkPropertyMetadata(typeof(DelegatingListBox))); + } +#endif + + /// + /// Initializes a new instance of the DelegatingListBox class. + /// + public DelegatingListBox() + { +#if SILVERLIGHT + DefaultStyleKey = typeof(DelegatingListBox); +#endif + } + + /// + /// Determines if the specified item is (or is eligible to be) its own container. + /// + /// The item to check. + /// True if the item is (or is eligible to be) its own container; otherwise, false. + protected override bool IsItemItsOwnContainerOverride(object item) + { + return (null != IsItemItsOwnContainer) ? + IsItemItsOwnContainer(item) : + base.IsItemItsOwnContainerOverride(item); + } + + /// + /// Creates or identifies the element that is used to display the given item. + /// + /// The element that is used to display the given item. + protected override DependencyObject GetContainerForItemOverride() + { + return (null != GetContainerForItem) ? + GetContainerForItem() : + base.GetContainerForItemOverride(); + } + + /// + /// Prepares the specified element to display the specified item. + /// + /// The element used to display the specified item. + /// The item to display. + protected override void PrepareContainerForItemOverride(DependencyObject element, object item) + { + base.PrepareContainerForItemOverride(element, item); + if (null != PrepareContainerForItem) + { + PrepareContainerForItem(element, item); + } + } + + /// + /// Undoes the effects of the PrepareContainerForItemOverride method. + /// + /// The container element. + /// The item to display. + protected override void ClearContainerForItemOverride(DependencyObject element, object item) + { + base.ClearContainerForItemOverride(element, item); + if (null != ClearContainerForItem) + { + ClearContainerForItem(element, item); + } + } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Primitives/Edge.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Primitives/Edge.cs new file mode 100644 index 00000000..8a7ab426 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Primitives/Edge.cs @@ -0,0 +1,44 @@ +// (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; + +namespace System.Windows.Controls.DataVisualization.Charting.Primitives +{ + /// + /// Specifies the edge position of a child element that is inside an + /// EdgePanel. + /// + /// Preview + public enum Edge + { + /// + /// A child element that is positioned in the center of a EdgePanel. + /// + Center, + + /// + /// A child element that is positioned on the left side of the + /// EdgePanel. + /// + Left, + + /// + /// A child element that is positioned at the top of the EdgePanel. + /// + Top, + + /// + /// A child element that is positioned on the right side of the + /// EdgePanel. + /// + Right, + + /// + /// A child element that is positioned at the bottom of the EdgePanel. + /// + Bottom, + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Primitives/EdgePanel.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Primitives/EdgePanel.cs new file mode 100644 index 00000000..c59d026e --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Primitives/EdgePanel.cs @@ -0,0 +1,554 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using System.Windows.Media; + +namespace System.Windows.Controls.DataVisualization.Charting.Primitives +{ + /// + /// Defines an area where you can arrange child elements either horizontally + /// or vertically, relative to each other. + /// + /// Preview + public class EdgePanel : Panel + { + /// + /// The maximum number of iterations. + /// + private const int MaximumIterations = 10; + + /// + /// A flag that ignores a property change when set. + /// + private static bool _ignorePropertyChange; + + #region public attached Edge Edge + /// + /// Gets the value of the Edge attached property for a specified + /// UIElement. + /// + /// + /// The element from which the property value is read. + /// + /// The Edge property value for the element. + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "EdgePanel only has UIElement children")] + public static Edge GetEdge(UIElement element) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + return (Edge)element.GetValue(EdgeProperty); + } + + /// + /// Sets the value of the Edge attached property to a specified element. + /// + /// + /// The element to which the attached property is written. + /// + /// The needed Edge value. + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "EdgePanel only has UIElement children")] + public static void SetEdge(UIElement element, Edge edge) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + element.SetValue(EdgeProperty, edge); + } + + /// + /// Identifies the Edge dependency property. + /// + public static readonly DependencyProperty EdgeProperty = + DependencyProperty.RegisterAttached( + "Edge", + typeof(Edge), + typeof(EdgePanel), + new PropertyMetadata(Edge.Center, OnEdgePropertyChanged)); + + /// + /// EdgeProperty property changed handler. + /// + /// UIElement that changed its Edge. + /// Event arguments. + [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "Almost always set from the attached property CLR setter.")] + private static void OnEdgePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + // Ignore the change if requested + if (_ignorePropertyChange) + { + _ignorePropertyChange = false; + return; + } + + UIElement element = (UIElement)d; + Edge value = (Edge)e.NewValue; + + // Validate the Edge property + if ((value != Edge.Left) && + (value != Edge.Top) && + (value != Edge.Right) && + (value != Edge.Center) && + (value != Edge.Bottom)) + { + // Reset the property to its original state before throwing + _ignorePropertyChange = true; + element.SetValue(EdgeProperty, (Edge)e.OldValue); + + string message = string.Format( + CultureInfo.InvariantCulture, + Properties.Resources.EdgePanel_OnEdgePropertyChanged, + value); + + throw new ArgumentException(message, "value"); + } + + // Cause the EdgePanel to update its layout when a child changes + EdgePanel panel = VisualTreeHelper.GetParent(element) as EdgePanel; + if (panel != null) + { + panel.InvalidateMeasure(); + } + } + #endregion public attached Edge Edge + + /// + /// Initializes a new instance of the EdgePanel class. + /// + public EdgePanel() + { + this.SizeChanged += new SizeChangedEventHandler(EdgePanelSizeChanged); + } + + /// + /// Invalidate measure when edge panel is resized. + /// + /// The source of the event. + /// Information about the event. + private void EdgePanelSizeChanged(object sender, SizeChangedEventArgs e) + { + InvalidateMeasure(); + } + + /// + /// The left rectangle in which to render left elements. + /// + private Rect _leftRect; + + /// + /// The right rectangle in which to render right elements. + /// + private Rect _rightRect; + + /// + /// The top rectangle in which to render top elements. + /// + private Rect _topRect; + + /// + /// The bottom rectangle in which to render bottom elements. + /// + private Rect _bottomRect; + + /// + /// Measures the children of a EdgePanel in anticipation of arranging + /// them during the ArrangeOverride pass. + /// + /// A maximum Size to not exceed. + /// The desired size of the EdgePanel. + [SuppressMessage("Microsoft.Maintainability", "CA1505:AvoidUnmaintainableCode", Justification = "Code is by nature difficult to refactor into several methods.")] + [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "Compat with WPF.")] + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Splitting up method will make it more difficult to understand.")] + protected override Size MeasureOverride(Size constraint) + { + constraint = new Size(this.ActualWidth, this.ActualHeight); + + IList leftElements = this.Children.OfType().Where(element => GetEdge(element) == Edge.Left).ToList(); + IList rightElements = this.Children.OfType().Where(element => GetEdge(element) == Edge.Right).ToList(); + IList bottomElements = this.Children.OfType().Where(element => GetEdge(element) == Edge.Bottom).ToList(); + IList topElements = this.Children.OfType().Where(element => GetEdge(element) == Edge.Top).ToList(); + + Rect totalRect = SafeCreateRect(0, 0, constraint.Width, constraint.Height); + + _leftRect = (leftElements.Count > 0) ? totalRect : Rect.Empty; + _bottomRect = (bottomElements.Count > 0) ? totalRect : Rect.Empty; + _rightRect = (rightElements.Count > 0) ? totalRect : Rect.Empty; + _topRect = (topElements.Count > 0) ? totalRect : Rect.Empty; + + double rightAxesWidth = 0.0; + double leftAxesWidth = 0.0; + double topAxesHeight = 0.0; + double bottomAxesHeight = 0.0; + + double maxRightRequestedWidth = 0; + double maxLeftRequestedWidth = 0; + double maxTopRequestedHeight = 0; + double maxBottomRequestedHeight = 0; + + double previousRightAxesWidth = rightAxesWidth; + double previousLeftAxesWidth = leftAxesWidth; + double previousTopAxesHeight = topAxesHeight; + double previousBottomAxesHeight = bottomAxesHeight; + + int iterations = 0; + // Measure each of the Children + while (true) + { + // Measure the children using the rectangle regions. + if (rightElements.Count > 0) + { + Size rightSize = new Size(constraint.Width, _rightRect.Height); + foreach (UIElement rightUIElement in rightElements) + { + rightUIElement.Measure(rightSize); + } + + previousRightAxesWidth = rightAxesWidth; + rightAxesWidth = rightElements.Select(axis => axis.DesiredSize.Width).SumOrDefault(); + maxRightRequestedWidth = Math.Max(maxRightRequestedWidth, rightAxesWidth); + _rightRect = + SafeCreateRect( + constraint.Width - rightAxesWidth, + _rightRect.Top, + rightAxesWidth, + _rightRect.Height); + } + + if (topElements.Count > 0) + { + Size topSize = new Size(_topRect.Width, constraint.Height); + foreach (UIElement topUIElement in topElements) + { + topUIElement.Measure(topSize); + } + + previousTopAxesHeight = topAxesHeight; + topAxesHeight = topElements.Select(axis => axis.DesiredSize.Height).SumOrDefault(); + maxTopRequestedHeight = Math.Max(maxTopRequestedHeight, topAxesHeight); + _topRect = + SafeCreateRect( + _topRect.Left, + _topRect.Top, + _topRect.Width, + topAxesHeight); + } + + if (leftElements.Count > 0) + { + Size leftSize = new Size(constraint.Width, _leftRect.Height); + foreach (UIElement leftUIElement in leftElements) + { + leftUIElement.Measure(leftSize); + } + + previousLeftAxesWidth = leftAxesWidth; + leftAxesWidth = leftElements.Select(axis => axis.DesiredSize.Width).SumOrDefault(); + maxLeftRequestedWidth = Math.Max(maxLeftRequestedWidth, leftAxesWidth); + _leftRect = + SafeCreateRect( + _leftRect.Left, + _leftRect.Top, + leftElements.Select(axis => axis.DesiredSize.Width).SumOrDefault(), + _leftRect.Height); + } + + if (bottomElements.Count > 0) + { + Size bottomSize = new Size(_bottomRect.Width, constraint.Height); + foreach (UIElement bottomUIElement in bottomElements) + { + bottomUIElement.Measure(bottomSize); + } + + previousBottomAxesHeight = bottomAxesHeight; + bottomAxesHeight = bottomElements.Select(axis => axis.DesiredSize.Height).SumOrDefault(); + maxBottomRequestedHeight = Math.Max(maxBottomRequestedHeight, bottomAxesHeight); + _bottomRect = + SafeCreateRect( + _bottomRect.Left, + constraint.Height - bottomAxesHeight, + _bottomRect.Width, + bottomAxesHeight); + } + + // Ensuring that parallel axes don't collide + Rect leftRightCollisionRect = _leftRect; + leftRightCollisionRect.Intersect(_rightRect); + + Rect topBottomCollisionRect = _topRect; + topBottomCollisionRect.Intersect(_bottomRect); + + if (!leftRightCollisionRect.IsEmptyOrHasNoSize() || !topBottomCollisionRect.IsEmptyOrHasNoSize()) + { + return new Size(); + } + + // Resolving perpendicular axes collisions + Rect leftTopCollisionRect = _leftRect; + leftTopCollisionRect.Intersect(_topRect); + + Rect rightTopCollisionRect = _rightRect; + rightTopCollisionRect.Intersect(_topRect); + + Rect leftBottomCollisionRect = _leftRect; + leftBottomCollisionRect.Intersect(_bottomRect); + + Rect rightBottomCollisionRect = _rightRect; + rightBottomCollisionRect.Intersect(_bottomRect); + + if (leftBottomCollisionRect.IsEmptyOrHasNoSize() + && rightBottomCollisionRect.IsEmptyOrHasNoSize() + && leftTopCollisionRect.IsEmptyOrHasNoSize() + && rightTopCollisionRect.IsEmptyOrHasNoSize() + && previousBottomAxesHeight == bottomAxesHeight + && previousLeftAxesWidth == leftAxesWidth + && previousRightAxesWidth == rightAxesWidth + && previousTopAxesHeight == topAxesHeight) + { + break; + } + + if (iterations == MaximumIterations) + { + _leftRect = SafeCreateRect(0, maxTopRequestedHeight, maxLeftRequestedWidth, (constraint.Height - maxTopRequestedHeight) - maxBottomRequestedHeight); + _rightRect = SafeCreateRect(constraint.Width - maxRightRequestedWidth, maxTopRequestedHeight, maxRightRequestedWidth, (constraint.Height - maxTopRequestedHeight) - maxBottomRequestedHeight); + _bottomRect = SafeCreateRect(maxLeftRequestedWidth, constraint.Height - maxBottomRequestedHeight, (constraint.Width - maxLeftRequestedWidth) - maxRightRequestedWidth, maxBottomRequestedHeight); + _topRect = SafeCreateRect(maxLeftRequestedWidth, 0, (constraint.Width - maxLeftRequestedWidth) - maxRightRequestedWidth, maxTopRequestedHeight); + + foreach (UIElement leftElement in leftElements) + { + leftElement.Measure(new Size(_leftRect.Width, _leftRect.Height)); + } + + foreach (UIElement rightElement in rightElements) + { + rightElement.Measure(new Size(_rightRect.Width, _rightRect.Height)); + } + + foreach (UIElement bottomElement in bottomElements) + { + bottomElement.Measure(new Size(_bottomRect.Width, _bottomRect.Height)); + } + + foreach (UIElement topElement in topElements) + { + topElement.Measure(new Size(_topRect.Width, _topRect.Height)); + } + break; + } + + if (!leftBottomCollisionRect.IsEmptyOrHasNoSize()) + { + _leftRect = + SafeCreateRect( + _leftRect.Left, + _leftRect.Top, + _leftRect.Width, + _leftRect.Height - leftBottomCollisionRect.Height); + + _bottomRect = + SafeCreateRect( + _bottomRect.Left + leftBottomCollisionRect.Width, + _bottomRect.Top, + _bottomRect.Width - leftBottomCollisionRect.Width, + _bottomRect.Height); + } + + if (!leftTopCollisionRect.IsEmptyOrHasNoSize()) + { + _leftRect = + SafeCreateRect( + _leftRect.Left, + _leftRect.Top + leftTopCollisionRect.Height, + _leftRect.Width, + _leftRect.Height - leftTopCollisionRect.Height); + + _topRect = + SafeCreateRect( + _topRect.Left + leftTopCollisionRect.Width, + _topRect.Top, + _topRect.Width - leftTopCollisionRect.Width, + _topRect.Height); + } + + if (!rightBottomCollisionRect.IsEmptyOrHasNoSize()) + { + _rightRect = + SafeCreateRect( + _rightRect.Left, + _rightRect.Top, + _rightRect.Width, + _rightRect.Height - rightBottomCollisionRect.Height); + + _bottomRect = + SafeCreateRect( + _bottomRect.Left, + _bottomRect.Top, + _bottomRect.Width - rightBottomCollisionRect.Width, + _bottomRect.Height); + } + + if (!rightTopCollisionRect.IsEmptyOrHasNoSize()) + { + _rightRect = + SafeCreateRect( + _rightRect.Left, + _rightRect.Top + rightTopCollisionRect.Height, + _rightRect.Width, + _rightRect.Height - rightTopCollisionRect.Height); + + _topRect = + SafeCreateRect( + _topRect.Left, + _topRect.Top, + _topRect.Width - rightTopCollisionRect.Width, + _topRect.Height); + } + + // Bring axis measure rectangles together if there are gaps + // between them. + if (!_leftRect.IsEmpty) + { + _leftRect = + new Rect( + new Point(_leftRect.Left, _topRect.BottomOrDefault(0)), + new Point(_leftRect.Right, _bottomRect.TopOrDefault(constraint.Height))); + } + + if (!_rightRect.IsEmpty) + { + _rightRect = + new Rect( + new Point(_rightRect.Left, _topRect.BottomOrDefault(0)), + new Point(_rightRect.Right, _bottomRect.TopOrDefault(constraint.Height))); + } + + if (!_bottomRect.IsEmpty) + { + _bottomRect = + new Rect( + new Point(_leftRect.RightOrDefault(0), _bottomRect.Top), + new Point(_rightRect.LeftOrDefault(constraint.Width), _bottomRect.Bottom)); + } + + if (!_topRect.IsEmpty) + { + _topRect = + new Rect( + new Point(_leftRect.RightOrDefault(0), _topRect.Top), + new Point(_rightRect.LeftOrDefault(constraint.Width), _topRect.Bottom)); + } + + iterations++; + } + + Size centerSize = + new Size( + (constraint.Width - _leftRect.WidthOrDefault(0)) - _rightRect.WidthOrDefault(0), + (constraint.Height - _topRect.HeightOrDefault(0)) - _bottomRect.HeightOrDefault(0)); + + foreach (UIElement element in Children.OfType().Where(child => GetEdge(child) == Edge.Center)) + { + element.Measure(centerSize); + } + + return new Size(); + } + + /// + /// Arranges the content (child elements) of a EdgePanel element. + /// + /// + /// The Size the EdgePanel uses to arrange its child elements. + /// + /// The arranged size of the EdgePanel. + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Splitting up method will make it more difficult to understand.")] + [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "Compat with WPF.")] + protected override Size ArrangeOverride(Size arrangeSize) + { + if (arrangeSize.Width == 0 || arrangeSize.Height == 0 || !ValueHelper.CanGraph(arrangeSize.Width) || !ValueHelper.CanGraph(arrangeSize.Height)) + { + return arrangeSize; + } + + IList leftElements = this.Children.OfType().Where(element => GetEdge(element) == Edge.Left).ToList(); + IList rightElements = this.Children.OfType().Where(element => GetEdge(element) == Edge.Right).ToList(); + IList bottomElements = this.Children.OfType().Where(element => GetEdge(element) == Edge.Bottom).ToList(); + IList topElements = this.Children.OfType().Where(element => GetEdge(element) == Edge.Top).ToList(); + + if (!_bottomRect.IsEmpty) + { + double workingHeight = _bottomRect.Top; + foreach (UIElement bottomUIElement in bottomElements) + { + bottomUIElement.Arrange(SafeCreateRect(_leftRect.RightOrDefault(0), workingHeight, (arrangeSize.Width - _leftRect.WidthOrDefault(0)) - _rightRect.WidthOrDefault(0), bottomUIElement.DesiredSize.Height)); + workingHeight += bottomUIElement.DesiredSize.Height; + } + } + if (!_topRect.IsEmpty) + { + double workingTop = _topRect.Bottom; + foreach (UIElement topUIElement in topElements) + { + workingTop -= topUIElement.DesiredSize.Height; + topUIElement.Arrange(SafeCreateRect(_leftRect.RightOrDefault(0), workingTop, (arrangeSize.Width - _leftRect.WidthOrDefault(0)) - _rightRect.WidthOrDefault(0), topUIElement.DesiredSize.Height)); + } + } + + if (!_rightRect.IsEmpty) + { + double workingRight = _rightRect.Left; + foreach (UIElement rightUIElement in rightElements) + { + rightUIElement.Arrange(SafeCreateRect(workingRight, _topRect.BottomOrDefault(0), rightUIElement.DesiredSize.Width, (arrangeSize.Height - _bottomRect.HeightOrDefault(0)) - _topRect.HeightOrDefault(0))); + workingRight += rightUIElement.DesiredSize.Width; + } + } + + if (!_leftRect.IsEmpty) + { + double workingLeft = _leftRect.Right; + foreach (UIElement leftUIElement in leftElements) + { + workingLeft -= leftUIElement.DesiredSize.Width; + Rect leftRect = SafeCreateRect(workingLeft, _topRect.BottomOrDefault(0), leftUIElement.DesiredSize.Width, (arrangeSize.Height - _bottomRect.HeightOrDefault(0)) - _topRect.HeightOrDefault(0)); + leftUIElement.Arrange(leftRect); + } + } + + Rect centerRect = SafeCreateRect( + _leftRect.RightOrDefault(0), + _topRect.BottomOrDefault(0), + ((arrangeSize.Width - _leftRect.WidthOrDefault(0)) - _rightRect.WidthOrDefault(0)), + ((arrangeSize.Height - _topRect.HeightOrDefault(0)) - _bottomRect.HeightOrDefault(0))); + + foreach (UIElement element in Children.OfType().Where(child => GetEdge(child) == Edge.Center)) + { + element.Arrange(centerRect); + } + + return arrangeSize; + } + + /// + /// Creates a Rect safely by forcing width/height to be valid. + /// + /// Rect left parameter. + /// Rect top parameter. + /// Rect width parameter. + /// Rect height parameter. + /// New Rect struct. + private static Rect SafeCreateRect(double left, double top, double width, double height) + { + return new Rect(left, top, Math.Max(0.0, width), Math.Max(0.0, height)); + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/ResourceDictionaryDispensedEventArgs.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/ResourceDictionaryDispensedEventArgs.cs new file mode 100644 index 00000000..a0163334 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/ResourceDictionaryDispensedEventArgs.cs @@ -0,0 +1,56 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// Information describing the ResourceDictionary dispensed when a + /// ResourceDictionaryDispensed event is raised. + /// + internal class ResourceDictionaryDispensedEventArgs : EventArgs + { + /// + /// Initializes a new instance of the ResourceDictionaryDispensedEventArgs class. + /// + /// The index of the ResourceDictionary dispensed. + /// The ResourceDictionary dispensed. + public ResourceDictionaryDispensedEventArgs(int index, ResourceDictionary resourceDictionary) + { + this.ResourceDictionary = resourceDictionary; + this.Index = index; + } + + /// + /// Gets the index of the ResourceDictionary dispensed. + /// + public int Index { get; private set; } + + /// + /// Gets the ResourceDictionary dispensed. + /// + public ResourceDictionary ResourceDictionary { get; private set; } + + /// + /// Returns a value indicating whether two objects are equal. + /// + /// The other object. + /// + /// A value indicating whether the two objects are equal. + /// + public override bool Equals(object obj) + { + return base.Equals(obj); + } + + /// + /// Returns a hash code. + /// + /// A hash code. + public override int GetHashCode() + { + return base.GetHashCode(); + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/ResourceDictionaryDispenser.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/ResourceDictionaryDispenser.cs new file mode 100644 index 00000000..c8437f3a --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/ResourceDictionaryDispenser.cs @@ -0,0 +1,250 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics.CodeAnalysis; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// A class that rotates through a list of ResourceDictionaries. + /// + internal class ResourceDictionaryDispenser : IResourceDictionaryDispenser + { + /// + /// A linked list of ResourceDictionaries dispensed. + /// + private LinkedList _resourceDictionariesDispensed = new LinkedList(); + + /// + /// A bag of weak references to connected style enumerators. + /// + private WeakReferenceBag _resourceDictionaryEnumerators = new WeakReferenceBag(); + + /// + /// Value indicating whether to ignore that the enumerator has + /// dispensed a ResourceDictionary. + /// + private bool _ignoreResourceDictionaryDispensedByEnumerator; + + /// + /// The list of ResourceDictionaries of rotate. + /// + private IList _resourceDictionaries; + + /// + /// Gets or sets the list of ResourceDictionaries to rotate. + /// + public IList ResourceDictionaries + { + get + { + return _resourceDictionaries; + } + set + { + if (value != _resourceDictionaries) + { + { + INotifyCollectionChanged notifyCollectionChanged = _resourceDictionaries as INotifyCollectionChanged; + if (notifyCollectionChanged != null) + { + notifyCollectionChanged.CollectionChanged -= ResourceDictionariesCollectionChanged; + } + } + _resourceDictionaries = value; + { + INotifyCollectionChanged notifyCollectionChanged = _resourceDictionaries as INotifyCollectionChanged; + if (notifyCollectionChanged != null) + { + notifyCollectionChanged.CollectionChanged += ResourceDictionariesCollectionChanged; + } + } + + Reset(); + } + } + } + + /// + /// This method is raised when the ResourceDictionaries collection is changed. + /// + /// The source of the event. + /// Information about the event. + private void ResourceDictionariesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (!(e.Action == NotifyCollectionChangedAction.Add && (this.ResourceDictionaries.Count - e.NewItems.Count) == e.NewStartingIndex)) + { + Reset(); + } + } + + /// + /// The parent of the ResourceDictionaryDispenser. + /// + private IResourceDictionaryDispenser _parent; + + /// + /// Event that is invoked when the ResourceDictionaryDispenser's contents have changed. + /// + public event EventHandler ResourceDictionariesChanged; + + /// + /// Gets or sets the parent of the ResourceDictionaryDispenser. + /// + public IResourceDictionaryDispenser Parent + { + get + { + return _parent; + } + set + { + if (_parent != value) + { + if (null != _parent) + { + _parent.ResourceDictionariesChanged -= new EventHandler(ParentResourceDictionariesChanged); + } + _parent = value; + if (null != _parent) + { + _parent.ResourceDictionariesChanged += new EventHandler(ParentResourceDictionariesChanged); + } + OnParentChanged(); + } + } + } + + /// + /// Initializes a new instance of the ResourceDictionaryDispenser class. + /// + public ResourceDictionaryDispenser() + { + } + + /// + /// Resets the state of the ResourceDictionaryDispenser and its enumerators. + /// + private void Reset() + { + OnResetting(); + + // Invoke event + EventHandler handler = ResourceDictionariesChanged; + if (null != handler) + { + handler.Invoke(this, EventArgs.Empty); + } + } + + /// + /// Unregisters an enumerator so that it can be garbage collected. + /// + /// The enumerator. + internal void Unregister(ResourceDictionaryEnumerator enumerator) + { + _resourceDictionaryEnumerators.Remove(enumerator); + } + + /// + /// Returns a rotating enumerator of ResourceDictionary objects that coordinates + /// with the dispenser object to ensure that no two enumerators are on the same + /// item. If the dispenser is reset or its collection is changed then the + /// enumerators are also reset. + /// + /// A predicate that returns a value indicating + /// whether to return an item. + /// An enumerator of ResourceDictionaries. + [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Returning a usable enumerator instance.")] + public IEnumerator GetResourceDictionariesWhere(Func predicate) + { + ResourceDictionaryEnumerator enumerator = new ResourceDictionaryEnumerator(this, predicate); + + _ignoreResourceDictionaryDispensedByEnumerator = true; + try + { + foreach (ResourceDictionaryDispensedEventArgs args in _resourceDictionariesDispensed) + { + enumerator.ResourceDictionaryDispenserResourceDictionaryDispensed(this, args); + } + } + finally + { + _ignoreResourceDictionaryDispensedByEnumerator = false; + } + + _resourceDictionaryEnumerators.Add(enumerator); + return enumerator; + } + + /// + /// This method is raised when an enumerator dispenses a ResourceDictionary. + /// + /// The source of the event. + /// Information about the event. + internal void EnumeratorResourceDictionaryDispensed(object sender, ResourceDictionaryDispensedEventArgs e) + { + if (!_ignoreResourceDictionaryDispensedByEnumerator) + { + OnEnumeratorResourceDictionaryDispensed(this, e); + } + } + + /// + /// Raises the ParentChanged event. + /// + private void OnParentChanged() + { + foreach (ResourceDictionaryEnumerator enumerator in _resourceDictionaryEnumerators) + { + enumerator.ResourceDictionaryDispenserParentChanged(); + } + } + + /// + /// Raises the EnumeratorResourceDictionaryDispensed event. + /// + /// The source of the event. + /// Information about the event. + private void OnEnumeratorResourceDictionaryDispensed(object source, ResourceDictionaryDispensedEventArgs args) + { + // Remove this item from the list of dispensed styles. + _resourceDictionariesDispensed.Remove(args); + + // Add this item to the end of the list of dispensed styles. + _resourceDictionariesDispensed.AddLast(args); + + foreach (ResourceDictionaryEnumerator enumerator in _resourceDictionaryEnumerators) + { + enumerator.ResourceDictionaryDispenserResourceDictionaryDispensed(source, args); + } + } + + /// + /// This method raises the EnumeratorsResetting event. + /// + private void OnResetting() + { + _resourceDictionariesDispensed.Clear(); + + foreach (ResourceDictionaryEnumerator enumerator in _resourceDictionaryEnumerators) + { + enumerator.ResourceDictionaryDispenserResetting(); + } + } + + /// + /// Handles the Parent's ResourceDictionariesChanged event. + /// + /// Parent instance. + /// Event args. + private void ParentResourceDictionariesChanged(object sender, EventArgs e) + { + Reset(); + } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/ResourceDictionaryEnumerator.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/ResourceDictionaryEnumerator.cs new file mode 100644 index 00000000..f9a6a8ac --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/ResourceDictionaryEnumerator.cs @@ -0,0 +1,232 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// An enumerator that dispenses ResourceDictionaries sequentially by coordinating with + /// related enumerators. Enumerators are related through an association + /// with a parent ResourceDictionaryDispenser class. + /// + internal class ResourceDictionaryEnumerator : IEnumerator + { + /// + /// The index of current item in the ResourceDictionaryDispenser's list. + /// + private int? index; + + /// + /// Gets or sets the current ResourceDictionary. + /// + private ResourceDictionary CurrentResourceDictionary { get; set; } + + /// + /// The parent enumerator. + /// + private IEnumerator _parentEnumerator; + + /// + /// Gets the parent enumerator. + /// + private IEnumerator ParentEnumerator + { + get + { + if (_parentEnumerator == null && ResourceDictionaryDispenser.Parent != null) + { + _parentEnumerator = ResourceDictionaryDispenser.Parent.GetResourceDictionariesWhere(Predicate); + } + return _parentEnumerator; + } + } + + /// + /// Initializes a new instance of a ResourceDictionaryEnumerator. + /// + /// The dispenser that dispensed this + /// ResourceDictionaryEnumerator. + /// A predicate used to determine which + /// ResourceDictionaries to return. + public ResourceDictionaryEnumerator(ResourceDictionaryDispenser dispenser, Func predicate) + { + ResourceDictionaryDispenser = dispenser; + Predicate = predicate; + } + + /// + /// Called when the parent has changed. + /// + internal void ResourceDictionaryDispenserParentChanged() + { + _parentEnumerator = null; + } + + /// + /// Returns the index of the next suitable style in the list. + /// + /// The index at which to start looking. + /// The index of the next suitable ResourceDictionary. + private int? GetIndexOfNextSuitableResourceDictionary(int startIndex) + { + if (ResourceDictionaryDispenser.ResourceDictionaries == null || ResourceDictionaryDispenser.ResourceDictionaries.Count == 0) + { + return new int?(); + } + + if (startIndex >= ResourceDictionaryDispenser.ResourceDictionaries.Count) + { + startIndex = 0; + } + + int counter = startIndex; + do + { + if (Predicate(ResourceDictionaryDispenser.ResourceDictionaries[counter])) + { + return counter; + } + + counter = (counter + 1) % ResourceDictionaryDispenser.ResourceDictionaries.Count; + } + while (startIndex != counter); + + return new int?(); + } + + /// + /// Resets the dispenser. + /// + internal void ResourceDictionaryDispenserResetting() + { + if (!ShouldRetrieveFromParentEnumerator) + { + index = new int?(); + } + } + + /// + /// Gets or sets a predicate that returns a value indicating whether a + /// ResourceDictionary should be returned by this enumerator. + /// + /// A value indicating whether a ResourceDictionary can be returned by this + /// enumerator. + private Func Predicate { get; set; } + + /// + /// This method is invoked when one of the related enumerator's + /// dispenses. The enumerator checks to see if the item + /// dispensed would've been the next item it would have returned. If + /// so it updates it's index to the position after the previously + /// returned item. + /// + /// The ResourceDictionaryDispenser. + /// Information about the event. + internal void ResourceDictionaryDispenserResourceDictionaryDispensed(object sender, ResourceDictionaryDispensedEventArgs e) + { + if (!ShouldRetrieveFromParentEnumerator && Predicate(e.ResourceDictionary)) + { + int? nextStyleIndex = GetIndexOfNextSuitableResourceDictionary(index ?? 0); + if ((nextStyleIndex ?? -1) == e.Index) + { + index = (e.Index + 1) % ResourceDictionaryDispenser.ResourceDictionaries.Count; + } + } + } + + /// + /// Raises the EnumeratorResourceDictionaryDispensed. + /// + /// Information about the ResourceDictionary dispensed. + protected virtual void OnStyleDispensed(ResourceDictionaryDispensedEventArgs args) + { + ResourceDictionaryDispenser.EnumeratorResourceDictionaryDispensed(this, args); + } + + /// + /// Gets the dispenser that dispensed this enumerator. + /// + public ResourceDictionaryDispenser ResourceDictionaryDispenser { get; private set; } + + /// + /// Gets the current ResourceDictionary. + /// + public ResourceDictionary Current + { + get { return CurrentResourceDictionary; } + } + + /// + /// Gets the current ResourceDictionary. + /// + object System.Collections.IEnumerator.Current + { + get { return CurrentResourceDictionary; } + } + + /// + /// Moves to the next ResourceDictionary. + /// + /// A value indicating whether there are any more suitable + /// ResourceDictionary. + public bool MoveNext() + { + if (ShouldRetrieveFromParentEnumerator && ParentEnumerator != null) + { + bool isMore = ParentEnumerator.MoveNext(); + if (isMore) + { + this.CurrentResourceDictionary = ParentEnumerator.Current; + } + return isMore; + } + + index = GetIndexOfNextSuitableResourceDictionary(index ?? 0); + if (index == null) + { + CurrentResourceDictionary = null; + Dispose(); + return false; + } + + CurrentResourceDictionary = ResourceDictionaryDispenser.ResourceDictionaries[index.Value]; + OnStyleDispensed(new ResourceDictionaryDispensedEventArgs(index.Value, CurrentResourceDictionary)); + + return true; + } + + /// + /// Gets a value indicating whether a enumerator should return ResourceDictionaries + /// from its parent enumerator. + /// + private bool ShouldRetrieveFromParentEnumerator + { + get { return this.ResourceDictionaryDispenser.ResourceDictionaries == null; } + } + + /// + /// Resets the enumerator. + /// + public void Reset() + { + throw new NotSupportedException(Properties.Resources.ResourceDictionaryEnumerator_CantResetEnumeratorResetDispenserInstead); + } + + /// + /// Stops listening to the dispenser. + /// + public void Dispose() + { + if (_parentEnumerator != null) + { + _parentEnumerator.Dispose(); + } + + this.ResourceDictionaryDispenser.Unregister(this); + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/AreaSeries.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/AreaSeries.cs new file mode 100644 index 00000000..beb6035f --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/AreaSeries.cs @@ -0,0 +1,207 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Windows.Media; +using System.Windows.Shapes; + +#if !DEFINITION_SERIES_COMPATIBILITY_MODE + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Represents a control that contains a data series to be rendered in X/Y + /// line format. + /// + /// Preview + [StyleTypedProperty(Property = DataPointStyleName, StyleTargetType = typeof(AreaDataPoint))] + [StyleTypedProperty(Property = "LegendItemStyle", StyleTargetType = typeof(LegendItem))] + [StyleTypedProperty(Property = "PathStyle", StyleTargetType = typeof(Path))] + [TemplatePart(Name = DataPointSeries.PlotAreaName, Type = typeof(Canvas))] + [SuppressMessage("Microsoft.Maintainability", "CA1501:AvoidExcessiveInheritance", Justification = "Depth of hierarchy is necessary to avoid code duplication.")] + public partial class AreaSeries : LineAreaBaseSeries, IAnchoredToOrigin + { + #region public Geometry Geometry + /// + /// Gets the geometry property. + /// + public Geometry Geometry + { + get { return GetValue(GeometryProperty) as Geometry; } + private set { SetValue(GeometryProperty, value); } + } + + /// + /// Identifies the Geometry dependency property. + /// + public static readonly DependencyProperty GeometryProperty = + DependencyProperty.Register( + "Geometry", + typeof(Geometry), + typeof(AreaSeries), + null); + #endregion public Geometry Geometry + + #region public Style PathStyle + /// + /// Gets or sets the style of the Path object that follows the data + /// points. + /// + public Style PathStyle + { + get { return GetValue(PathStyleProperty) as Style; } + set { SetValue(PathStyleProperty, value); } + } + + /// + /// Identifies the PathStyle dependency property. + /// + public static readonly DependencyProperty PathStyleProperty = + DependencyProperty.Register( + "PathStyle", + typeof(Style), + typeof(AreaSeries), + null); + #endregion public Style PathStyle + +#if !SILVERLIGHT + /// + /// Initializes the static members of the AreaSeries class. + /// + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Dependency properties are initialized in-line.")] + static AreaSeries() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(AreaSeries), new FrameworkPropertyMetadata(typeof(AreaSeries))); + } + +#endif + /// + /// Initializes a new instance of the AreaSeries class. + /// + public AreaSeries() + { +#if SILVERLIGHT + this.DefaultStyleKey = typeof(AreaSeries); +#endif + } + + /// + /// Acquire a horizontal linear axis and a vertical linear axis. + /// + /// The first data point. + protected override void GetAxes(DataPoint firstDataPoint) + { + GetAxes( + firstDataPoint, + (axis) => axis.Orientation == AxisOrientation.X, + () => + { + IAxis axis = CreateRangeAxisFromData(firstDataPoint.IndependentValue); + if (axis == null) + { + axis = new CategoryAxis(); + } + axis.Orientation = AxisOrientation.X; + return axis; + }, + (axis) => + { + IRangeAxis rangeAxis = axis as IRangeAxis; + return rangeAxis != null && rangeAxis.Origin != null && axis.Orientation == AxisOrientation.Y; + }, + () => + { + DisplayAxis axis = (DisplayAxis)CreateRangeAxisFromData(firstDataPoint.DependentValue); + if (axis == null || (axis as IRangeAxis).Origin == null) + { + throw new InvalidOperationException(Properties.Resources.DataPointSeriesWithAxes_NoSuitableAxisAvailableForPlottingDependentValue); + } + axis.ShowGridLines = true; + axis.Orientation = AxisOrientation.Y; + return axis; + }); + } + + /// + /// Updates the Series shape object from a collection of Points. + /// + /// Collection of Points. + protected override void UpdateShapeFromPoints(IEnumerable points) + { + UnitValue originCoordinate = ActualDependentRangeAxis.GetPlotAreaCoordinate(ActualDependentRangeAxis.Origin); + UnitValue maximumCoordinate = ActualDependentRangeAxis.GetPlotAreaCoordinate(ActualDependentRangeAxis.Range.Maximum); + if (points.Any() && ValueHelper.CanGraph(originCoordinate.Value) && ValueHelper.CanGraph(maximumCoordinate.Value)) + { + double originY = Math.Floor(originCoordinate.Value); + PathFigure figure = new PathFigure(); + figure.IsClosed = true; + figure.IsFilled = true; + + double maximum = maximumCoordinate.Value; + Point startPoint; + IEnumerator pointEnumerator = points.GetEnumerator(); + pointEnumerator.MoveNext(); + startPoint = new Point(pointEnumerator.Current.X, maximum - originY); + figure.StartPoint = startPoint; + + Point lastPoint; + do + { + lastPoint = pointEnumerator.Current; + figure.Segments.Add(new LineSegment { Point = pointEnumerator.Current }); + } + while (pointEnumerator.MoveNext()); + figure.Segments.Add(new LineSegment { Point = new Point(lastPoint.X, maximum - originY) }); + + if (figure.Segments.Count > 1) + { + PathGeometry geometry = new PathGeometry(); + geometry.Figures.Add(figure); + Geometry = geometry; + return; + } + } + else + { + Geometry = null; + } + } + + /// + /// Remove value margins from the side of the data points to ensure + /// that area chart is flush against the edge of the chart. + /// + /// The value margin consumer. + /// A sequence of value margins. + protected override IEnumerable GetValueMargins(IValueMarginConsumer consumer) + { + if (consumer == ActualIndependentAxis) + { + return Enumerable.Empty(); + } + return base.GetValueMargins(consumer); + } + + /// + /// Gets the axis to which the series is anchored. + /// + IRangeAxis IAnchoredToOrigin.AnchoredAxis + { + get { return AnchoredAxis; } + } + + /// + /// Gets the axis to which the series is anchored. + /// + protected IRangeAxis AnchoredAxis + { + get { return ActualDependentRangeAxis; } + } + } +} + +#endif diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/BarSeries.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/BarSeries.cs new file mode 100644 index 00000000..71c31613 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/BarSeries.cs @@ -0,0 +1,136 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +#if !DEFINITION_SERIES_COMPATIBILITY_MODE + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Represents a control that contains a data series to be rendered in bar format. + /// + /// Preview + [SuppressMessage("Microsoft.Maintainability", "CA1501:AvoidExcessiveInheritance", Justification = "Depth of hierarchy is necessary to avoid code duplication.")] + [StyleTypedProperty(Property = DataPointStyleName, StyleTargetType = typeof(BarDataPoint))] + [StyleTypedProperty(Property = "LegendItemStyle", StyleTargetType = typeof(LegendItem))] + [TemplatePart(Name = DataPointSeries.PlotAreaName, Type = typeof(Canvas))] + public partial class BarSeries : ColumnBarBaseSeries + { + /// + /// Initializes a new instance of the BarSeries class. + /// + public BarSeries() + { + } + + /// + /// Acquire a horizontal category axis and a vertical linear axis. + /// + /// The first data point. + protected override void GetAxes(DataPoint firstDataPoint) + { + GetAxes( + firstDataPoint, + (axis) => axis.Orientation == AxisOrientation.Y, + () => new CategoryAxis { Orientation = AxisOrientation.Y }, + (axis) => + { + IRangeAxis rangeAxis = axis as IRangeAxis; + return rangeAxis != null && rangeAxis.Origin != null && axis.Orientation == AxisOrientation.X; + }, + () => + { + IRangeAxis rangeAxis = CreateRangeAxisFromData(firstDataPoint.DependentValue); + rangeAxis.Orientation = AxisOrientation.X; + if (rangeAxis == null || rangeAxis.Origin == null) + { + throw new InvalidOperationException(Properties.Resources.DataPointSeriesWithAxes_NoSuitableAxisAvailableForPlottingDependentValue); + } + DisplayAxis axis = rangeAxis as DisplayAxis; + if (axis != null) + { + axis.ShowGridLines = true; + } + return rangeAxis; + }); + } + + /// + /// Updates each point. + /// + /// The data point to update. + protected override void UpdateDataPoint(DataPoint dataPoint) + { + if (SeriesHost == null || PlotArea == null) + { + return; + } + + object category = dataPoint.ActualIndependentValue ?? (this.ActiveDataPoints.IndexOf(dataPoint) + 1); + Range coordinateRange = GetCategoryRange(category); + + if (!coordinateRange.HasData) + { + return; + } + else if (coordinateRange.Maximum.Unit != Unit.Pixels || coordinateRange.Minimum.Unit != Unit.Pixels) + { + throw new InvalidOperationException(Properties.Resources.DataPointSeriesWithAxes_ThisSeriesDoesNotSupportRadialAxes); + } + + double minimum = (double)coordinateRange.Minimum.Value; + double maximum = (double)coordinateRange.Maximum.Value; + + IEnumerable barSeries = SeriesHost.Series.OfType().Where(series => series.ActualIndependentAxis == ActualIndependentAxis); + int numberOfSeries = barSeries.Count(); + double coordinateRangeHeight = (maximum - minimum); + double segmentHeight = coordinateRangeHeight * 0.8; + double barHeight = segmentHeight / numberOfSeries; + int seriesIndex = barSeries.IndexOf(this); + + double dataPointX = ActualDependentRangeAxis.GetPlotAreaCoordinate(ValueHelper.ToDouble(dataPoint.ActualDependentValue)).Value; + double zeroPointX = ActualDependentRangeAxis.GetPlotAreaCoordinate(ActualDependentRangeAxis.Origin).Value; + + double offset = seriesIndex * Math.Round(barHeight) + coordinateRangeHeight * 0.1; + double dataPointY = minimum + offset; + + if (GetIsDataPointGrouped(category)) + { + // Multiple DataPoints share this category; offset and overlap them appropriately + IGrouping categoryGrouping = GetDataPointGroup(category); + int index = categoryGrouping.IndexOf(dataPoint); + dataPointY += (index * (barHeight * 0.2)) / (categoryGrouping.Count() - 1); + barHeight *= 0.8; + Canvas.SetZIndex(dataPoint, -index); + } + + if (ValueHelper.CanGraph(dataPointX) && ValueHelper.CanGraph(dataPointY) && ValueHelper.CanGraph(zeroPointX)) + { + dataPoint.Visibility = Visibility.Visible; + + double top = Math.Round(dataPointY); + double height = Math.Round(barHeight); + + double left = Math.Round(Math.Min(dataPointX, zeroPointX) - 0.5); + double right = Math.Round(Math.Max(dataPointX, zeroPointX) - 0.5); + double width = right - left + 1; + + Canvas.SetLeft(dataPoint, left); + Canvas.SetTop(dataPoint, top); + dataPoint.Width = width; + dataPoint.Height = height; + } + else + { + dataPoint.Visibility = Visibility.Collapsed; + } + } + } +} + +#endif diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/BubbleSeries.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/BubbleSeries.cs new file mode 100644 index 00000000..31679a1a --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/BubbleSeries.cs @@ -0,0 +1,415 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; +using System.Linq; +using System.Windows.Data; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Represents a control that contains a data series to be rendered in X/Y + /// line format. A third binding determines the size of the data point. + /// + /// Preview + [TemplatePart(Name = DataPointSeries.PlotAreaName, Type = typeof(Canvas))] + [StyleTypedProperty(Property = DataPointStyleName, StyleTargetType = typeof(BubbleDataPoint))] + [StyleTypedProperty(Property = "LegendItemStyle", StyleTargetType = typeof(LegendItem))] + public class BubbleSeries : DataPointSingleSeriesWithAxes + { + /// + /// The maximum bubble size as a ratio of the smallest dimension. + /// + private const double MaximumBubbleSizeAsRatioOfSmallestDimension = 0.25; + + /// + /// The binding used to identify the size value. + /// + private Binding _sizeValueBinding; + + /// + /// Gets or sets the Binding to use for identifying the size of the bubble. + /// + public Binding SizeValueBinding + { + get + { + return _sizeValueBinding; + } + set + { + if (_sizeValueBinding != value) + { + _sizeValueBinding = value; + Refresh(); + } + } + } + + /// + /// Gets or sets the Binding Path to use for identifying the size of the bubble. + /// + public string SizeValuePath + { + get + { + return (null != SizeValueBinding) ? SizeValueBinding.Path.Path : null; + } + set + { + if (null == value) + { + SizeValueBinding = null; + } + else + { + SizeValueBinding = new Binding(value); + } + } + } + + /// + /// Stores the range of ActualSize values for the BubbleDataPoints. + /// + private Range _rangeOfActualSizeValues = new Range(); + + /// + /// Initializes a new instance of the bubble series. + /// + public BubbleSeries() + { + } + + /// + /// Creates a new instance of bubble data point. + /// + /// A new instance of bubble data point. + protected override DataPoint CreateDataPoint() + { + return new BubbleDataPoint(); + } + + /// + /// Returns the custom ResourceDictionary to use for necessary resources. + /// + /// + /// ResourceDictionary to use for necessary resources. + /// + protected override IEnumerator GetResourceDictionaryEnumeratorFromHost() + { + return GetResourceDictionaryWithTargetType(SeriesHost, typeof(BubbleDataPoint), true); + } + + /// + /// Acquire a horizontal linear axis and a vertical linear axis. + /// + /// The first data point. + protected override void GetAxes(DataPoint firstDataPoint) + { + GetAxes( + firstDataPoint, + (axis) => axis.Orientation == AxisOrientation.X, + () => + { + IAxis axis = CreateRangeAxisFromData(firstDataPoint.IndependentValue); + if (axis == null) + { + axis = new CategoryAxis(); + } + axis.Orientation = AxisOrientation.X; + return axis; + }, + (axis) => axis.Orientation == AxisOrientation.Y && axis is IRangeAxis, + () => + { + DisplayAxis axis = (DisplayAxis)CreateRangeAxisFromData(firstDataPoint.DependentValue); + if (axis == null) + { + throw new InvalidOperationException(Properties.Resources.DataPointSeriesWithAxes_NoSuitableAxisAvailableForPlottingDependentValue); + } + axis.ShowGridLines = true; + axis.Orientation = AxisOrientation.Y; + return axis; + }); + } + + /// + /// Prepares a bubble data point by binding the size value binding to + /// the size property. + /// + /// The data point to prepare. + /// The data context of the data point. + /// + protected override void PrepareDataPoint(DataPoint dataPoint, object dataContext) + { + base.PrepareDataPoint(dataPoint, dataContext); + + BubbleDataPoint bubbleDataPoint = (BubbleDataPoint)dataPoint; + bubbleDataPoint.SetBinding(BubbleDataPoint.SizeProperty, SizeValueBinding ?? DependentValueBinding ?? IndependentValueBinding); + } + + /// + /// Attaches size change and actual size change event handlers to the + /// data point. + /// + /// The data point. + protected override void AttachEventHandlersToDataPoint(DataPoint dataPoint) + { + BubbleDataPoint bubbleDataPoint = (BubbleDataPoint)dataPoint; + bubbleDataPoint.SizePropertyChanged += BubbleDataPointSizePropertyChanged; + bubbleDataPoint.ActualSizePropertyChanged += BubbleDataPointActualSizePropertyChanged; + base.AttachEventHandlersToDataPoint(dataPoint); + } + + /// + /// Detaches size change and actual size change event handlers from the + /// data point. + /// + /// The data point. + protected override void DetachEventHandlersFromDataPoint(DataPoint dataPoint) + { + BubbleDataPoint bubbleDataPoint = (BubbleDataPoint)dataPoint; + bubbleDataPoint.SizePropertyChanged -= BubbleDataPointSizePropertyChanged; + bubbleDataPoint.ActualSizePropertyChanged -= BubbleDataPointActualSizePropertyChanged; + base.DetachEventHandlersFromDataPoint(dataPoint); + } + + /// + /// Updates all data points when the actual size property of a data + /// point changes. + /// + /// The source of the event. + /// Information about the event. + private void BubbleDataPointActualSizePropertyChanged(object sender, RoutedPropertyChangedEventArgs e) + { + Range newRangeOfActualSizeValues = ActiveDataPoints.OfType().Select(d => Math.Abs(d.ActualSize)).GetRange(); + if (newRangeOfActualSizeValues == _rangeOfActualSizeValues) + { + // No range change - only need to update the current point + UpdateDataPoint((BubbleDataPoint)sender); + } + else + { + // Range has changed - need to update all points + UpdateDataPoints(ActiveDataPoints); + } + } + + /// + /// Animates the value of the ActualSize property to the size property + /// when it changes. + /// + /// The source of the event. + /// Information about the event. + private void BubbleDataPointSizePropertyChanged(object sender, RoutedPropertyChangedEventArgs e) + { + BubbleDataPoint dataPoint = (BubbleDataPoint)sender; + + DependencyPropertyAnimationHelper.BeginAnimation( + dataPoint, + BubbleDataPoint.ActualSizeProperty, + "ActualSize", + e.NewValue, + TransitionDuration, + this.TransitionEasingFunction); + } + + /// + /// Calculates the range of ActualSize values of all active BubbleDataPoints. + /// + protected override void OnBeforeUpdateDataPoints() + { + _rangeOfActualSizeValues = ActiveDataPoints.OfType().Select(d => Math.Abs(d.ActualSize)).GetRange(); + } + + /// + /// Ensure that if any data points are updated, all data points are + /// updated. + /// + /// The data points to update. + protected override void UpdateDataPoints(IEnumerable dataPoints) + { + base.UpdateDataPoints(ActiveDataPoints); + } + + /// + /// Updates the data point's visual representation. + /// + /// The data point. + protected override void UpdateDataPoint(DataPoint dataPoint) + { + double maximumDiameter = Math.Min(PlotAreaSize.Width, PlotAreaSize.Height) * MaximumBubbleSizeAsRatioOfSmallestDimension; + + BubbleDataPoint bubbleDataPoint = (BubbleDataPoint)dataPoint; + + double ratioOfLargestBubble = + (_rangeOfActualSizeValues.HasData && _rangeOfActualSizeValues.Maximum != 0.0 && bubbleDataPoint.ActualSize >= 0.0) ? Math.Abs(bubbleDataPoint.ActualSize) / _rangeOfActualSizeValues.Maximum : 0.0; + + bubbleDataPoint.Width = ratioOfLargestBubble * maximumDiameter; + bubbleDataPoint.Height = ratioOfLargestBubble * maximumDiameter; + + double left = + (ActualIndependentAxis.GetPlotAreaCoordinate(bubbleDataPoint.ActualIndependentValue)).Value + - (bubbleDataPoint.Width / 2.0); + + double top = + (PlotAreaSize.Height + - (bubbleDataPoint.Height / 2.0)) + - ActualDependentRangeAxis.GetPlotAreaCoordinate(bubbleDataPoint.ActualDependentValue).Value; + + if (ValueHelper.CanGraph(left) && ValueHelper.CanGraph(top)) + { + dataPoint.Visibility = Visibility.Visible; + + Canvas.SetLeft(bubbleDataPoint, left); + Canvas.SetTop(bubbleDataPoint, top); + } + else + { + dataPoint.Visibility = Visibility.Collapsed; + } + } + + /// + /// Updates the value margins after all data points are updated. + /// + protected override void OnAfterUpdateDataPoints() + { + IValueMarginProvider provider = this as IValueMarginProvider; + { + IValueMarginConsumer consumer = ActualDependentRangeAxis as IValueMarginConsumer; + if (consumer != null) + { + consumer.ValueMarginsChanged(provider, GetValueMargins(consumer)); + } + } + { + IValueMarginConsumer consumer = ActualIndependentAxis as IValueMarginConsumer; + if (consumer != null) + { + consumer.ValueMarginsChanged(provider, GetValueMargins(consumer)); + } + } + base.OnAfterUpdateDataPoints(); + } + + /// + /// Gets the dependent axis as a range axis. + /// + public IRangeAxis ActualDependentRangeAxis { get { return this.InternalActualDependentAxis as IRangeAxis; } } + + #region public IRangeAxis DependentRangeAxis + /// + /// Gets or sets the dependent range axis. + /// + public IRangeAxis DependentRangeAxis + { + get { return GetValue(DependentRangeAxisProperty) as IRangeAxis; } + set { SetValue(DependentRangeAxisProperty, value); } + } + + /// + /// Identifies the DependentRangeAxis dependency property. + /// + public static readonly DependencyProperty DependentRangeAxisProperty = + DependencyProperty.Register( + "DependentRangeAxis", + typeof(IRangeAxis), + typeof(BubbleSeries), + new PropertyMetadata(null, OnDependentRangeAxisPropertyChanged)); + + /// + /// DependentRangeAxisProperty property changed handler. + /// + /// BubbleSeries that changed its DependentRangeAxis. + /// Event arguments. + private static void OnDependentRangeAxisPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + BubbleSeries source = (BubbleSeries)d; + IRangeAxis newValue = (IRangeAxis)e.NewValue; + source.OnDependentRangeAxisPropertyChanged(newValue); + } + + /// + /// DependentRangeAxisProperty property changed handler. + /// + /// New value. + private void OnDependentRangeAxisPropertyChanged(IRangeAxis newValue) + { + this.InternalDependentAxis = (IAxis)newValue; + } + #endregion public IRangeAxis DependentRangeAxis + + /// + /// Gets the independent axis as a range axis. + /// + public IAxis ActualIndependentAxis { get { return this.InternalActualIndependentAxis as IAxis; } } + + #region public IAxis IndependentAxis + /// + /// Gets or sets independent range axis. + /// + public IAxis IndependentAxis + { + get { return GetValue(IndependentAxisProperty) as IAxis; } + set { SetValue(IndependentAxisProperty, value); } + } + + /// + /// Identifies the IndependentAxis dependency property. + /// + public static readonly DependencyProperty IndependentAxisProperty = + DependencyProperty.Register( + "IndependentAxis", + typeof(IAxis), + typeof(BubbleSeries), + new PropertyMetadata(null, OnIndependentAxisPropertyChanged)); + + /// + /// IndependentAxisProperty property changed handler. + /// + /// BubbleSeries that changed its IndependentAxis. + /// Event arguments. + private static void OnIndependentAxisPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + BubbleSeries source = (BubbleSeries)d; + IAxis newValue = (IAxis)e.NewValue; + source.OnIndependentAxisPropertyChanged(newValue); + } + + /// + /// IndependentAxisProperty property changed handler. + /// + /// New value. + private void OnIndependentAxisPropertyChanged(IAxis newValue) + { + this.InternalIndependentAxis = (IAxis)newValue; + } + #endregion public IAxis IndependentAxis + + /// + /// The margins required for each value. + /// + /// The consumer to return the value margins for. + /// A sequence of margins for each value. + protected override IEnumerable GetValueMargins(IValueMarginConsumer consumer) + { + IAxis axis = consumer as IAxis; + if (axis != null) + { + return ActiveDataPoints.Select(dataPoint => + { + double margin = dataPoint.GetMargin(axis); + return new ValueMargin( + GetActualDataPointAxisValue(dataPoint, axis), + margin, + margin); + }); + } + + return Enumerable.Empty(); + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/ColumnBarBaseSeries.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/ColumnBarBaseSeries.cs new file mode 100644 index 00000000..11b9afad --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/ColumnBarBaseSeries.cs @@ -0,0 +1,358 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// This series serves as the base class for the column and bar series. + /// + /// The type of the data point. + public abstract class ColumnBarBaseSeries : DataPointSingleSeriesWithAxes, IAnchoredToOrigin + where T : DataPoint, new() + { + #region public IRangeAxis DependentRangeAxis + /// + /// Gets or sets the dependent range axis. + /// + public IRangeAxis DependentRangeAxis + { + get { return GetValue(DependentRangeAxisProperty) as IRangeAxis; } + set { SetValue(DependentRangeAxisProperty, value); } + } + + /// + /// Identifies the DependentRangeAxis dependency property. + /// + [SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", Justification = "This member is necessary because child classes need to share this dependency property.")] + public static readonly DependencyProperty DependentRangeAxisProperty = + DependencyProperty.Register( + "DependentRangeAxis", + typeof(IRangeAxis), + typeof(ColumnBarBaseSeries), + new PropertyMetadata(null, OnDependentRangeAxisPropertyChanged)); + + /// + /// DependentRangeAxisProperty property changed handler. + /// + /// ColumnBarBaseSeries that changed its DependentRangeAxis. + /// Event arguments. + private static void OnDependentRangeAxisPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ColumnBarBaseSeries source = (ColumnBarBaseSeries)d; + IRangeAxis newValue = (IRangeAxis)e.NewValue; + source.OnDependentRangeAxisPropertyChanged(newValue); + } + + /// + /// DependentRangeAxisProperty property changed handler. + /// + /// New value. + private void OnDependentRangeAxisPropertyChanged(IRangeAxis newValue) + { + InternalDependentAxis = (IAxis)newValue; + } + #endregion public IRangeAxis DependentRangeAxis + + #region public IAxis IndependentAxis + /// + /// Gets or sets the independent category axis. + /// + public IAxis IndependentAxis + { + get { return GetValue(IndependentAxisProperty) as IAxis; } + set { SetValue(IndependentAxisProperty, value); } + } + + /// + /// Identifies the IndependentAxis dependency property. + /// + [SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", Justification = "This member is necessary because child classes need to share this dependency property.")] + public static readonly DependencyProperty IndependentAxisProperty = + DependencyProperty.Register( + "IndependentAxis", + typeof(IAxis), + typeof(ColumnBarBaseSeries), + new PropertyMetadata(null, OnIndependentAxisPropertyChanged)); + + /// + /// IndependentAxisProperty property changed handler. + /// + /// ColumnBarBaseSeries that changed its IndependentAxis. + /// Event arguments. + private static void OnIndependentAxisPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ColumnBarBaseSeries source = (ColumnBarBaseSeries)d; + IAxis newValue = (IAxis)e.NewValue; + source.OnIndependentAxisPropertyChanged(newValue); + } + + /// + /// IndependentAxisProperty property changed handler. + /// + /// New value. + private void OnIndependentAxisPropertyChanged(IAxis newValue) + { + InternalIndependentAxis = (IAxis)newValue; + } + #endregion public IAxis IndependentAxis + + /// + /// Keeps a list of DataPoints that share the same category. + /// + private IDictionary> _categoriesWithMultipleDataPoints; + + /// + /// Returns the group of data points in a given category. + /// + /// The category for which to return the data + /// point group. + /// The group of data points in a given category. + protected IGrouping GetDataPointGroup(object category) + { + return _categoriesWithMultipleDataPoints[category]; + } + + /// + /// Returns a value indicating whether a data point corresponding to + /// a category is grouped. + /// + /// The category. + /// A value indicating whether a data point corresponding to + /// a category is grouped. + protected bool GetIsDataPointGrouped(object category) + { + return _categoriesWithMultipleDataPoints.ContainsKey(category); + } + + /// + /// The length of each data point. + /// + private double? _dataPointlength; + + /// + /// Gets the dependent axis as a range axis. + /// + public IRangeAxis ActualDependentRangeAxis { get { return this.InternalActualDependentAxis as IRangeAxis; } } + + /// + /// Gets the independent axis as a category axis. + /// + public IAxis ActualIndependentAxis { get { return this.InternalActualIndependentAxis; } } + + /// + /// Initializes a new instance of the ColumnBarBaseSeries class. + /// + protected ColumnBarBaseSeries() + { + } + + /// + /// Method run before DataPoints are updated. + /// + protected override void OnBeforeUpdateDataPoints() + { + base.OnBeforeUpdateDataPoints(); + + CalculateDataPointLength(); + + // Update the list of DataPoints with the same category + _categoriesWithMultipleDataPoints = ActiveDataPoints + .Where(point => null != point.IndependentValue) + .OrderBy(point => point.DependentValue) + .GroupBy(point => point.IndependentValue) + .Where(grouping => 1 < grouping.Count()) + .ToDictionary(grouping => grouping.Key); + } + + /// + /// Returns the custom ResourceDictionary to use for necessary resources. + /// + /// + /// ResourceDictionary to use for necessary resources. + /// + protected override IEnumerator GetResourceDictionaryEnumeratorFromHost() + { + return GetResourceDictionaryWithTargetType(SeriesHost, typeof(T), true); + } + + /// + /// Updates a data point when its actual dependent value has changed. + /// + /// The data point. + /// The old value. + /// The new value. + protected override void OnDataPointActualDependentValueChanged(DataPoint dataPoint, IComparable oldValue, IComparable newValue) + { + UpdateDataPoint(dataPoint); + base.OnDataPointActualDependentValueChanged(dataPoint, oldValue, newValue); + } + + /// + /// Redraws other column series to assure they allocate the right amount + /// of space for their columns. + /// + /// The series host to update. + protected void RedrawOtherSeries(ISeriesHost seriesHost) + { + Type thisType = typeof(ColumnBarBaseSeries); + + // redraw all other column series to ensure they make space for new one + foreach (ColumnBarBaseSeries series in seriesHost.Series.Where(series => thisType.IsAssignableFrom(series.GetType())).OfType>().Where(series => series != this)) + { + series.UpdateDataPoints(series.ActiveDataPoints); + } + } + + /// + /// Called after data points have been loaded from the items source. + /// + /// New active data points. + /// Old inactive data points. + protected override void OnDataPointsChanged(IList newDataPoints, IList oldDataPoints) + { + base.OnDataPointsChanged(newDataPoints, oldDataPoints); + + CalculateDataPointLength(); + + if (this.SeriesHost != null) + { + RedrawOtherSeries(this.SeriesHost); + } + } + + /// + /// Redraw other column series when removed from a series host. + /// + /// The old value of the series host property. + /// The new value of the series host property. + protected override void OnSeriesHostPropertyChanged(ISeriesHost oldValue, ISeriesHost newValue) + { + base.OnSeriesHostPropertyChanged(oldValue, newValue); + + // If being removed from series host, redraw all column series. + if (newValue == null || oldValue != null) + { + RedrawOtherSeries(oldValue); + } + } + + /// + /// Creates the bar data point. + /// + /// A bar data point. + protected override DataPoint CreateDataPoint() + { + return new T(); + } + + /// + /// Calculates the length of the data points. + /// + protected void CalculateDataPointLength() + { + if (!(ActualIndependentAxis is ICategoryAxis)) + { + IEnumerable values = + ActiveDataPoints + .Select(dataPoint => ActualIndependentAxis.GetPlotAreaCoordinate(dataPoint.ActualIndependentValue)) + .Where(value => ValueHelper.CanGraph(value.Value)) + .OrderBy(value => value.Value) + .ToList(); + + _dataPointlength = + EnumerableFunctions.Zip( + values, + values.Skip(1), + (left, right) => new Range(left.Value, right.Value)) + .Select(range => range.Maximum - range.Minimum) + .MinOrNullable(); + } + } + + /// + /// Returns the value margins for a given axis. + /// + /// The axis to retrieve the value margins for. + /// + /// A sequence of value margins. + protected override IEnumerable GetValueMargins(IValueMarginConsumer consumer) + { + double dependentValueMargin = this.ActualHeight / 10; + IAxis axis = consumer as IAxis; + if (axis != null && ActiveDataPoints.Any()) + { + Func selector = null; + if (axis == InternalActualIndependentAxis) + { + selector = (dataPoint) => (IComparable)dataPoint.ActualIndependentValue; + + DataPoint minimumPoint = ActiveDataPoints.MinOrNull(selector); + DataPoint maximumPoint = ActiveDataPoints.MaxOrNull(selector); + + double minimumMargin = minimumPoint.GetMargin(axis); + yield return new ValueMargin(selector(minimumPoint), minimumMargin, minimumMargin); + + double maximumMargin = maximumPoint.GetMargin(axis); + yield return new ValueMargin(selector(maximumPoint), maximumMargin, maximumMargin); + } + else if (axis == InternalActualDependentAxis) + { + selector = (dataPoint) => (IComparable)dataPoint.ActualDependentValue; + + DataPoint minimumPoint = ActiveDataPoints.MinOrNull(selector); + DataPoint maximumPoint = ActiveDataPoints.MaxOrNull(selector); + + yield return new ValueMargin(selector(minimumPoint), dependentValueMargin, dependentValueMargin); + yield return new ValueMargin(selector(maximumPoint), dependentValueMargin, dependentValueMargin); + } + } + else + { + yield break; + } + } + + /// + /// Gets a range in which to render a data point. + /// + /// The category to retrieve the range for. + /// + /// The range in which to render a data point. + protected Range GetCategoryRange(object category) + { + ICategoryAxis categoryAxis = ActualIndependentAxis as CategoryAxis; + if (categoryAxis != null) + { + return categoryAxis.GetPlotAreaCoordinateRange(category); + } + else + { + UnitValue unitValue = ActualIndependentAxis.GetPlotAreaCoordinate(category); + if (ValueHelper.CanGraph(unitValue.Value) && _dataPointlength.HasValue) + { + double halfLength = _dataPointlength.Value / 2.0; + + return new Range( + new UnitValue(unitValue.Value - halfLength, unitValue.Unit), + new UnitValue(unitValue.Value + halfLength, unitValue.Unit)); + } + + return new Range(); + } + } + + /// + /// Gets the axis to which the data is anchored. + /// + IRangeAxis IAnchoredToOrigin.AnchoredAxis + { + get { return this.ActualDependentRangeAxis; } + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/ColumnSeries.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/ColumnSeries.cs new file mode 100644 index 00000000..aabf0499 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/ColumnSeries.cs @@ -0,0 +1,137 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +#if !DEFINITION_SERIES_COMPATIBILITY_MODE + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Represents a control that contains a data series to be rendered in column format. + /// + /// Preview + [SuppressMessage("Microsoft.Maintainability", "CA1501:AvoidExcessiveInheritance", Justification = "Depth of hierarchy is necessary to avoid code duplication.")] + [StyleTypedProperty(Property = DataPointStyleName, StyleTargetType = typeof(ColumnDataPoint))] + [StyleTypedProperty(Property = "LegendItemStyle", StyleTargetType = typeof(LegendItem))] + [TemplatePart(Name = DataPointSeries.PlotAreaName, Type = typeof(Canvas))] + public partial class ColumnSeries : ColumnBarBaseSeries + { + /// + /// Initializes a new instance of the ColumnSeries class. + /// + public ColumnSeries() + { + } + + /// + /// Acquire a horizontal category axis and a vertical linear axis. + /// + /// The first data point. + protected override void GetAxes(DataPoint firstDataPoint) + { + GetAxes( + firstDataPoint, + (axis) => axis.Orientation == AxisOrientation.X, + () => new CategoryAxis { Orientation = AxisOrientation.X }, + (axis) => + { + IRangeAxis rangeAxis = axis as IRangeAxis; + return rangeAxis != null && rangeAxis.Origin != null && axis.Orientation == AxisOrientation.Y; + }, + () => + { + IRangeAxis rangeAxis = CreateRangeAxisFromData(firstDataPoint.DependentValue); + rangeAxis.Orientation = AxisOrientation.Y; + if (rangeAxis == null || rangeAxis.Origin == null) + { + throw new InvalidOperationException(Properties.Resources.DataPointSeriesWithAxes_NoSuitableAxisAvailableForPlottingDependentValue); + } + DisplayAxis axis = rangeAxis as DisplayAxis; + if (axis != null) + { + axis.ShowGridLines = true; + } + return rangeAxis; + }); + } + + /// + /// Updates each point. + /// + /// The data point to update. + protected override void UpdateDataPoint(DataPoint dataPoint) + { + if (SeriesHost == null || PlotArea == null) + { + return; + } + + object category = dataPoint.ActualIndependentValue ?? (this.ActiveDataPoints.IndexOf(dataPoint) + 1); + Range coordinateRange = GetCategoryRange(category); + + if (!coordinateRange.HasData) + { + return; + } + else if (coordinateRange.Maximum.Unit != Unit.Pixels || coordinateRange.Minimum.Unit != Unit.Pixels) + { + throw new InvalidOperationException(Properties.Resources.DataPointSeriesWithAxes_ThisSeriesDoesNotSupportRadialAxes); + } + + double minimum = (double)coordinateRange.Minimum.Value; + double maximum = (double)coordinateRange.Maximum.Value; + + double plotAreaHeight = ActualDependentRangeAxis.GetPlotAreaCoordinate(ActualDependentRangeAxis.Range.Maximum).Value; + IEnumerable columnSeries = SeriesHost.Series.OfType().Where(series => series.ActualIndependentAxis == ActualIndependentAxis); + int numberOfSeries = columnSeries.Count(); + double coordinateRangeWidth = (maximum - minimum); + double segmentWidth = coordinateRangeWidth * 0.8; + double columnWidth = segmentWidth / numberOfSeries; + int seriesIndex = columnSeries.IndexOf(this); + + double dataPointY = ActualDependentRangeAxis.GetPlotAreaCoordinate(ValueHelper.ToDouble(dataPoint.ActualDependentValue)).Value; + double zeroPointY = ActualDependentRangeAxis.GetPlotAreaCoordinate(ActualDependentRangeAxis.Origin).Value; + + double offset = seriesIndex * Math.Round(columnWidth) + coordinateRangeWidth * 0.1; + double dataPointX = minimum + offset; + + if (GetIsDataPointGrouped(category)) + { + // Multiple DataPoints share this category; offset and overlap them appropriately + IGrouping categoryGrouping = GetDataPointGroup(category); + int index = categoryGrouping.IndexOf(dataPoint); + dataPointX += (index * (columnWidth * 0.2)) / (categoryGrouping.Count() - 1); + columnWidth *= 0.8; + Canvas.SetZIndex(dataPoint, -index); + } + + if (ValueHelper.CanGraph(dataPointY) && ValueHelper.CanGraph(dataPointX) && ValueHelper.CanGraph(zeroPointY)) + { + dataPoint.Visibility = Visibility.Visible; + + double left = Math.Round(dataPointX); + double width = Math.Round(columnWidth); + + double top = Math.Round(plotAreaHeight - Math.Max(dataPointY, zeroPointY) + 0.5); + double bottom = Math.Round(plotAreaHeight - Math.Min(dataPointY, zeroPointY) + 0.5); + double height = bottom - top + 1; + + Canvas.SetLeft(dataPoint, left); + Canvas.SetTop(dataPoint, top); + dataPoint.Width = width; + dataPoint.Height = height; + } + else + { + dataPoint.Visibility = Visibility.Collapsed; + } + } + } +} + +#endif diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Compatible/AreaSeries.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Compatible/AreaSeries.cs new file mode 100644 index 00000000..ee653fa9 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Compatible/AreaSeries.cs @@ -0,0 +1,265 @@ +// (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.Linq; +using System.Windows.Data; +using System.Windows.Media.Animation; +using System.Windows.Shapes; + +#if DEFINITION_SERIES_COMPATIBILITY_MODE +namespace System.Windows.Controls.DataVisualization.Charting +#else +namespace System.Windows.Controls.DataVisualization.Charting.Compatible +#endif +{ + /// + /// Control that displays values as an area chart visualization. + /// + /// + /// Based on the DefinitionSeries hierarchy. + /// + /// Preview + [StyleTypedProperty(Property = DataPointStyleName, StyleTargetType = typeof(AreaDataPoint))] + [StyleTypedProperty(Property = LegendItemStyleName, StyleTargetType = typeof(LegendItem))] + [StyleTypedProperty(Property = PathStyleName, StyleTargetType = typeof(Path))] + public class AreaSeries : StackedAreaSeries + { + /// + /// Name of the DataPointStyle property. + /// + private const string DataPointStyleName = "DataPointStyle"; + + /// + /// Name of the LegendItemStyle property. + /// + private const string LegendItemStyleName = "LegendItemStyle"; + + /// + /// Name of the PathStyle property. + /// + private const string PathStyleName = "PathStyle"; + + /// + /// Field storing the single SeriesDefinition used by the series. + /// + private SeriesDefinition _definition; + + /// + /// Initializes a new instance of the AreaSeries class. + /// + public AreaSeries() + { + SetBinding(DefinitionSeries.DependentAxisProperty, new Binding("DependentRangeAxis") { Source = this }); + SetBinding(DefinitionSeries.SelectionModeProperty, new Binding("IsSelectionEnabled") { Source = this, Converter = new System.Windows.Controls.DataVisualization.Charting.Compatible.SelectionEnabledToSelectionModeConverter() }); + _definition = new SeriesDefinition(); + _definition.SetBinding(SeriesDefinition.ItemsSourceProperty, new Binding("ItemsSource") { Source = this }); + _definition.SetBinding(SeriesDefinition.TitleProperty, new Binding("Title") { Source = this }); + _definition.SetBinding(SeriesDefinition.DataPointStyleProperty, new Binding(DataPointStyleName) { Source = this }); + _definition.SetBinding(SeriesDefinition.LegendItemStyleProperty, new Binding(LegendItemStyleName) { Source = this }); + _definition.SetBinding(SeriesDefinition.DataShapeStyleProperty, new Binding(PathStyleName) { Source = this }); + _definition.SetBinding(SeriesDefinition.TransitionDurationProperty, new Binding("TransitionDuration") { Source = this }); +#if !NO_EASING_FUNCTIONS + _definition.SetBinding(SeriesDefinition.TransitionEasingFunctionProperty, new Binding("TransitionEasingFunction") { Source = this }); +#endif + // For compatibility + DependentValueBinding = new Binding(); + IndependentValueBinding = new Binding(); + SeriesDefinitions.Add(_definition); + } + + /// + /// Gets a sequence of IndependentValueGroups. + /// + protected override IEnumerable IndependentValueGroups + { + get + { + // Base implementation groups by independent value; when plotting a single series in isolation, that's not desirable + return DataItems + .Select(di => new IndependentValueGroup(di.ActualIndependentValue, new DataItem[] { di })); + } + } + + /// + /// Gets or sets a sequence that provides the content 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(AreaSeries), null); + + /// + /// Gets or sets the Binding that identifies the dependent values of the series. + /// + public Binding DependentValueBinding + { + get { return _definition.DependentValueBinding; } + set { _definition.DependentValueBinding = value; } + } + + /// + /// Gets or sets the Binding path that identifies the dependent values of the series. + /// + public string DependentValuePath + { + get { return _definition.DependentValuePath; } + set { _definition.DependentValuePath = value; } + } + + /// + /// Gets or sets the Binding that identifies the independent values of the series. + /// + public Binding IndependentValueBinding + { + get { return _definition.IndependentValueBinding; } + set { _definition.IndependentValueBinding = value; } + } + + /// + /// Gets or sets the Binding path that identifies the independent values of the series. + /// + public string IndependentValuePath + { + get { return _definition.IndependentValuePath; } + set { _definition.IndependentValuePath = value; } + } + + /// + /// Gets or sets the IRangeAxis to use as the dependent axis of the series. + /// + public IRangeAxis DependentRangeAxis + { + get { return (IRangeAxis)GetValue(DependentRangeAxisProperty); } + set { SetValue(DependentRangeAxisProperty, value); } + } + + /// + /// Identifies the DependentRangeAxis dependency property. + /// + public static readonly DependencyProperty DependentRangeAxisProperty = + DependencyProperty.Register("DependentRangeAxis", typeof(IRangeAxis), typeof(AreaSeries), null); + + /// + /// Gets or sets the title of the series. + /// + public object Title + { + get { return (object)GetValue(TitleProperty); } + set { SetValue(TitleProperty, value); } + } + + /// + /// Identifies the Title dependency property. + /// + public static readonly DependencyProperty TitleProperty = + DependencyProperty.Register("Title", typeof(object), typeof(AreaSeries), null); + + /// + /// Gets or sets the Style to use for the DataPoints of the series. + /// + public Style DataPointStyle + { + get { return (Style)GetValue(DataPointStyleProperty); } + set { SetValue(DataPointStyleProperty, value); } + } + + /// + /// Identifies the DataPointStyle dependency property. + /// + public static readonly DependencyProperty DataPointStyleProperty = + DependencyProperty.Register(DataPointStyleName, typeof(Style), typeof(AreaSeries), null); + + /// + /// Gets or sets the Style to use for the LegendItem of the series. + /// + public Style LegendItemStyle + { + get { return (Style)GetValue(LegendItemStyleProperty); } + set { SetValue(LegendItemStyleProperty, value); } + } + + /// + /// Identifies the LegendItemStyle dependency property. + /// + public static readonly DependencyProperty LegendItemStyleProperty = + DependencyProperty.Register(LegendItemStyleName, typeof(Style), typeof(AreaSeries), null); + + /// + /// Gets or sets a value indicating whether selection is enabled. + /// + 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(AreaSeries), null); + + /// + /// Gets or sets the Style to use for the Path of the series. + /// + public Style PathStyle + { + get { return (Style)GetValue(PathStyleProperty); } + set { SetValue(PathStyleProperty, value); } + } + + /// + /// Identifies the PathStyle dependency property. + /// + public static readonly DependencyProperty PathStyleProperty = + DependencyProperty.Register(PathStyleName, typeof(Style), typeof(AreaSeries), null); + + /// + /// Gets or sets the TimeSpan to use for the duration of data transitions. + /// + 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(AreaSeries), new PropertyMetadata(TimeSpan.FromSeconds(0.5))); + +#if !NO_EASING_FUNCTIONS + /// + /// Gets or sets the IEasingFunction to use for data transitions. + /// + public IEasingFunction TransitionEasingFunction + { + get { return (IEasingFunction)GetValue(TransitionEasingFunctionProperty); } + set { SetValue(TransitionEasingFunctionProperty, value); } + } + + /// + /// Identifies the TransitionEasingFunction dependency property. + /// + public static readonly DependencyProperty TransitionEasingFunctionProperty = + DependencyProperty.Register("TransitionEasingFunction", typeof(IEasingFunction), typeof(AreaSeries), new PropertyMetadata(new QuadraticEase { EasingMode = EasingMode.EaseInOut })); +#else + /// + /// Gets or sets a placeholder for the TransitionEasingFunction dependency property. + /// + internal IEasingFunction TransitionEasingFunction { get; set; } +#endif + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Compatible/BarSeries.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Compatible/BarSeries.cs new file mode 100644 index 00000000..b428a114 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Compatible/BarSeries.cs @@ -0,0 +1,227 @@ +// (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.Windows.Data; +using System.Windows.Media.Animation; + +#if DEFINITION_SERIES_COMPATIBILITY_MODE +namespace System.Windows.Controls.DataVisualization.Charting +#else +namespace System.Windows.Controls.DataVisualization.Charting.Compatible +#endif +{ + /// + /// Control that displays values as a bar chart visualization. + /// + /// + /// Based on the DefinitionSeries hierarchy. + /// + /// Preview + [StyleTypedProperty(Property = DataPointStyleName, StyleTargetType = typeof(BarDataPoint))] + [StyleTypedProperty(Property = LegendItemStyleName, StyleTargetType = typeof(LegendItem))] + public class BarSeries : StackedBarSeries + { + /// + /// Name of the DataPointStyle property. + /// + private const string DataPointStyleName = "DataPointStyle"; + + /// + /// Name of the LegendItemStyle property. + /// + private const string LegendItemStyleName = "LegendItemStyle"; + + /// + /// Field storing the single SeriesDefinition used by the series. + /// + private SeriesDefinition _definition; + + /// + /// Initializes a new instance of the BarSeries class. + /// + public BarSeries() + { + SetBinding(DefinitionSeries.DependentAxisProperty, new Binding("DependentRangeAxis") { Source = this }); + SetBinding(DefinitionSeries.SelectionModeProperty, new Binding("IsSelectionEnabled") { Source = this, Converter = new System.Windows.Controls.DataVisualization.Charting.Compatible.SelectionEnabledToSelectionModeConverter() }); + _definition = new SeriesDefinition(); + _definition.SetBinding(SeriesDefinition.ItemsSourceProperty, new Binding("ItemsSource") { Source = this }); + _definition.SetBinding(SeriesDefinition.TitleProperty, new Binding("Title") { Source = this }); + _definition.SetBinding(SeriesDefinition.DataPointStyleProperty, new Binding(DataPointStyleName) { Source = this }); + _definition.SetBinding(SeriesDefinition.LegendItemStyleProperty, new Binding(LegendItemStyleName) { Source = this }); + _definition.SetBinding(SeriesDefinition.TransitionDurationProperty, new Binding("TransitionDuration") { Source = this }); +#if !NO_EASING_FUNCTIONS + _definition.SetBinding(SeriesDefinition.TransitionEasingFunctionProperty, new Binding("TransitionEasingFunction") { Source = this }); +#endif + // For compatibility + DependentValueBinding = new Binding(); + IndependentValueBinding = new Binding(); + SeriesDefinitions.Add(_definition); + } + + /// + /// Gets or sets a sequence that provides the content 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(BarSeries), null); + + /// + /// Gets or sets the Binding that identifies the dependent values of the series. + /// + public Binding DependentValueBinding + { + get { return _definition.DependentValueBinding; } + set { _definition.DependentValueBinding = value; } + } + + /// + /// Gets or sets the Binding path that identifies the dependent values of the series. + /// + public string DependentValuePath + { + get { return _definition.DependentValuePath; } + set { _definition.DependentValuePath = value; } + } + + /// + /// Gets or sets the Binding that identifies the independent values of the series. + /// + public Binding IndependentValueBinding + { + get { return _definition.IndependentValueBinding; } + set { _definition.IndependentValueBinding = value; } + } + + /// + /// Gets or sets the Binding path that identifies the independent values of the series. + /// + public string IndependentValuePath + { + get { return _definition.IndependentValuePath; } + set { _definition.IndependentValuePath = value; } + } + + /// + /// Gets or sets the IRangeAxis to use as the dependent axis of the series. + /// + public IRangeAxis DependentRangeAxis + { + get { return (IRangeAxis)GetValue(DependentRangeAxisProperty); } + set { SetValue(DependentRangeAxisProperty, value); } + } + + /// + /// Identifies the DependentRangeAxis dependency property. + /// + public static readonly DependencyProperty DependentRangeAxisProperty = + DependencyProperty.Register("DependentRangeAxis", typeof(IRangeAxis), typeof(BarSeries), null); + + /// + /// Gets or sets the title of the series. + /// + public object Title + { + get { return (object)GetValue(TitleProperty); } + set { SetValue(TitleProperty, value); } + } + + /// + /// Identifies the Title dependency property. + /// + public static readonly DependencyProperty TitleProperty = + DependencyProperty.Register("Title", typeof(object), typeof(BarSeries), null); + + /// + /// Gets or sets the Style to use for the DataPoints of the series. + /// + public Style DataPointStyle + { + get { return (Style)GetValue(DataPointStyleProperty); } + set { SetValue(DataPointStyleProperty, value); } + } + + /// + /// Identifies the DataPointStyle dependency property. + /// + public static readonly DependencyProperty DataPointStyleProperty = + DependencyProperty.Register(DataPointStyleName, typeof(Style), typeof(BarSeries), null); + + /// + /// Gets or sets the Style to use for the LegendItem of the series. + /// + public Style LegendItemStyle + { + get { return (Style)GetValue(LegendItemStyleProperty); } + set { SetValue(LegendItemStyleProperty, value); } + } + + /// + /// Identifies the LegendItemStyle dependency property. + /// + public static readonly DependencyProperty LegendItemStyleProperty = + DependencyProperty.Register(LegendItemStyleName, typeof(Style), typeof(BarSeries), null); + + /// + /// Gets or sets a value indicating whether selection is enabled. + /// + 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(BarSeries), null); + + /// + /// Gets or sets the TimeSpan to use for the duration of data transitions. + /// + 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(BarSeries), new PropertyMetadata(TimeSpan.FromSeconds(0.5))); + +#if !NO_EASING_FUNCTIONS + /// + /// Gets or sets the IEasingFunction to use for data transitions. + /// + public IEasingFunction TransitionEasingFunction + { + get { return (IEasingFunction)GetValue(TransitionEasingFunctionProperty); } + set { SetValue(TransitionEasingFunctionProperty, value); } + } + + /// + /// Identifies the TransitionEasingFunction dependency property. + /// + public static readonly DependencyProperty TransitionEasingFunctionProperty = + DependencyProperty.Register("TransitionEasingFunction", typeof(IEasingFunction), typeof(BarSeries), new PropertyMetadata(new QuadraticEase { EasingMode = EasingMode.EaseInOut })); +#else + /// + /// Gets or sets a placeholder for the TransitionEasingFunction dependency property. + /// + internal IEasingFunction TransitionEasingFunction { get; set; } +#endif + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Compatible/ColumnSeries.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Compatible/ColumnSeries.cs new file mode 100644 index 00000000..6889822d --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Compatible/ColumnSeries.cs @@ -0,0 +1,227 @@ +// (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.Windows.Data; +using System.Windows.Media.Animation; + +#if DEFINITION_SERIES_COMPATIBILITY_MODE +namespace System.Windows.Controls.DataVisualization.Charting +#else +namespace System.Windows.Controls.DataVisualization.Charting.Compatible +#endif +{ + /// + /// Control that displays values as a column chart visualization. + /// + /// + /// Based on the DefinitionSeries hierarchy. + /// + /// Preview + [StyleTypedProperty(Property = DataPointStyleName, StyleTargetType = typeof(ColumnDataPoint))] + [StyleTypedProperty(Property = LegendItemStyleName, StyleTargetType = typeof(LegendItem))] + public class ColumnSeries : StackedColumnSeries + { + /// + /// Name of the DataPointStyle property. + /// + private const string DataPointStyleName = "DataPointStyle"; + + /// + /// Name of the LegendItemStyle property. + /// + private const string LegendItemStyleName = "LegendItemStyle"; + + /// + /// Field storing the single SeriesDefinition used by the series. + /// + private SeriesDefinition _definition; + + /// + /// Initializes a new instance of the ColumnSeries class. + /// + public ColumnSeries() + { + SetBinding(DefinitionSeries.DependentAxisProperty, new Binding("DependentRangeAxis") { Source = this }); + SetBinding(DefinitionSeries.SelectionModeProperty, new Binding("IsSelectionEnabled") { Source = this, Converter = new System.Windows.Controls.DataVisualization.Charting.Compatible.SelectionEnabledToSelectionModeConverter() }); + _definition = new SeriesDefinition(); + _definition.SetBinding(SeriesDefinition.ItemsSourceProperty, new Binding("ItemsSource") { Source = this }); + _definition.SetBinding(SeriesDefinition.TitleProperty, new Binding("Title") { Source = this }); + _definition.SetBinding(SeriesDefinition.DataPointStyleProperty, new Binding(DataPointStyleName) { Source = this }); + _definition.SetBinding(SeriesDefinition.LegendItemStyleProperty, new Binding(LegendItemStyleName) { Source = this }); + _definition.SetBinding(SeriesDefinition.TransitionDurationProperty, new Binding("TransitionDuration") { Source = this }); +#if !NO_EASING_FUNCTIONS + _definition.SetBinding(SeriesDefinition.TransitionEasingFunctionProperty, new Binding("TransitionEasingFunction") { Source = this }); +#endif + // For compatibility + DependentValueBinding = new Binding(); + IndependentValueBinding = new Binding(); + SeriesDefinitions.Add(_definition); + } + + /// + /// Gets or sets a sequence that provides the content 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(ColumnSeries), null); + + /// + /// Gets or sets the Binding that identifies the dependent values of the series. + /// + public Binding DependentValueBinding + { + get { return _definition.DependentValueBinding; } + set { _definition.DependentValueBinding = value; } + } + + /// + /// Gets or sets the Binding path that identifies the dependent values of the series. + /// + public string DependentValuePath + { + get { return _definition.DependentValuePath; } + set { _definition.DependentValuePath = value; } + } + + /// + /// Gets or sets the Binding that identifies the independent values of the series. + /// + public Binding IndependentValueBinding + { + get { return _definition.IndependentValueBinding; } + set { _definition.IndependentValueBinding = value; } + } + + /// + /// Gets or sets the Binding path that identifies the independent values of the series. + /// + public string IndependentValuePath + { + get { return _definition.IndependentValuePath; } + set { _definition.IndependentValuePath = value; } + } + + /// + /// Gets or sets the IRangeAxis to use as the dependent axis of the series. + /// + public IRangeAxis DependentRangeAxis + { + get { return (IRangeAxis)GetValue(DependentRangeAxisProperty); } + set { SetValue(DependentRangeAxisProperty, value); } + } + + /// + /// Identifies the DependentRangeAxis dependency property. + /// + public static readonly DependencyProperty DependentRangeAxisProperty = + DependencyProperty.Register("DependentRangeAxis", typeof(IRangeAxis), typeof(ColumnSeries), null); + + /// + /// Gets or sets the title of the series. + /// + public object Title + { + get { return (object)GetValue(TitleProperty); } + set { SetValue(TitleProperty, value); } + } + + /// + /// Identifies the Title dependency property. + /// + public static readonly DependencyProperty TitleProperty = + DependencyProperty.Register("Title", typeof(object), typeof(ColumnSeries), null); + + /// + /// Gets or sets the Style to use for the DataPoints of the series. + /// + public Style DataPointStyle + { + get { return (Style)GetValue(DataPointStyleProperty); } + set { SetValue(DataPointStyleProperty, value); } + } + + /// + /// Identifies the DataPointStyle dependency property. + /// + public static readonly DependencyProperty DataPointStyleProperty = + DependencyProperty.Register(DataPointStyleName, typeof(Style), typeof(ColumnSeries), null); + + /// + /// Gets or sets the Style to use for the LegendItem of the series. + /// + public Style LegendItemStyle + { + get { return (Style)GetValue(LegendItemStyleProperty); } + set { SetValue(LegendItemStyleProperty, value); } + } + + /// + /// Identifies the LegendItemStyle dependency property. + /// + public static readonly DependencyProperty LegendItemStyleProperty = + DependencyProperty.Register(LegendItemStyleName, typeof(Style), typeof(ColumnSeries), null); + + /// + /// Gets or sets a value indicating whether selection is enabled. + /// + 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(ColumnSeries), null); + + /// + /// Gets or sets the TimeSpan to use for the duration of data transitions. + /// + 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(ColumnSeries), new PropertyMetadata(TimeSpan.FromSeconds(0.5))); + +#if !NO_EASING_FUNCTIONS + /// + /// Gets or sets the IEasingFunction to use for data transitions. + /// + public IEasingFunction TransitionEasingFunction + { + get { return (IEasingFunction)GetValue(TransitionEasingFunctionProperty); } + set { SetValue(TransitionEasingFunctionProperty, value); } + } + + /// + /// Identifies the TransitionEasingFunction dependency property. + /// + public static readonly DependencyProperty TransitionEasingFunctionProperty = + DependencyProperty.Register("TransitionEasingFunction", typeof(IEasingFunction), typeof(ColumnSeries), new PropertyMetadata(new QuadraticEase { EasingMode = EasingMode.EaseInOut })); +#else + /// + /// Gets or sets a placeholder for the TransitionEasingFunction dependency property. + /// + internal IEasingFunction TransitionEasingFunction { get; set; } +#endif + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Compatible/LineSeries.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Compatible/LineSeries.cs new file mode 100644 index 00000000..02a9c283 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Compatible/LineSeries.cs @@ -0,0 +1,268 @@ +// (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.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Windows.Data; +using System.Windows.Media.Animation; +using System.Windows.Shapes; + +#if DEFINITION_SERIES_COMPATIBILITY_MODE +namespace System.Windows.Controls.DataVisualization.Charting +#else +namespace System.Windows.Controls.DataVisualization.Charting.Compatible +#endif +{ + /// + /// Control that displays values as an line chart visualization. + /// + /// + /// Based on the DefinitionSeries hierarchy. + /// + /// Preview + [StyleTypedProperty(Property = DataPointStyleName, StyleTargetType = typeof(LineDataPoint))] + [StyleTypedProperty(Property = LegendItemStyleName, StyleTargetType = typeof(LegendItem))] + [StyleTypedProperty(Property = PolylineStyleName, StyleTargetType = typeof(Polyline))] + public class LineSeries : StackedLineSeries + { + /// + /// Name of the DataPointStyle property. + /// + private const string DataPointStyleName = "DataPointStyle"; + + /// + /// Name of the LegendItemStyle property. + /// + private const string LegendItemStyleName = "LegendItemStyle"; + + /// + /// Name of the PolylineStyle property. + /// + private const string PolylineStyleName = "PolylineStyle"; + + /// + /// Field storing the single SeriesDefinition used by the series. + /// + private SeriesDefinition _definition; + + /// + /// Initializes a new instance of the LineSeries class. + /// + public LineSeries() + { + SetBinding(DefinitionSeries.DependentAxisProperty, new Binding("DependentRangeAxis") { Source = this }); + SetBinding(DefinitionSeries.SelectionModeProperty, new Binding("IsSelectionEnabled") { Source = this, Converter = new System.Windows.Controls.DataVisualization.Charting.Compatible.SelectionEnabledToSelectionModeConverter() }); + _definition = new SeriesDefinition(); + _definition.SetBinding(SeriesDefinition.ItemsSourceProperty, new Binding("ItemsSource") { Source = this }); + _definition.SetBinding(SeriesDefinition.TitleProperty, new Binding("Title") { Source = this }); + _definition.SetBinding(SeriesDefinition.DataPointStyleProperty, new Binding(DataPointStyleName) { Source = this }); + _definition.SetBinding(SeriesDefinition.LegendItemStyleProperty, new Binding(LegendItemStyleName) { Source = this }); + _definition.SetBinding(SeriesDefinition.DataShapeStyleProperty, new Binding(PolylineStyleName) { Source = this }); + _definition.SetBinding(SeriesDefinition.TransitionDurationProperty, new Binding("TransitionDuration") { Source = this }); +#if !NO_EASING_FUNCTIONS + _definition.SetBinding(SeriesDefinition.TransitionEasingFunctionProperty, new Binding("TransitionEasingFunction") { Source = this }); +#endif + // For compatibility + DependentValueBinding = new Binding(); + IndependentValueBinding = new Binding(); + SeriesDefinitions.Add(_definition); + } + + /// + /// Gets a sequence of IndependentValueGroups. + /// + protected override IEnumerable IndependentValueGroups + { + get + { + // Base implementation groups by independent value; when plotting a single series in isolation, that's not desirable + return DataItems + .Select(di => new IndependentValueGroup(di.ActualIndependentValue, new DataItem[] { di })); + } + } + + /// + /// Gets or sets a sequence that provides the content 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(LineSeries), null); + + /// + /// Gets or sets the Binding that identifies the dependent values of the series. + /// + public Binding DependentValueBinding + { + get { return _definition.DependentValueBinding; } + set { _definition.DependentValueBinding = value; } + } + + /// + /// Gets or sets the Binding path that identifies the dependent values of the series. + /// + public string DependentValuePath + { + get { return _definition.DependentValuePath; } + set { _definition.DependentValuePath = value; } + } + + /// + /// Gets or sets the Binding that identifies the independent values of the series. + /// + public Binding IndependentValueBinding + { + get { return _definition.IndependentValueBinding; } + set { _definition.IndependentValueBinding = value; } + } + + /// + /// Gets or sets the Binding path that identifies the independent values of the series. + /// + public string IndependentValuePath + { + get { return _definition.IndependentValuePath; } + set { _definition.IndependentValuePath = value; } + } + + /// + /// Gets or sets the IRangeAxis to use as the dependent axis of the series. + /// + public IRangeAxis DependentRangeAxis + { + get { return (IRangeAxis)GetValue(DependentRangeAxisProperty); } + set { SetValue(DependentRangeAxisProperty, value); } + } + + /// + /// Identifies the DependentRangeAxis dependency property. + /// + public static readonly DependencyProperty DependentRangeAxisProperty = + DependencyProperty.Register("DependentRangeAxis", typeof(IRangeAxis), typeof(LineSeries), null); + + /// + /// Gets or sets the title of the series. + /// + public object Title + { + get { return (object)GetValue(TitleProperty); } + set { SetValue(TitleProperty, value); } + } + + /// + /// Identifies the Title dependency property. + /// + public static readonly DependencyProperty TitleProperty = + DependencyProperty.Register("Title", typeof(object), typeof(LineSeries), null); + + /// + /// Gets or sets the Style to use for the DataPoints of the series. + /// + public Style DataPointStyle + { + get { return (Style)GetValue(DataPointStyleProperty); } + set { SetValue(DataPointStyleProperty, value); } + } + + /// + /// Identifies the DataPointStyle dependency property. + /// + public static readonly DependencyProperty DataPointStyleProperty = + DependencyProperty.Register(DataPointStyleName, typeof(Style), typeof(LineSeries), null); + + /// + /// Gets or sets the Style to use for the LegendItem of the series. + /// + public Style LegendItemStyle + { + get { return (Style)GetValue(LegendItemStyleProperty); } + set { SetValue(LegendItemStyleProperty, value); } + } + + /// + /// Identifies the LegendItemStyle dependency property. + /// + public static readonly DependencyProperty LegendItemStyleProperty = + DependencyProperty.Register(LegendItemStyleName, typeof(Style), typeof(LineSeries), null); + + /// + /// Gets or sets a value indicating whether selection is enabled. + /// + 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(LineSeries), null); + + /// + /// Gets or sets the Style to use for the Path of the series. + /// + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Polyline", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.Compatible.LineSeries.#PolylineStyle", Justification = "Matches spelling of same-named framework class.")] + public Style PolylineStyle + { + get { return (Style)GetValue(PolylineStyleProperty); } + set { SetValue(PolylineStyleProperty, value); } + } + + /// + /// Identifies the PolylineStyle dependency property. + /// + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Polyline", Justification = "Matches spelling of same-named framework class.")] + public static readonly DependencyProperty PolylineStyleProperty = + DependencyProperty.Register(PolylineStyleName, typeof(Style), typeof(LineSeries), null); + + /// + /// Gets or sets the TimeSpan to use for the duration of data transitions. + /// + 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(LineSeries), new PropertyMetadata(TimeSpan.FromSeconds(0.5))); + +#if !NO_EASING_FUNCTIONS + /// + /// Gets or sets the IEasingFunction to use for data transitions. + /// + public IEasingFunction TransitionEasingFunction + { + get { return (IEasingFunction)GetValue(TransitionEasingFunctionProperty); } + set { SetValue(TransitionEasingFunctionProperty, value); } + } + + /// + /// Identifies the TransitionEasingFunction dependency property. + /// + public static readonly DependencyProperty TransitionEasingFunctionProperty = + DependencyProperty.Register("TransitionEasingFunction", typeof(IEasingFunction), typeof(LineSeries), new PropertyMetadata(new QuadraticEase { EasingMode = EasingMode.EaseInOut })); +#else + /// + /// Gets or sets a placeholder for the TransitionEasingFunction dependency property. + /// + internal IEasingFunction TransitionEasingFunction { get; set; } +#endif + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Compatible/ScatterSeries.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Compatible/ScatterSeries.cs new file mode 100644 index 00000000..d2e814df --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Compatible/ScatterSeries.cs @@ -0,0 +1,260 @@ +// (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.Linq; +using System.Windows.Data; +using System.Windows.Media.Animation; + +#if DEFINITION_SERIES_COMPATIBILITY_MODE +namespace System.Windows.Controls.DataVisualization.Charting +#else +namespace System.Windows.Controls.DataVisualization.Charting.Compatible +#endif +{ + /// + /// Control that displays values as a scatter chart visualization. + /// + /// + /// Based on the DefinitionSeries hierarchy. + /// + /// Preview + [StyleTypedProperty(Property = DataPointStyleName, StyleTargetType = typeof(ScatterDataPoint))] + [StyleTypedProperty(Property = LegendItemStyleName, StyleTargetType = typeof(LegendItem))] + public class ScatterSeries : StackedLineSeries + { + /// + /// Name of the DataPointStyle property. + /// + private const string DataPointStyleName = "DataPointStyle"; + + /// + /// Name of the LegendItemStyle property. + /// + private const string LegendItemStyleName = "LegendItemStyle"; + + /// + /// Field storing the single SeriesDefinition used by the series. + /// + private SeriesDefinition _definition; + + /// + /// Initializes a new instance of the ScatterSeries class. + /// + public ScatterSeries() + { + SetBinding(DefinitionSeries.DependentAxisProperty, new Binding("DependentRangeAxis") { Source = this }); + SetBinding(DefinitionSeries.SelectionModeProperty, new Binding("IsSelectionEnabled") { Source = this, Converter = new System.Windows.Controls.DataVisualization.Charting.Compatible.SelectionEnabledToSelectionModeConverter() }); + _definition = new SeriesDefinition(); + _definition.SetBinding(SeriesDefinition.ItemsSourceProperty, new Binding("ItemsSource") { Source = this }); + _definition.SetBinding(SeriesDefinition.TitleProperty, new Binding("Title") { Source = this }); + _definition.SetBinding(SeriesDefinition.DataPointStyleProperty, new Binding(DataPointStyleName) { Source = this }); + _definition.SetBinding(SeriesDefinition.LegendItemStyleProperty, new Binding(LegendItemStyleName) { Source = this }); + _definition.SetBinding(SeriesDefinition.TransitionDurationProperty, new Binding("TransitionDuration") { Source = this }); +#if !NO_EASING_FUNCTIONS + _definition.SetBinding(SeriesDefinition.TransitionEasingFunctionProperty, new Binding("TransitionEasingFunction") { Source = this }); +#endif + // For compatibility + DependentValueBinding = new Binding(); + IndependentValueBinding = new Binding(); + SeriesDefinitions.Add(_definition); + } + + /// + /// Creates a DataPoint for the series. + /// + /// Series-appropriate DataPoint instance. + protected override DataPoint CreateDataPoint() + { + return new ScatterDataPoint(); + } + + /// + /// Updates the shape for the series. + /// + /// Locations of the points of each SeriesDefinition in the series. + protected override void UpdateShape(IList> definitionPoints) + { + // Do not call base class implementation; leave shape empty for an easy way to use StackedLineSeries for ScatterSeries + } + + /// + /// Gets a sequence of IndependentValueGroups. + /// + protected override IEnumerable IndependentValueGroups + { + get + { + // Base implementation groups by independent value; when plotting a single series in isolation, that's not desirable + return DataItems + .Select(di => new IndependentValueGroup(di.ActualIndependentValue, new DataItem[] { di })); + } + } + + /// + /// Gets or sets a sequence that provides the content 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(ScatterSeries), null); + + /// + /// Gets or sets the Binding that identifies the dependent values of the series. + /// + public Binding DependentValueBinding + { + get { return _definition.DependentValueBinding; } + set { _definition.DependentValueBinding = value; } + } + + /// + /// Gets or sets the Binding path that identifies the dependent values of the series. + /// + public string DependentValuePath + { + get { return _definition.DependentValuePath; } + set { _definition.DependentValuePath = value; } + } + + /// + /// Gets or sets the Binding that identifies the independent values of the series. + /// + public Binding IndependentValueBinding + { + get { return _definition.IndependentValueBinding; } + set { _definition.IndependentValueBinding = value; } + } + + /// + /// Gets or sets the Binding path that identifies the independent values of the series. + /// + public string IndependentValuePath + { + get { return _definition.IndependentValuePath; } + set { _definition.IndependentValuePath = value; } + } + + /// + /// Gets or sets the IRangeAxis to use as the dependent axis of the series. + /// + public IRangeAxis DependentRangeAxis + { + get { return (IRangeAxis)GetValue(DependentRangeAxisProperty); } + set { SetValue(DependentRangeAxisProperty, value); } + } + + /// + /// Identifies the DependentRangeAxis dependency property. + /// + public static readonly DependencyProperty DependentRangeAxisProperty = + DependencyProperty.Register("DependentRangeAxis", typeof(IRangeAxis), typeof(ScatterSeries), null); + + /// + /// Gets or sets the title of the series. + /// + public object Title + { + get { return (object)GetValue(TitleProperty); } + set { SetValue(TitleProperty, value); } + } + + /// + /// Identifies the Title dependency property. + /// + public static readonly DependencyProperty TitleProperty = + DependencyProperty.Register("Title", typeof(object), typeof(ScatterSeries), null); + + /// + /// Gets or sets the Style to use for the DataPoints of the series. + /// + public Style DataPointStyle + { + get { return (Style)GetValue(DataPointStyleProperty); } + set { SetValue(DataPointStyleProperty, value); } + } + + /// + /// Identifies the DataPointStyle dependency property. + /// + public static readonly DependencyProperty DataPointStyleProperty = + DependencyProperty.Register(DataPointStyleName, typeof(Style), typeof(ScatterSeries), null); + + /// + /// Gets or sets the Style to use for the LegendItem of the series. + /// + public Style LegendItemStyle + { + get { return (Style)GetValue(LegendItemStyleProperty); } + set { SetValue(LegendItemStyleProperty, value); } + } + + /// + /// Identifies the LegendItemStyle dependency property. + /// + public static readonly DependencyProperty LegendItemStyleProperty = + DependencyProperty.Register(LegendItemStyleName, typeof(Style), typeof(ScatterSeries), null); + + /// + /// Gets or sets a value indicating whether selection is enabled. + /// + 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(ScatterSeries), null); + + /// + /// Gets or sets the TimeSpan to use for the duration of data transitions. + /// + 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(ScatterSeries), new PropertyMetadata(TimeSpan.FromSeconds(0.5))); + +#if !NO_EASING_FUNCTIONS + /// + /// Gets or sets the IEasingFunction to use for data transitions. + /// + public IEasingFunction TransitionEasingFunction + { + get { return (IEasingFunction)GetValue(TransitionEasingFunctionProperty); } + set { SetValue(TransitionEasingFunctionProperty, value); } + } + + /// + /// Identifies the TransitionEasingFunction dependency property. + /// + public static readonly DependencyProperty TransitionEasingFunctionProperty = + DependencyProperty.Register("TransitionEasingFunction", typeof(IEasingFunction), typeof(ScatterSeries), new PropertyMetadata(new QuadraticEase { EasingMode = EasingMode.EaseInOut })); +#else + /// + /// Gets or sets a placeholder for the TransitionEasingFunction dependency property. + /// + internal IEasingFunction TransitionEasingFunction { get; set; } +#endif + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Compatible/SelectionEnabledToSelectionModeConverter.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Compatible/SelectionEnabledToSelectionModeConverter.cs new file mode 100644 index 00000000..4c2eeca4 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Compatible/SelectionEnabledToSelectionModeConverter.cs @@ -0,0 +1,54 @@ +// (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.Globalization; +using System.Windows.Data; + +namespace System.Windows.Controls.DataVisualization.Charting.Compatible +{ + /// + /// Converts from a true/false value indicating whether selection is enabled to a SeriesSelectionMode. + /// + internal class SelectionEnabledToSelectionModeConverter : IValueConverter + { + /// + /// Initializes a new instance of the SelectionEnabledToSelectionModeConverter class. + /// + public SelectionEnabledToSelectionModeConverter() + { + } + + /// + /// Converts a value. + /// + /// The value produced by the binding source. + /// The type of the binding target property. + /// The converter parameter to use. + /// The culture to use in the converter. + /// Converted value. + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + SeriesSelectionMode selectionMode = SeriesSelectionMode.None; + if ((value is bool) && (bool)value) + { + selectionMode = SeriesSelectionMode.Single; + } + return selectionMode; + } + + /// + /// Converts a value back. + /// + /// The value produced by the binding source. + /// The type of the binding target property. + /// The converter parameter to use. + /// The culture to use in the converter. + /// Converted value. + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/DataPointSeries.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/DataPointSeries.cs new file mode 100644 index 00000000..af11f6eb --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/DataPointSeries.cs @@ -0,0 +1,1398 @@ +// (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; + }); + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/DataPointSeriesWithAxes.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/DataPointSeriesWithAxes.cs new file mode 100644 index 00000000..d34874b0 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/DataPointSeriesWithAxes.cs @@ -0,0 +1,665 @@ +// (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; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.DataVisualization; +using System.Windows.Controls.DataVisualization.Collections; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Represents a dynamic series that uses axes to display data points. + /// + /// Preview + public abstract class DataPointSeriesWithAxes : DataPointSeries, IDataProvider, IRangeProvider, IAxisListener, IValueMarginProvider + { + /// + /// Gets or sets the data points by dependent value. + /// + private OrderedMultipleDictionary DataPointsByActualDependentValue { get; set; } + + /// + /// Creates the correct range axis based on the data. + /// + /// The value to evaluate to determine which type of + /// axis to create. + /// The range axis appropriate that can plot the provided + /// value. + protected static IRangeAxis CreateRangeAxisFromData(object value) + { + double doubleValue; + DateTime dateTime; + if (ValueHelper.TryConvert(value, out doubleValue)) + { + return new LinearAxis(); + } + else if (ValueHelper.TryConvert(value, out dateTime)) + { + return new DateTimeAxis(); + } + else + { + return null; + } + } + + /// + /// Retrieves the value for a given access from a data point. + /// + /// The data point to retrieve the value from. + /// The axis to retrieve the value for. + /// A function that returns a value appropriate for the axis + /// when provided a DataPoint. + protected virtual object GetActualDataPointAxisValue(DataPoint dataPoint, IAxis axis) + { + if (axis == InternalActualIndependentAxis) + { + return dataPoint.ActualIndependentValue; + } + else if (axis == InternalActualDependentAxis) + { + return dataPoint.ActualDependentValue; + } + return null; + } + + /// + /// Gets or sets the actual dependent axis. + /// + protected IAxis InternalActualDependentAxis { get; set; } + + #region public Axis InternalDependentAxis + + /// + /// Stores the internal dependent axis. + /// + private IAxis _internalDependentAxis; + + /// + /// Gets or sets the value of the internal dependent axis. + /// + protected IAxis InternalDependentAxis + { + get { return _internalDependentAxis; } + set + { + if (_internalDependentAxis != value) + { + IAxis oldValue = _internalDependentAxis; + _internalDependentAxis = value; + OnInternalDependentAxisPropertyChanged(oldValue, value); + } + } + } + + /// + /// DependentAxisProperty property changed handler. + /// + /// Old value. + /// New value. + protected virtual void OnInternalDependentAxisPropertyChanged(IAxis oldValue, IAxis newValue) + { + if (newValue != null + && InternalActualDependentAxis != null + && InternalActualDependentAxis != newValue + && InternalActualDependentAxis.RegisteredListeners.Contains(this)) + { + InternalActualDependentAxis.RegisteredListeners.Remove(this); + InternalActualDependentAxis = null; + GetAxes(); + } + } + #endregion public Axis InternalDependentAxis + + /// + /// Gets or sets the actual independent axis value. + /// + protected IAxis InternalActualIndependentAxis { get; set; } + + #region protected Axis InternalIndependentAxis + + /// + /// The internal independent axis. + /// + private IAxis _internalIndependentAxis; + + /// + /// Gets or sets the value of the internal independent axis. + /// + protected IAxis InternalIndependentAxis + { + get { return _internalIndependentAxis; } + set + { + if (value != _internalIndependentAxis) + { + IAxis oldValue = _internalIndependentAxis; + _internalIndependentAxis = value; + OnInternalIndependentAxisPropertyChanged(oldValue, value); + } + } + } + + /// + /// IndependentAxisProperty property changed handler. + /// + /// Old value. + /// New value. + protected virtual void OnInternalIndependentAxisPropertyChanged(IAxis oldValue, IAxis newValue) + { + if (newValue != null + && InternalActualIndependentAxis != null + && InternalActualIndependentAxis != newValue + && InternalActualIndependentAxis.RegisteredListeners.Contains(this)) + { + InternalActualIndependentAxis.RegisteredListeners.Remove(this); + InternalActualIndependentAxis = null; + GetAxes(); + } + } + #endregion protected Axis IndependentAxis + + /// + /// Initializes a new instance of the DataPointSeriesWithAxes class. + /// + protected DataPointSeriesWithAxes() + { + this.DataPointsByActualDependentValue = + new OrderedMultipleDictionary( + false, + (left, right) => + left.CompareTo(right), + (leftDataPoint, rightDataPoint) => + RuntimeHelpers.GetHashCode(leftDataPoint).CompareTo(RuntimeHelpers.GetHashCode(rightDataPoint))); + } + + /// + /// Update the axes when the specified data point's ActualDependentValue property changes. + /// + /// The data point. + /// The old value. + /// The new value. + protected override void OnDataPointActualDependentValueChanged(DataPoint dataPoint, IComparable oldValue, IComparable newValue) + { + if (oldValue != null) + { + bool removed = DataPointsByActualDependentValue.Remove(oldValue, dataPoint); + if (removed) + { + DataPointsByActualDependentValue.Add(newValue, dataPoint); + } + } + + UpdateActualDependentAxis(); + UpdateDataPoint(dataPoint); + base.OnDataPointActualDependentValueChanged(dataPoint, oldValue, newValue); + } + + /// + /// Update the axes when the specified data point's DependentValue property changes. + /// + /// The data point. + /// The old value. + /// The new value. + protected override void OnDataPointDependentValueChanged(DataPoint dataPoint, IComparable oldValue, IComparable newValue) + { + if ((null != InternalActualDependentAxis)) + { + dataPoint.BeginAnimation(DataPoint.ActualDependentValueProperty, "ActualDependentValue", newValue, this.TransitionDuration, this.TransitionEasingFunction); + } + else + { + dataPoint.ActualDependentValue = newValue; + } + base.OnDataPointDependentValueChanged(dataPoint, oldValue, newValue); + } + + /// + /// Update axes when the specified data point's effective dependent value changes. + /// + private void UpdateActualDependentAxis() + { + if (InternalActualDependentAxis != null) + { + IDataConsumer dataConsumer = InternalActualDependentAxis as IDataConsumer; + if (dataConsumer != null) + { + IDataProvider categoryInformationProvider = (IDataProvider)this; + dataConsumer.DataChanged(categoryInformationProvider, categoryInformationProvider.GetData(dataConsumer)); + } + + IRangeConsumer rangeAxis = InternalActualDependentAxis as IRangeConsumer; + if (rangeAxis != null) + { + IRangeProvider rangeInformationProvider = (IRangeProvider)this; + rangeAxis.RangeChanged(rangeInformationProvider, rangeInformationProvider.GetRange(rangeAxis)); + } + } + } + + /// + /// Update axes when the specified data point's actual independent value changes. + /// + /// The data point. + /// The old value. + /// The new value. + protected override void OnDataPointActualIndependentValueChanged(DataPoint dataPoint, object oldValue, object newValue) + { + UpdateActualIndependentAxis(); + UpdateDataPoint(dataPoint); + base.OnDataPointActualIndependentValueChanged(dataPoint, oldValue, newValue); + } + + /// + /// Update axes when the specified data point's independent value changes. + /// + /// The data point. + /// The old value. + /// The new value. + protected override void OnDataPointIndependentValueChanged(DataPoint dataPoint, object oldValue, object newValue) + { + if ((null != InternalActualIndependentAxis) && (InternalActualIndependentAxis is IRangeAxis)) + { + dataPoint.BeginAnimation(DataPoint.ActualIndependentValueProperty, "ActualIndependentValue", newValue, this.TransitionDuration, this.TransitionEasingFunction); + } + else + { + dataPoint.ActualIndependentValue = newValue; + } + base.OnDataPointIndependentValueChanged(dataPoint, oldValue, newValue); + } + + /// + /// Update axes when a data point's effective independent value changes. + /// + private void UpdateActualIndependentAxis() + { + if (InternalActualIndependentAxis != null) + { + ICategoryAxis categoryAxis = InternalActualIndependentAxis as ICategoryAxis; + if (categoryAxis != null) + { + IDataProvider categoryInformationProvider = (IDataProvider)this; + categoryAxis.DataChanged(categoryInformationProvider, categoryInformationProvider.GetData(categoryAxis)); + } + IRangeConsumer rangeAxis = InternalActualIndependentAxis as IRangeConsumer; + if (rangeAxis != null) + { + IRangeProvider rangeInformationProvider = (IRangeProvider)this; + rangeAxis.RangeChanged(rangeInformationProvider, rangeInformationProvider.GetRange(rangeAxis)); + } + } + } + + /// + /// Called after data points have been loaded from the items source. + /// + /// New active data points. + /// Old inactive data points. + protected override void OnDataPointsChanged(IList newDataPoints, IList oldDataPoints) + { + foreach (DataPoint dataPoint in newDataPoints) + { + DataPointsByActualDependentValue.Add(dataPoint.ActualDependentValue, dataPoint); + } + + foreach (DataPoint dataPoint in oldDataPoints) + { + DataPointsByActualDependentValue.Remove(dataPoint.ActualDependentValue, dataPoint); + } + + GetAxes(); + + if (InternalActualDependentAxis != null && InternalActualIndependentAxis != null) + { + Action action = () => + { + AxesInvalidated = false; + UpdatingAllAxes = true; + try + { + UpdateActualIndependentAxis(); + UpdateActualDependentAxis(); + } + finally + { + UpdatingAllAxes = false; + } + + if (AxesInvalidated) + { + UpdateDataPoints(ActiveDataPoints); + } + else + { + UpdateDataPoints(newDataPoints); + } + + AxesInvalidated = false; + }; + + InvokeOnLayoutUpdated(action); + } + + base.OnDataPointsChanged(newDataPoints, oldDataPoints); + } + + /// + /// Gets or sets a value indicating whether to the axes are being + /// updated. + /// + private bool UpdatingAllAxes { get; set; } + + /// + /// Gets or sets a value indicating whether the axes have been + /// invalidated. + /// + private bool AxesInvalidated { get; set; } + + /// + /// Only updates all data points if series has axes. + /// + /// A sequence of data points to update. + /// + protected override void UpdateDataPoints(IEnumerable dataPoints) + { + if (InternalActualIndependentAxis != null && InternalActualDependentAxis != null) + { + base.UpdateDataPoints(dataPoints); + } + } + + /// + /// Method called to get series to acquire the axes it needs. Acquires + /// no axes by default. + /// + private void GetAxes() + { + if (SeriesHost != null) + { + DataPoint firstDataPoint = ActiveDataPoints.FirstOrDefault(); + if (firstDataPoint == null) + { + return; + } + + GetAxes(firstDataPoint); + } + } + + /// + /// Method called to get series to acquire the axes it needs. Acquires + /// no axes by default. + /// + /// The first data point. + protected abstract void GetAxes(DataPoint firstDataPoint); + + /// + /// Method called to get the axes that the series needs. + /// + /// The first data point. + /// A predicate that returns + /// a value indicating whether an axis is an acceptable candidate for + /// the series independent axis. + /// A function that creates an + /// acceptable independent axis. + /// A predicate that returns + /// a value indicating whether an axis is an acceptable candidate for + /// the series dependent axis. + /// A function that creates an + /// acceptable dependent axis. + protected virtual void GetAxes(DataPoint firstDataPoint, Func independentAxisPredicate, Func independentAxisFactory, Func dependentAxisPredicate, Func dependentAxisFactory) + { + Func actualIndependentAxisPredicate = (axis) => independentAxisPredicate(axis) && axis.CanPlot(firstDataPoint.IndependentValue); + IAxis workingIndependentAxis = null; + if (this.InternalActualIndependentAxis == null) + { + if (this.InternalIndependentAxis != null) + { + if (actualIndependentAxisPredicate(this.InternalIndependentAxis)) + { + workingIndependentAxis = this.InternalIndependentAxis; + } + else + { + throw new InvalidOperationException(Properties.Resources.DataPointSeriesWithAxes_GetAxes_AssignedIndependentAxisCannotBeUsed); + } + } + + if (workingIndependentAxis == null) + { + workingIndependentAxis = this.SeriesHost.Axes.FirstOrDefault(actualIndependentAxisPredicate); + } + + if (workingIndependentAxis == null) + { + workingIndependentAxis = independentAxisFactory(); + } + + this.InternalActualIndependentAxis = workingIndependentAxis; + + if (!workingIndependentAxis.RegisteredListeners.Contains(this)) + { + workingIndependentAxis.RegisteredListeners.Add(this); + } + if (!this.SeriesHost.Axes.Contains(workingIndependentAxis)) + { + this.SeriesHost.Axes.Add(workingIndependentAxis); + } + } + + Func actualDependentAxisPredicate = (axis) => dependentAxisPredicate(axis) && axis.CanPlot(firstDataPoint.DependentValue); + IAxis workingDependentAxis = null; + if (this.InternalActualDependentAxis == null) + { + if (this.InternalDependentAxis != null) + { + if (actualDependentAxisPredicate(this.InternalDependentAxis)) + { + workingDependentAxis = this.InternalDependentAxis; + } + else + { + throw new InvalidOperationException(Properties.Resources.DataPointSeriesWithAxes_GetAxes_AssignedDependentAxisCannotBeUsed); + } + } + + if (workingDependentAxis == null) + { + workingDependentAxis = InternalActualIndependentAxis.DependentAxes.Concat(this.SeriesHost.Axes).FirstOrDefault(actualDependentAxisPredicate); + } + + if (workingDependentAxis == null) + { + workingDependentAxis = dependentAxisFactory(); + } + + this.InternalActualDependentAxis = workingDependentAxis; + + if (!workingDependentAxis.RegisteredListeners.Contains(this)) + { + workingDependentAxis.RegisteredListeners.Add(this); + } + + // Only add axis to the axes collection of the series host if + // it is not a dependent axis belonging to the acquired + // independent axis. + if (!this.SeriesHost.Axes.Contains(workingDependentAxis) && !InternalActualIndependentAxis.DependentAxes.Contains(workingDependentAxis)) + { + this.SeriesHost.Axes.Add(workingDependentAxis); + } + } + } + + /// + /// Updates data points when the axis is invalidated. + /// + /// The axis that was invalidated. + void IAxisListener.AxisInvalidated(IAxis axis) + { + if (InternalActualDependentAxis != null && InternalActualIndependentAxis != null && PlotArea != null) + { + if (!UpdatingAllAxes) + { + UpdateDataPoints(ActiveDataPoints); + } + else + { + AxesInvalidated = true; + } + } + } + + /// + /// Returns the actual range of data for a given axis. + /// + /// The axis to retrieve the range for. + /// The actual range of data. + protected virtual Range GetRange(IRangeConsumer consumer) + { + if (consumer == null) + { + throw new ArgumentNullException("consumer"); + } + + if (consumer == InternalActualDependentAxis) + { + if (this.DataPointsByActualDependentValue.Count > 0) + { + return this.DataPointsByActualDependentValue.GetKeyRange(); + } + } + + IAxis axis = consumer as IAxis; + return (axis != null) + ? ActiveDataPoints.Select(dataPoint => (IComparable)GetActualDataPointAxisValue(dataPoint, axis)).GetRange() + : new Range(); + } + + /// + /// Returns the value margins for a given axis. + /// + /// The axis to retrieve the value margins for. + /// + /// A sequence of value margins. + protected virtual IEnumerable GetValueMargins(IValueMarginConsumer consumer) + { + IAxis axis = consumer as IAxis; + if (axis != null && ActiveDataPoints.Any()) + { + Func selector = null; + DataPoint minimumPoint = null; + DataPoint maximumPoint = null; + double margin = 0.0; + if (axis == InternalActualIndependentAxis) + { + selector = (dataPoint) => (IComparable)dataPoint.ActualIndependentValue; + + minimumPoint = ActiveDataPoints.MinOrNull(selector); + maximumPoint = ActiveDataPoints.MaxOrNull(selector); + margin = minimumPoint.GetActualMargin(this.InternalActualIndependentAxis); + } + else if (axis == InternalActualDependentAxis) + { + selector = (dataPoint) => (IComparable)dataPoint.ActualDependentValue; + + Tuple largestAndSmallestValues = this.DataPointsByActualDependentValue.GetLargestAndSmallestValues(); + minimumPoint = largestAndSmallestValues.Item1; + maximumPoint = largestAndSmallestValues.Item2; + margin = minimumPoint.GetActualMargin(this.InternalActualDependentAxis); + } + + yield return new ValueMargin(selector(minimumPoint), margin, margin); + yield return new ValueMargin(selector(maximumPoint), margin, margin); + } + else + { + yield break; + } + } + + /// + /// Returns data to a data consumer. + /// + /// The data consumer requesting the data. + /// + /// The data for a given data consumer. + IEnumerable IDataProvider.GetData(IDataConsumer dataConsumer) + { + IAxis axis = (IAxis)dataConsumer; + if (axis == null) + { + throw new ArgumentNullException("dataConsumer"); + } + + Func selector = null; + if (axis == InternalActualIndependentAxis) + { + if (IndependentValueBinding == null) + { + return Enumerable.Range(1, ActiveDataPointCount).CastWrapper(); + } + selector = (dataPoint) => dataPoint.ActualIndependentValue ?? dataPoint.ActualDependentValue; + } + else if (axis == InternalActualDependentAxis) + { + selector = (dataPoint) => dataPoint.ActualDependentValue; + } + + return ActiveDataPoints.Select(selector).Distinct(); + } + + /// + /// Called when the value of the SeriesHost property changes. + /// + /// The value to be replaced. + /// The new series host value. + protected override void OnSeriesHostPropertyChanged(ISeriesHost oldValue, ISeriesHost newValue) + { + if (oldValue != null) + { + if (InternalActualIndependentAxis != null) + { + InternalActualIndependentAxis.RegisteredListeners.Remove(this); + InternalActualIndependentAxis = null; + } + if (InternalActualDependentAxis != null) + { + InternalActualDependentAxis.RegisteredListeners.Remove(this); + InternalActualDependentAxis = null; + } + } + + base.OnSeriesHostPropertyChanged(oldValue, newValue); + } + + /// + /// Returns the data range. + /// + /// The consumer requesting the range. + /// The data range. + Range IRangeProvider.GetRange(IRangeConsumer rangeConsumer) + { + return GetRange(rangeConsumer); + } + + /// + /// Returns the value margins for a given axis. + /// + /// The axis to retrieve the value margins for. + /// + /// A sequence of value margins. + IEnumerable IValueMarginProvider.GetValueMargins(IValueMarginConsumer axis) + { + return GetValueMargins(axis); + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/DataPointSingleSeriesWithAxes.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/DataPointSingleSeriesWithAxes.cs new file mode 100644 index 00000000..d3f97314 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/DataPointSingleSeriesWithAxes.cs @@ -0,0 +1,352 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Windows.Data; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// A dynamic series with axes and only one legend item and style for all + /// data points. + /// + /// Preview + public abstract class DataPointSingleSeriesWithAxes : DataPointSeriesWithAxes, IRequireGlobalSeriesIndex + { + /// + /// Name of the ActualDataPointStyle property. + /// + protected const string ActualDataPointStyleName = "ActualDataPointStyle"; + + /// + /// Gets the single legend item associated with the series. + /// + protected LegendItem LegendItem + { + get + { + if (null == _legendItem) + { + _legendItem = CreateLegendItem(this); + LegendItems.Add(_legendItem); + } + return _legendItem; + } + } + + /// + /// Stores the LegendItem for the series. + /// + private LegendItem _legendItem; + + /// + /// Gets the Palette-dispensed ResourceDictionary for the Series. + /// + protected ResourceDictionary PaletteResources { get; private set; } + + /// + /// Gets or sets a value indicating whether a custom title is in use. + /// + private bool CustomTitleInUse { get; set; } + + /// + /// DataPointStyleProperty property changed handler. + /// + /// Old value. + /// New value. + protected override void OnDataPointStylePropertyChanged(Style oldValue, Style newValue) + { + // Propagate change + ActualDataPointStyle = newValue; + base.OnDataPointStylePropertyChanged(oldValue, newValue); + } + + #region protected Style ActualDataPointStyle + /// + /// Gets or sets the actual style used for the data points. + /// + protected Style ActualDataPointStyle + { + get { return GetValue(ActualDataPointStyleProperty) as Style; } + set { SetValue(ActualDataPointStyleProperty, value); } + } + + /// + /// Identifies the ActualDataPointStyle dependency property. + /// + protected static readonly DependencyProperty ActualDataPointStyleProperty = + DependencyProperty.Register( + ActualDataPointStyleName, + typeof(Style), + typeof(DataPointSingleSeriesWithAxes), + null); + #endregion protected Style ActualDataPointStyle + + #region protected Style ActualLegendItemStyle + /// + /// Gets or sets the actual style used for the legend item. + /// + protected Style ActualLegendItemStyle + { + get { return GetValue(ActualLegendItemStyleProperty) as Style; } + set { SetValue(ActualLegendItemStyleProperty, value); } + } + + /// + /// Identifies the ActualLegendItemStyle dependency property. + /// + protected static readonly DependencyProperty ActualLegendItemStyleProperty = + DependencyProperty.Register( + ActualLegendItemStyleName, + typeof(Style), + typeof(DataPointSeries), + null); + #endregion protected Style ActualLegendItemStyle + + /// + /// Called when the value of the LegendItemStyle property changes. + /// + /// Old value. + /// New value. + protected override void OnLegendItemStylePropertyChanged(Style oldValue, Style newValue) + { + // Propagate change + ActualLegendItemStyle = newValue; + base.OnLegendItemStylePropertyChanged(oldValue, newValue); + } + + #region public int? GlobalSeriesIndex + /// + /// Gets the index of the series in the Parent's series collection. + /// + public int? GlobalSeriesIndex + { + get { return (int?)GetValue(GlobalSeriesIndexProperty); } + private set { SetValue(GlobalSeriesIndexProperty, value); } + } + + /// + /// Identifies the GlobalSeriesIndex dependency property. + /// + public static readonly DependencyProperty GlobalSeriesIndexProperty = + DependencyProperty.Register( + "GlobalSeriesIndex", + typeof(int?), + typeof(Series), + new PropertyMetadata(new int?(), OnGlobalSeriesIndexPropertyChanged)); + + /// + /// GlobalSeriesIndexProperty property changed handler. + /// + /// Series that changed its Index. + /// Event arguments. + private static void OnGlobalSeriesIndexPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DataPointSingleSeriesWithAxes source = (DataPointSingleSeriesWithAxes)d; + int? oldValue = (int?)e.OldValue; + int? newValue = (int?)e.NewValue; + source.OnGlobalSeriesIndexPropertyChanged(oldValue, newValue); + } + + /// + /// GlobalSeriesIndexProperty property changed handler. + /// + /// Old value. + /// New value. + [SuppressMessage("Microsoft.Usage", "CA2233:OperationsShouldNotOverflow", MessageId = "newValue+1", Justification = "Impractical to add as many Series as it would take to overflow.")] + protected virtual void OnGlobalSeriesIndexPropertyChanged(int? oldValue, int? newValue) + { + if (!CustomTitleInUse && (null == GetBindingExpression(TitleProperty))) + { + Title = newValue.HasValue ? string.Format(CultureInfo.CurrentCulture, Properties.Resources.Series_OnGlobalSeriesIndexPropertyChanged_UntitledSeriesFormatString, newValue.Value + 1) : null; + // Setting Title will set CustomTitleInUse; reset it now + CustomTitleInUse = false; + } + } + #endregion public int? GlobalSeriesIndex + + /// + /// Called when the Title property changes. + /// + /// Old value of the Title property. + /// New value of the Title property. + protected override void OnTitleChanged(object oldValue, object newValue) + { + // Title property is being set, so a custom Title is in use + CustomTitleInUse = true; + } + + /// + /// Initializes a new instance of the DataPointSingleSeriesWithAxes class. + /// + protected DataPointSingleSeriesWithAxes() + { + } + + /// + /// Returns the custom ResourceDictionary to use for necessary resources. + /// + /// + /// ResourceDictionary to use for necessary resources. + /// + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This property does more work than get functions typically do.")] + protected abstract IEnumerator GetResourceDictionaryEnumeratorFromHost(); + + /// + /// Insert grid containing data point used for legend item into the + /// plot area. + /// + /// The old plot area. + /// The new plot area. + protected override void OnPlotAreaChanged(Panel oldValue, Panel newValue) + { + if (newValue != null) + { + CreateLegendItemDataPoint(); + } + base.OnPlotAreaChanged(oldValue, newValue); + } + + /// + /// When the series host property is set retrieves a style to use for all the + /// data points. + /// + /// The old series host value. + /// The new series host value. + protected override void OnSeriesHostPropertyChanged(ISeriesHost oldValue, ISeriesHost newValue) + { + base.OnSeriesHostPropertyChanged(oldValue, newValue); + + if (oldValue != null) + { + oldValue.ResourceDictionariesChanged -= new EventHandler(SeriesHostResourceDictionariesChanged); + } + + if (newValue != null) + { + newValue.ResourceDictionariesChanged += new EventHandler(SeriesHostResourceDictionariesChanged); + + DispensedResourcesChanging(); + } + } + + /// + /// Creates the LegendItem Control if conditions are right. + /// + private void CreateLegendItemDataPoint() + { + DataPoint dataPoint = CreateDataPoint(); + if (null != PlotArea) + { + // Bounce into the visual tree to get default Style applied + PlotArea.Children.Add(dataPoint); + PlotArea.Children.Remove(dataPoint); + } + dataPoint.SetBinding(DataPoint.StyleProperty, new Binding(ActualDataPointStyleName) { Source = this }); + // Start DataContext null to avoid Binding warnings in the output window + LegendItem.DataContext = null; +#if !SILVERLIGHT + if (null == LegendItem.Parent) + { +#endif + LegendItem.Loaded += delegate + { + // Wait for Loaded to set the DataPoint + LegendItem.DataContext = dataPoint; + }; +#if !SILVERLIGHT + } + else + { + LegendItem.DataContext = dataPoint; + } +#endif + } + + /// + /// Called after data points have been loaded from the items source. + /// + /// New active data points. + /// Old inactive data points. + protected override void OnDataPointsChanged(IList newDataPoints, IList oldDataPoints) + { + base.OnDataPointsChanged(newDataPoints, oldDataPoints); + + if (null != PlotArea) + { + // Create the Control for use by LegendItem + // Add it to the visual tree so that its style will be applied + if (null != LegendItem.DataContext) + { + PlotArea.Children.Remove(LegendItem.DataContext as UIElement); + } + } + } + + /// + /// Sets the style of the data point to the single style used for all + /// data points. + /// + /// The data point to apply the style to. + /// + /// The object associated with the data point. + /// + protected override void PrepareDataPoint(DataPoint dataPoint, object dataContext) + { + dataPoint.SetBinding(DataPoint.StyleProperty, new Binding(ActualDataPointStyleName) { Source = this }); + base.PrepareDataPoint(dataPoint, dataContext); + } + + /// + /// This method updates the global series index property. + /// + /// The global index of the series. + public void GlobalSeriesIndexChanged(int? globalIndex) + { + this.GlobalSeriesIndex = globalIndex; + } + + /// + /// Handles the SeriesHost's ResourceDictionariesChanged event. + /// + /// ISeriesHost instance. + /// Event args. + private void SeriesHostResourceDictionariesChanged(object sender, EventArgs e) + { + DispensedResourcesChanging(); + } + + /// + /// Processes the change of the DispensedResources property. + /// + private void DispensedResourcesChanging() + { + if (null != PaletteResources) + { + Resources.MergedDictionaries.Remove(PaletteResources); + PaletteResources = null; + } + using (IEnumerator enumerator = GetResourceDictionaryEnumeratorFromHost()) + { + if (enumerator.MoveNext()) + { + PaletteResources = +#if SILVERLIGHT + enumerator.Current.ShallowCopy(); +#else + enumerator.Current; +#endif + Resources.MergedDictionaries.Add(PaletteResources); + } + } + CreateLegendItemDataPoint(); + ActualDataPointStyle = DataPointStyle ?? (Resources[DataPointStyleName] as Style); + ActualLegendItemStyle = LegendItemStyle ?? (Resources[LegendItemStyleName] as Style); + Refresh(); + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/DefinitionSeries.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/DefinitionSeries.cs new file mode 100644 index 00000000..b6a73d96 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/DefinitionSeries.cs @@ -0,0 +1,1611 @@ +// (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 +{ + /// + /// Implements a series that is defined by one or more instances of the DefinitionSeries class. + /// + /// Preview + [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 + { + /// + /// Name of the SeriesArea property. + /// + private const string SeriesAreaName = "SeriesArea"; + + /// + /// Name of the ItemContainer property. + /// + private const string ItemContainerName = "ItemContainer"; + + /// + /// Gets or sets a value indicating whether the series is 100% stacked (versus normally stacked). + /// + protected bool IsStacked100 { get; set; } + + /// + /// Gets the collection of DataItems representing the data of the series. + /// + protected ObservableCollection DataItems { get; private set; } + + /// + /// Gets the SeriesArea template part instance. + /// + protected Panel SeriesArea { get; private set; } + + /// + /// Stores an aggregated collection of legend items from the series definitions. + /// + private readonly AggregatedObservableCollection _legendItems = new AggregatedObservableCollection(); + + /// + /// Stores the collection of SeriesDefinitions that define the series. + /// + private readonly ObservableCollection _seriesDefinitions = new UniqueObservableCollection(); + + /// + /// Stores a mirror collection of ISeries corresponding directly to the collection of SeriesDefinitions. + /// + /// + /// Not using ObservableCollectionListAdapter because of race condition on ItemsChanged event + /// + private readonly ObservableCollection _seriesDefinitionsAsISeries = new ObservableCollection(); + + /// + /// Keeps the SeriesDefinitions collection synchronized with the Children collection of the SeriesArea. + /// + private readonly ObservableCollectionListAdapter _seriesAreaChildrenListAdapter = new ObservableCollectionListAdapter(); + + /// + /// Stores the clip geometry for the ItemContainer. + /// + private readonly RectangleGeometry _clipGeometry = new RectangleGeometry(); + + /// + /// Stores a reference to the ItemContainer template part. + /// + private DelegatingListBox _itemContainer; + + /// + /// Tracks the collection of DataItem that are queued for update. + /// + private readonly List _queueUpdateDataItemPlacement_DataItems = new List(); + + /// + /// Tracks whether the dependent axis values changed for the next update. + /// + private bool _queueUpdateDataItemPlacement_DependentAxisValuesChanged; + + /// + /// Tracks whether the independent axis values changed for the next update. + /// + private bool _queueUpdateDataItemPlacement_IndependentAxisValuesChanged; + + /// + /// Stores a reference to the backing collection for the SelectedItems property. + /// + private ObservableCollection _selectedItems = new ObservableCollection(); + + /// + /// Tracks whether the SelectedItems collection is being synchronized (to prevent reentrancy). + /// + private bool _synchronizingSelectedItems; + +#if !SILVERLIGHT + /// + /// Performs one-time initialization of DefinitionSeries data. + /// + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Dependency properties are initialized in-line.")] + static DefinitionSeries() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(DefinitionSeries), new FrameworkPropertyMetadata(typeof(DefinitionSeries))); + } +#endif + + /// + /// Initializes a new instance of the DefinitionSeries class. + /// + 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(); + } + + /// + /// Gets or sets the dependent axis of the series. + /// + public IAxis DependentAxis + { + get { return (IAxis)GetValue(DependentAxisProperty); } + set { SetValue(DependentAxisProperty, value); } + } + + /// + /// Identifies the DependentAxis dependency property. + /// + public static readonly DependencyProperty DependentAxisProperty = + DependencyProperty.Register("DependentAxis", typeof(IAxis), typeof(DefinitionSeries), new PropertyMetadata(OnDependentAxisChanged)); + + /// + /// Handles changes to the DependentAxis dependency property. + /// + /// DependencyObject that changed. + /// Event data for the DependencyPropertyChangedEvent. + private static void OnDependentAxisChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + ((DefinitionSeries)o).OnDependentAxisChanged((IAxis)e.OldValue, (IAxis)e.NewValue); + } + + /// + /// Handles changes to the DependentAxis property. + /// + /// Old value. + /// New value. + [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); + } + } + + /// + /// Gets or sets the independent axis of the series. + /// + public IAxis IndependentAxis + { + get { return (IAxis)GetValue(IndependentAxisProperty); } + set { SetValue(IndependentAxisProperty, value); } + } + + /// + /// Identifies the IndependentAxis dependency property. + /// + public static readonly DependencyProperty IndependentAxisProperty = + DependencyProperty.Register("IndependentAxis", typeof(IAxis), typeof(DefinitionSeries), new PropertyMetadata(OnIndependentAxisChanged)); + + /// + /// Handles changes to the IndependentAxis dependency property. + /// + /// DependencyObject that changed. + /// Event data for the DependencyPropertyChangedEvent. + private static void OnIndependentAxisChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + ((DefinitionSeries)o).OnIndependentAxisChanged((IAxis)e.OldValue, (IAxis)e.NewValue); + } + + /// + /// Handles changes to the IndependentAxis property. + /// + /// Old value. + /// New value. + [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); + } + } + + /// + /// Gets the rendered dependent axis of the series. + /// + public IAxis ActualDependentAxis + { + get { return (IAxis)GetValue(ActualDependentAxisProperty); } + protected set { SetValue(ActualDependentAxisProperty, value); } + } + + /// + /// Identifies the ActualDependentAxis dependency property. + /// + public static readonly DependencyProperty ActualDependentAxisProperty = + DependencyProperty.Register("ActualDependentAxis", typeof(IAxis), typeof(DefinitionSeries), null); + + /// + /// Gets the rendered independent axis of the series. + /// + public IAxis ActualIndependentAxis + { + get { return (IAxis)GetValue(ActualIndependentAxisProperty); } + protected set { SetValue(ActualIndependentAxisProperty, value); } + } + + /// + /// Identifies the ActualIndependentAxis dependency property. + /// + public static readonly DependencyProperty ActualIndependentAxisProperty = + DependencyProperty.Register("ActualIndependentAxis", typeof(IAxis), typeof(DefinitionSeries), null); + + /// + /// Gets the ActualDependentAxis as an IRangeAxis instance. + /// + protected IRangeAxis ActualDependentRangeAxis + { + get { return (IRangeAxis)ActualDependentAxis; } + } + + /// + /// Gets the collection of legend items for the series. + /// + public ObservableCollection LegendItems + { + get { return _legendItems; } + } + + /// + /// Gets or sets the SeriesHost for the series. + /// + 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); + } + } + } + } + + /// + /// Stores the SeriesHost for the series. + /// + private ISeriesHost _seriesHost; + + /// + /// Gets or sets the collection of SeriesDefinitions that define the series. + /// + [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 SeriesDefinitions + { + get { return _seriesDefinitions; } + set { throw new NotSupportedException(Properties.Resources.DefinitionSeries_SeriesDefinitions_SetterNotSupported); } + } + + /// + /// Gets or sets the SelectionMode property. + /// + public SeriesSelectionMode SelectionMode + { + get { return (SeriesSelectionMode)GetValue(SelectionModeProperty); } + set { SetValue(SelectionModeProperty, value); } + } + + /// + /// Identifies the SelectionMode dependency property. + /// + public static readonly DependencyProperty SelectionModeProperty = + DependencyProperty.Register("SelectionMode", typeof(SeriesSelectionMode), typeof(DefinitionSeries), new PropertyMetadata(SeriesSelectionMode.None, OnSelectionModeChanged)); + + /// + /// Handles changes to the SelectionMode dependency property. + /// + /// DependencyObject that changed. + /// Event data for the DependencyPropertyChangedEvent. + private static void OnSelectionModeChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + ((DefinitionSeries)o).OnSelectionModeChanged((SeriesSelectionMode)e.OldValue, (SeriesSelectionMode)e.NewValue); + } + + /// + /// Handles changes to the SelectionMode property. + /// + /// Old value. + /// New value. + [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; + } + } + } + + /// + /// Gets or sets the SelectedIndex property. + /// + public int SelectedIndex + { + get { return (int)GetValue(SelectedIndexProperty); } + set { SetValue(SelectedIndexProperty, value); } + } + + /// + /// Identifies the SelectedIndex dependency property. + /// + public static readonly DependencyProperty SelectedIndexProperty = + DependencyProperty.Register("SelectedIndex", typeof(int), typeof(DefinitionSeries), new PropertyMetadata(-1)); + + /// + /// Gets or sets the SelectedItem property. + /// + public object SelectedItem + { + get { return (object)GetValue(SelectedItemProperty); } + set { SetValue(SelectedItemProperty, value); } + } + + /// + /// Identifies the SelectedItem dependency property. + /// + public static readonly DependencyProperty SelectedItemProperty = + DependencyProperty.Register("SelectedItem", typeof(object), typeof(DefinitionSeries), null); + + /// + /// Gets the currently selected items. + /// + /// + /// This property is meant to be used when SelectionMode is Multiple. If the selection mode is Single the correct property to use is SelectedItem. + /// + public IList SelectedItems + { + get { return _selectedItems; } + } + + /// + /// Handles the CollectionChanged event of the SelectedItems collection. + /// + /// Event source. + /// Event arguments. + 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().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().SelectMany(v => DataItems.Where(di => object.Equals(di.Value, v)))) + { + _itemContainer.SelectedItems.Add(dataItem); + } + } + } + } + } + finally + { + _synchronizingSelectedItems = false; + } + } + } + + /// + /// Handles the SelectionChanged event of the ItemContainer class. + /// + /// Event source. + /// Event arguments. + private void ItemContainerSelectionChanged(object sender, SelectionChangedEventArgs e) + { + DataItem[] removedDataItems = e.RemovedItems.CastWrapper().ToArray(); + DataItem[] addedDataItems = e.AddedItems.CastWrapper().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 + } + + /// + /// Occurs when the selection of a DefinitionSeries changes. + /// +#if SILVERLIGHT + public event SelectionChangedEventHandler SelectionChanged; +#else + public event SelectionChangedEventHandler SelectionChanged + { + add { AddHandler(SelectionChangedEvent, value); } + remove { RemoveHandler(SelectionChangedEvent, value); } + } + + /// + /// Identifies the SelectionChanged routed event. + /// + public static readonly RoutedEvent SelectionChangedEvent = + EventManager.RegisterRoutedEvent("SelectionChanged", RoutingStrategy.Bubble, typeof(SelectionChangedEventHandler), typeof(DefinitionSeries)); +#endif + + /// + /// Builds the visual tree for the control when a new template is applied. + /// + 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)); + } + + /// + /// Prepares the specified element to display the specified item. + /// + /// The element used to display the specified item. + /// The item to display. + 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(DataPointDependentValueChanged); + dataPoint.ActualDependentValueChanged += new RoutedPropertyChangedEventHandler(DataPointActualDependentValueChanged); + dataPoint.IndependentValueChanged += new RoutedPropertyChangedEventHandler(DataPointIndependentValueChanged); + dataPoint.ActualIndependentValueChanged += new RoutedPropertyChangedEventHandler(DataPointActualIndependentValueChanged); + dataPoint.StateChanged += new RoutedPropertyChangedEventHandler(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; + } + + /// + /// Undoes the effects of the PrepareContainerForItemOverride method. + /// + /// The container element. + /// The item to display. + private void ClearContainerForItem(DependencyObject element, object item) + { + DataItem dataItem = (DataItem)item; + DataPoint dataPoint = dataItem.DataPoint; + dataPoint.DependentValueChanged -= new RoutedPropertyChangedEventHandler(DataPointDependentValueChanged); + dataPoint.ActualDependentValueChanged -= new RoutedPropertyChangedEventHandler(DataPointActualDependentValueChanged); + dataPoint.IndependentValueChanged -= new RoutedPropertyChangedEventHandler(DataPointIndependentValueChanged); + dataPoint.ActualIndependentValueChanged -= new RoutedPropertyChangedEventHandler(DataPointActualIndependentValueChanged); + dataPoint.StateChanged -= new RoutedPropertyChangedEventHandler(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; + } + + /// + /// Prepares a DataPoint for use. + /// + /// DataPoint instance. + protected virtual void PrepareDataPoint(DataPoint dataPoint) { } + + /// + /// Creates a DataPoint for the series. + /// + /// Series-appropriate DataPoint instance. + protected abstract DataPoint CreateDataPoint(); + + /// + /// Provides an internally-accessible wrapper for calling CreateDataPoint. + /// + /// Series-appropriate DataPoint instance. + internal DataPoint InternalCreateDataPoint() + { + return CreateDataPoint(); + } + + /// + /// Handles the SizeChanged event of the ItemContainer. + /// + /// Event source. + /// Event arguments. + private void ItemContainerSizeChanged(object sender, SizeChangedEventArgs e) + { + _clipGeometry.Rect = new Rect(0, 0, e.NewSize.Width, e.NewSize.Height); + QueueUpdateDataItemPlacement(false, false, DataItems); + } + + /// + /// Returns the DataItem corresponding to the specified DataPoint. + /// + /// Specified DataPoint. + /// Corresponding DataItem. + protected DataItem DataItemFromDataPoint(DataPoint dataPoint) + { + return DataItems.Where(di => di.DataPoint == dataPoint).Single(); + } + + /// + /// Handles the DependentValueChanged event of a DataPoint. + /// + /// Event source. + /// Event arguments. + private void DataPointDependentValueChanged(object sender, RoutedPropertyChangedEventArgs 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; + } + } + + /// + /// Handles the ActualDependentValueChanged event of a DataPoint. + /// + /// Event source. + /// Event arguments. + private void DataPointActualDependentValueChanged(object sender, RoutedPropertyChangedEventArgs e) + { + DataPoint dataPoint = (DataPoint)sender; + QueueUpdateDataItemPlacement(true, false, DataItems.Where(di => di.DataPoint == dataPoint)); + } + + /// + /// Handles the IndependentValueChanged event of a DataPoint. + /// + /// Event source. + /// Event arguments. + private void DataPointIndependentValueChanged(object sender, RoutedPropertyChangedEventArgs 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; + } + } + + /// + /// Handles the ActualIndependentValueChanged event of a DataPoint. + /// + /// Event source. + /// Event arguments. + private void DataPointActualIndependentValueChanged(object sender, RoutedPropertyChangedEventArgs e) + { + DataPoint dataPoint = (DataPoint)sender; + QueueUpdateDataItemPlacement(false, true, DataItems.Where(di => di.DataPoint == dataPoint)); + } + + /// + /// Handles the StateChanged event of a DataPoint. + /// + /// Event source. + /// Event arguments. + private void DataPointStateChanged(object sender, RoutedPropertyChangedEventArgs e) + { + DataPoint dataPoint = (DataPoint)sender; + if (DataPointState.Hidden == dataPoint.State) + { + DataItems.Remove(DataItems.Where(di => di.DataPoint == dataPoint).Single()); + RemovedDataItems(); + } + } + + /// + /// Notifies the specified axis of changes to values plotting against it. + /// + /// Specified axis. + 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() /*rangeProvider.GetRange(rangeConsumer)*/); + } + IDataConsumer dataConsumer = axis as IDataConsumer; + if (null != dataConsumer) + { + IDataProvider dataProvider = (IDataProvider)this; + dataConsumer.DataChanged(dataProvider, null /*dataProvider.GetData(dataConsumer)*/); + } + } + } + + /// + /// Notifies the specified axis of changes to value margins plotting against it. + /// + /// Specified axis. + /// Sequence of value margins that have changed. + protected void NotifyValueMarginsChanged(IAxis axis, IEnumerable valueMargins) + { + if (null != axis) + { + IValueMarginConsumer valueMarginConsumer = axis as IValueMarginConsumer; + if (null != valueMarginConsumer) + { + IValueMarginProvider valueMarginProvider = (IValueMarginProvider)this; + valueMarginConsumer.ValueMarginsChanged(valueMarginProvider, valueMargins); + } + } + } + + /// + /// Handles the CollectionChanged event of the SeriesDefinitions collection. + /// + /// Event source. + /// Event arguments. + private void SeriesDefinitionsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + SeriesDefinitionsCollectionChanged(e.Action, e.OldItems, e.OldStartingIndex, e.NewItems, e.NewStartingIndex); + } + + /// + /// Handles the CollectionChanged event of the SeriesDefinitions collection. + /// + /// Type of change. + /// Sequence of old items. + /// Starting index of old items. + /// Sequence of new items. + /// Starting index of new items. + protected virtual void SeriesDefinitionsCollectionChanged(NotifyCollectionChangedAction action, IList oldItems, int oldStartingIndex, IList newItems, int newStartingIndex) + { + if (null != oldItems) + { + foreach (SeriesDefinition oldDefinition in oldItems.CastWrapper()) + { + 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()) + { + 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++; + } + } + } + + /// + /// Updates the palette properties of the specified SeriesDefinition. + /// + /// Specified SeriesDefinition. + private void UpdatePaletteProperties(SeriesDefinition definition) + { + ResourceDictionary resources = null; + if (null != SeriesHost) + { + Type dataPointType = CreateDataPoint().GetType(); + using (IEnumerator 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; + } + + /// + /// Handles changes to the ItemsSource of a SeriesDefinition. + /// + /// SeriesDefinition owner. + /// Old value. + /// New value. + 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(), 0); + } + } + } + + /// + /// Handles changes to the ItemsSource collection of a SeriesDefinition. + /// + /// SeriesDefinition owner. + /// Type of change. + /// Sequence of old items. + /// Starting index of old items. + /// Sequence of new items. + /// Starting index of new items. + [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().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(), 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().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 + } + + /// + /// Handles the ResourceDictionariesChanged event of the SeriesHost owner. + /// + /// Event source. + /// Event arguments. + private void SeriesHostResourceDictionariesChanged(object sender, EventArgs e) + { + foreach (SeriesDefinition definition in SeriesDefinitions) + { + UpdatePaletteProperties(definition); + } + } + + /// + /// Creates and adds DataItems for the specified SeriesDefinition's items. + /// + /// Specified SeriesDefinition. + /// Sequence of items. + /// Starting index. + private void AddDataItems(SeriesDefinition definition, IEnumerable 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); + } + + /// + /// Updates the axes after DataItems have been added. + /// + private void AddedDataItems() + { + EnsureAxes(false, false, true); + } + + /// + /// Notifies the axes after DataItems have been removed. + /// + private void RemovedDataItems() + { + NotifyAxisValuesChanged(ActualIndependentAxis); + NotifyAxisValuesChanged(ActualDependentAxis); + } + + /// + /// Ensures that suitable axes are present and registered. + /// + /// True if the dependent axis needs to be updated. + /// True if the independent axis needs to be updated. + /// True if both axis are to be notified unconditionally. + 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); + } + } + } + + /// + /// Acquires a dependent axis suitable for use with the data values of the series. + /// + /// Axis instance. + protected abstract IAxis AcquireDependentAxis(); + + /// + /// Acquires an independent axis suitable for use with the data values of the series. + /// + /// Axis instance. + protected abstract IAxis AcquireIndependentAxis(); + + /// + /// Handles notification of the invalidation of an axis. + /// + /// Invalidated axis. + void IAxisListener.AxisInvalidated(IAxis axis) + { + QueueUpdateDataItemPlacement(false, false, DataItems); + } + + /// + /// Queues an update of DataItem placement for the next update opportunity. + /// + /// True if the dependent axis values have changed. + /// True if the independent axis values have changed. + /// Sequence of DataItems to update. + private void QueueUpdateDataItemPlacement(bool dependentAxisValuesChanged, bool independentAxisValuesChanged, IEnumerable dataItems) + { + _queueUpdateDataItemPlacement_DependentAxisValuesChanged |= dependentAxisValuesChanged; + _queueUpdateDataItemPlacement_IndependentAxisValuesChanged |= independentAxisValuesChanged; + _queueUpdateDataItemPlacement_DataItems.AddRange(dataItems); + InvalidateArrange(); + } + + /// + /// Called when the control needs to arrange its children. + /// + /// Bounds to arrange within. + /// Arranged size. + /// + /// Used as a good place to dequeue queued work. + /// + 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; + } + + /// + /// Updates the placement of the DataItems (data points) of the series. + /// + /// DataItems in need of an update. + protected abstract void UpdateDataItemPlacement(IEnumerable dataItems); + + /// + /// Returns the range for the data points of the series. + /// + /// Consumer of the range. + /// Range of values. + Range IRangeProvider.GetRange(IRangeConsumer rangeConsumer) + { + return IRangeProviderGetRange(rangeConsumer); + } + + /// + /// Returns the range for the data points of the series. + /// + /// Consumer of the range. + /// Range of values. + protected virtual Range 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() + .GetRange(); + } + else + { + return IndependentValueGroups + .Select(g => ValueHelper.ToDateTime(g.IndependentValue)) + .DefaultIfEmpty() + .CastWrapper() + .GetRange(); + } + } + throw new NotSupportedException(); + } + + /// + /// Returns the value margins for the data points of the series. + /// + /// Consumer of the value margins. + /// Sequence of value margins. + IEnumerable IValueMarginProvider.GetValueMargins(IValueMarginConsumer valueMarginConsumer) + { + return IValueMarginProviderGetValueMargins(valueMarginConsumer); + } + + /// + /// Returns the value margins for the data points of the series. + /// + /// Consumer of the value margins. + /// Sequence of value margins. + protected virtual IEnumerable IValueMarginProviderGetValueMargins(IValueMarginConsumer valueMarginConsumer) + { + throw new NotImplementedException(); + } + + /// + /// Returns the data for the data points of the series. + /// + /// Consumer of the data. + /// Sequence of data. + IEnumerable IDataProvider.GetData(IDataConsumer dataConsumer) + { + return IDataProviderGetData(dataConsumer); + } + + /// + /// Returns the data for the data points of the series. + /// + /// Consumer of the data. + /// Sequence of data. + protected virtual IEnumerable IDataProviderGetData(IDataConsumer dataConsumer) + { + if (dataConsumer == ActualIndependentAxis) + { + return IndependentValueGroups.Select(cg => cg.IndependentValue).Distinct(); + } + throw new NotImplementedException(); + } + + /// + /// Gets a sequence of IndependentValueGroups. + /// + protected virtual IEnumerable IndependentValueGroups + { + get + { + return DataItems + .GroupBy(di => di.ActualIndependentValue) + .Select(g => new IndependentValueGroup(g.Key, g.OrderBy(di => di.SeriesDefinition.Index))); + } + } + + /// + /// Gets a sequence of IndependentValueGroups ordered by independent value. + /// + protected IEnumerable IndependentValueGroupsOrderedByIndependentValue + { + get + { + return IndependentValueGroups + .OrderBy(g => g.IndependentValue); + } + } + + /// + /// Gets a sequence of sequences of the dependent values associated with each independent value. + /// + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nesting reflects the actual hierarchy of the data.")] + protected IEnumerable> 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))); + } + } + + /// + /// Represents an independent value and the dependent values that are associated with it. + /// + protected class IndependentValueGroup + { + /// + /// Initializes a new instance of the IndependentValueGroup class. + /// + /// Independent value. + /// Associated DataItems. + public IndependentValueGroup(object independentValue, IEnumerable dataItems) + { + IndependentValue = independentValue; + DataItems = dataItems; + } + + /// + /// Gets the independent value. + /// + public object IndependentValue { get; private set; } + + /// + /// Gets a sequence of DataItems associated with the independent value. + /// + public IEnumerable DataItems { get; private set; } + + /// + /// Gets or sets the denominator to use when computing with this instance. + /// + /// + /// Exists here purely to simplify the the corresponding algorithm. + /// + public double Denominator { get; set; } + } + + /// + /// Represents a single data value from a SeriesDefinition's ItemsSource. + /// + protected class DataItem + { + /// + /// Stores a reference to a shared BindingHelper instance. + /// + private static readonly BindingHelper _bindingHelper = new BindingHelper(); + + /// + /// Initializes a new instance of the DataItem class. + /// + /// SeriesDefinition owner. + public DataItem(SeriesDefinition seriesDefinition) + { + SeriesDefinition = seriesDefinition; + CenterPoint = new Point(double.NaN, double.NaN); + } + + /// + /// Gets the SeriesDefinition owner of the DataItem. + /// + public SeriesDefinition SeriesDefinition { get; private set; } + + /// + /// Gets or sets the value of the DataItem. + /// + public object Value + { + get { return _value; } + set + { + _value = value; + if (null != DataPoint) + { + DataPoint.DataContext = value; + } + } + } + + /// + /// Stores the value of the DataItem. + /// + private object _value; + + /// + /// Gets or sets the index of the DataItem. + /// + public int Index { get; set; } + + /// + /// Gets or sets the DataPoint associated with the DataItem. + /// + public DataPoint DataPoint { get; set; } + + /// + /// Gets or sets the container for the DataPoint within its parent ItemsControl. + /// + public UIElement Container { get; set; } + + /// + /// Gets the ActualDependentValue of the DataPoint (or its equivalent). + /// + public IComparable ActualDependentValue + { + get + { + if (null != DataPoint) + { + return DataPoint.ActualDependentValue; + } + else + { + return (IComparable)_bindingHelper.EvaluateBinding(SeriesDefinition.DependentValueBinding, Value); + } + } + } + + /// + /// Gets the ActualIndependentValue of the DataPoint (or its equivalent). + /// + public object ActualIndependentValue + { + get + { + if (null != DataPoint) + { + return DataPoint.ActualIndependentValue; + } + else + { + return _bindingHelper.EvaluateBinding(SeriesDefinition.IndependentValueBinding, Value); + } + } + } + + /// + /// Gets or sets the ActualDependentValue of the DataPoint after adjusting for applicable stacking. + /// + public double ActualStackedDependentValue { get; set; } + + /// + /// Gets or sets the center-point of the DataPoint in plot area coordinates (if relevant). + /// + public Point CenterPoint { get; set; } + } + + /// + /// Provides an easy way to evaluate a Binding against a source instance. + /// + private class BindingHelper : FrameworkElement + { + /// + /// Initializes a new instance of the BindingHelper class. + /// + public BindingHelper() + { + } + + /// + /// Identifies the Result dependency property. + /// + private static readonly DependencyProperty ResultProperty = + DependencyProperty.Register("Result", typeof(object), typeof(BindingHelper), null); + + /// + /// Evaluates a Binding against a source instance. + /// + /// Binding to evaluate. + /// Source instance. + /// Result of Binding on source instance. + public object EvaluateBinding(Binding binding, object instance) + { + DataContext = instance; + SetBinding(ResultProperty, binding); + object result = GetValue(ResultProperty); + ClearValue(ResultProperty); + DataContext = null; + return result; + } + } + + /// + /// Converts from a selected item to the corresponding DataItem. + /// + private class SelectedItemToDataItemConverter : IValueConverter + { + /// + /// Stores a reference to the DataItem collection. + /// + private ObservableCollection _dataItems; + + /// + /// Initializes a new instance of the SelectedItemToDataItemConverter class. + /// + /// Collection of DataItems. + public SelectedItemToDataItemConverter(ObservableCollection dataItems) + { + _dataItems = dataItems; + } + + /// + /// Converts a value. + /// + /// The value produced by the binding source. + /// The type of the binding target property. + /// The converter parameter to use. + /// The culture to use in the converter. + /// Converted value. + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return _dataItems.Where(di => di.Value == value).FirstOrDefault(); + } + + /// + /// Converts a value back. + /// + /// The value produced by the binding source. + /// The type of the binding target property. + /// The converter parameter to use. + /// The culture to use in the converter. + /// Converted value. + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + DataItem dataItem = value as DataItem; + return (null != dataItem) ? dataItem.Value : null; + } + } + + /// + /// Converts from a SeriesSelectionMode to a true/false value indicating whether selection is enabled. + /// + private class SelectionModeToSelectionEnabledConverter : IValueConverter + { + /// + /// Initializes a new instance of the SelectionModeToSelectionEnabledConverter class. + /// + public SelectionModeToSelectionEnabledConverter() + { + } + + /// + /// Converts a value. + /// + /// The value produced by the binding source. + /// The type of the binding target property. + /// The converter parameter to use. + /// The culture to use in the converter. + /// Converted value. + 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; + } + + /// + /// Converts a value back. + /// + /// The value produced by the binding source. + /// The type of the binding target property. + /// The converter parameter to use. + /// The culture to use in the converter. + /// Converted value. + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } + + /// + /// Gets the axes for the series as a series host. + /// + [SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations", Justification = "Property exists as an interface requirement; implementation is unnecessary.")] + ObservableCollection ISeriesHost.Axes + { + get { throw new NotImplementedException(); } + } + + /// + /// Gets the series for the series as a series host. + /// + ObservableCollection ISeriesHost.Series + { + get { return _seriesDefinitionsAsISeries; } + } + + /// + /// Gets the foreground elements for the series as a series host. + /// + [SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations", Justification = "Property exists as an interface requirement; implementation is unnecessary.")] + ObservableCollection ISeriesHost.ForegroundElements + { + get { throw new NotImplementedException(); } + } + + /// + /// Gets the background elements for the series as a series host. + /// + [SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations", Justification = "Property exists as an interface requirement; implementation is unnecessary.")] + ObservableCollection ISeriesHost.BackgroundElements + { + get { throw new NotImplementedException(); } + } + + /// + /// Gets a IResourceDictionaryDispenser for the series as a series host. + /// + /// Predicate function. + /// Sequence of ResourceDictionaries. + IEnumerator IResourceDictionaryDispenser.GetResourceDictionariesWhere(Func predicate) + { + throw new NotImplementedException(); + } + + /// + /// Event that is triggered when the available ResourceDictionaries change. + /// + [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(); } + } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/IRequireGlobalSeriesIndex.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/IRequireGlobalSeriesIndex.cs new file mode 100644 index 00000000..337d8e56 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/IRequireGlobalSeriesIndex.cs @@ -0,0 +1,21 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Defines methods on classes that contain a global index. + /// + /// Preview + public interface IRequireGlobalSeriesIndex + { + /// + /// Occurs when a global series index changes. + /// + /// The global index that has changed. + /// + void GlobalSeriesIndexChanged(int? globalIndex); + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/ISeries.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/ISeries.cs new file mode 100644 index 00000000..0c614aa0 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/ISeries.cs @@ -0,0 +1,20 @@ +// (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.ObjectModel; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Represents a series in a chart. + /// + public interface ISeries : IRequireSeriesHost + { + /// + /// Gets a list of the legend items associated with the object. + /// + ObservableCollection LegendItems { get; } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/LegendItem.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/LegendItem.cs new file mode 100644 index 00000000..5d2fdd60 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/LegendItem.cs @@ -0,0 +1,39 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Represents an item used by a Series in the Legend of a Chart. + /// + /// Preview + public class LegendItem : ContentControl + { + /// + /// Gets or sets the owner of the LegendItem. + /// + public object Owner { get; set; } + +#if !SILVERLIGHT + /// + /// Initializes the static members of the LegendItem class. + /// + static LegendItem() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(LegendItem), new FrameworkPropertyMetadata(typeof(LegendItem))); + } + +#endif + /// + /// Initializes a new instance of the LegendItem class. + /// + public LegendItem() + { +#if SILVERLIGHT + this.DefaultStyleKey = typeof(LegendItem); +#endif + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/LineAreaBaseSeries.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/LineAreaBaseSeries.cs new file mode 100644 index 00000000..e2566351 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/LineAreaBaseSeries.cs @@ -0,0 +1,306 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Windows.Controls.DataVisualization.Collections; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// A base class that contains methods used by both the line and area series. + /// + /// The type of data point used by the series. + public abstract class LineAreaBaseSeries : DataPointSingleSeriesWithAxes + where T : DataPoint, new() + { + #region public IRangeAxis DependentRangeAxis + /// + /// Gets or sets the dependent range axis. + /// + public IRangeAxis DependentRangeAxis + { + get { return GetValue(DependentRangeAxisProperty) as IRangeAxis; } + set { SetValue(DependentRangeAxisProperty, value); } + } + + /// + /// Identifies the DependentRangeAxis dependency property. + /// + [SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", Justification = "This member is necessary because child classes need to share this dependency property.")] + public static readonly DependencyProperty DependentRangeAxisProperty = + DependencyProperty.Register( + "DependentRangeAxis", + typeof(IRangeAxis), + typeof(LineAreaBaseSeries), + new PropertyMetadata(null, OnDependentRangeAxisPropertyChanged)); + + /// + /// DependentRangeAxisProperty property changed handler. + /// + /// LineAreaBaseSeries that changed its DependentRangeAxis. + /// Event arguments. + private static void OnDependentRangeAxisPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + LineAreaBaseSeries source = (LineAreaBaseSeries)d; + IRangeAxis newValue = (IRangeAxis)e.NewValue; + source.OnDependentRangeAxisPropertyChanged(newValue); + } + + /// + /// DependentRangeAxisProperty property changed handler. + /// + /// New value. + private void OnDependentRangeAxisPropertyChanged(IRangeAxis newValue) + { + this.InternalDependentAxis = (IAxis)newValue; + } + #endregion public IRangeAxis DependentRangeAxis + + #region public IAxis IndependentAxis + /// + /// Gets or sets the independent range axis. + /// + public IAxis IndependentAxis + { + get { return GetValue(IndependentAxisProperty) as IAxis; } + set { SetValue(IndependentAxisProperty, value); } + } + + /// + /// Identifies the IndependentAxis dependency property. + /// + [SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", Justification = "This member is necessary because child classes need to share this dependency property.")] + public static readonly DependencyProperty IndependentAxisProperty = + DependencyProperty.Register( + "IndependentAxis", + typeof(IAxis), + typeof(LineAreaBaseSeries), + new PropertyMetadata(null, OnIndependentAxisPropertyChanged)); + + /// + /// IndependentAxisProperty property changed handler. + /// + /// LineAreaBaseSeries that changed its IndependentAxis. + /// Event arguments. + private static void OnIndependentAxisPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + LineAreaBaseSeries source = (LineAreaBaseSeries)d; + IAxis newValue = (IAxis)e.NewValue; + source.OnIndependentAxisPropertyChanged(newValue); + } + + /// + /// IndependentAxisProperty property changed handler. + /// + /// New value. + private void OnIndependentAxisPropertyChanged(IAxis newValue) + { + this.InternalIndependentAxis = (IAxis)newValue; + } + #endregion public IAxis IndependentAxis + + /// + /// Gets data points collection sorted by independent value. + /// + internal OrderedMultipleDictionary DataPointsByIndependentValue { get; private set; } + + /// + /// Gets the independent axis as a range axis. + /// + public IAxis ActualIndependentAxis { get { return this.InternalActualIndependentAxis as IAxis; } } + + /// + /// Gets the dependent axis as a range axis. + /// + public IRangeAxis ActualDependentRangeAxis { get { return this.InternalActualDependentAxis as IRangeAxis; } } + + /// + /// Initializes a new instance of the LineAreaBaseSeries class. + /// + protected LineAreaBaseSeries() + { + DataPointsByIndependentValue = + new OrderedMultipleDictionary( + false, + (left, right) => + left.CompareTo(right), + (leftDataPoint, rightDataPoint) => + RuntimeHelpers.GetHashCode(leftDataPoint).CompareTo(RuntimeHelpers.GetHashCode(rightDataPoint))); + } + + /// + /// Creates a DataPoint for determining the line color. + /// + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + if (null != PlotArea) + { + Grid grid = new Grid(); + DataPoint dataPoint = CreateDataPoint(); + dataPoint.Visibility = Visibility.Collapsed; + dataPoint.Loaded += delegate + { + dataPoint.SetStyle(ActualDataPointStyle); + Background = dataPoint.Background; + if (null != PlotArea) + { + PlotArea.Children.Remove(grid); + } + }; + grid.Children.Add(dataPoint); + PlotArea.Children.Add(grid); + } + } + + /// + /// Called after data points have been loaded from the items source. + /// + /// New active data points. + /// Old inactive data points. + protected override void OnDataPointsChanged(IList newDataPoints, IList oldDataPoints) + { + base.OnDataPointsChanged(newDataPoints, oldDataPoints); + + if (ActualIndependentAxis is IRangeAxis) + { + foreach (DataPoint dataPoint in oldDataPoints) + { + DataPointsByIndependentValue.Remove((IComparable)dataPoint.IndependentValue, dataPoint); + } + + foreach (DataPoint dataPoint in newDataPoints) + { + DataPointsByIndependentValue.Add((IComparable)dataPoint.IndependentValue, dataPoint); + } + } + } + + /// + /// This method executes after all data points have been updated. + /// + protected override void OnAfterUpdateDataPoints() + { + if (InternalActualDependentAxis != null && InternalActualIndependentAxis != null) + { + UpdateShape(); + } + } + + /// + /// Repositions line data point in the sorted collection if the actual + /// independent axis is a range axis. + /// + /// The data point that has changed. + /// The old value. + /// The new value. + protected override void OnDataPointIndependentValueChanged(DataPoint dataPoint, object oldValue, object newValue) + { + if (ActualIndependentAxis is IRangeAxis && !oldValue.Equals(newValue)) + { + bool removed = DataPointsByIndependentValue.Remove((IComparable)oldValue, dataPoint); + if (removed) + { + DataPointsByIndependentValue.Add((IComparable)newValue, dataPoint); + } + } + + base.OnDataPointIndependentValueChanged(dataPoint, oldValue, newValue); + } + + /// + /// Creates a new line data point. + /// + /// A line data point. + protected override DataPoint CreateDataPoint() + { + return new T(); + } + + /// + /// Returns the custom ResourceDictionary to use for necessary resources. + /// + /// + /// ResourceDictionary to use for necessary resources. + /// + protected override IEnumerator GetResourceDictionaryEnumeratorFromHost() + { + return GetResourceDictionaryWithTargetType(SeriesHost, typeof(T), true); + } + + /// + /// Updates the visual representation of the data point. + /// + /// The data point to update. + protected override void UpdateDataPoint(DataPoint dataPoint) + { + double maximum = ActualDependentRangeAxis.GetPlotAreaCoordinate(ActualDependentRangeAxis.Range.Maximum).Value; + if (ValueHelper.CanGraph(maximum)) + { + double x = ActualIndependentAxis.GetPlotAreaCoordinate(dataPoint.ActualIndependentValue).Value; + double y = ActualDependentRangeAxis.GetPlotAreaCoordinate(dataPoint.ActualDependentValue).Value; + + if (ValueHelper.CanGraph(x) && ValueHelper.CanGraph(y)) + { + dataPoint.Visibility = Visibility.Visible; + + double coordinateY = Math.Round(maximum - (y + (dataPoint.ActualHeight / 2))); + Canvas.SetTop(dataPoint, coordinateY); + double coordinateX = Math.Round(x - (dataPoint.ActualWidth / 2)); + Canvas.SetLeft(dataPoint, coordinateX); + } + else + { + dataPoint.Visibility = Visibility.Collapsed; + } + } + + if (!UpdatingDataPoints) + { + UpdateShape(); + } + } + + /// + /// Updates the Series shape object. + /// + protected virtual void UpdateShape() + { + double maximum = ActualDependentRangeAxis.GetPlotAreaCoordinate(ActualDependentRangeAxis.Range.Maximum).Value; + + Func createPoint = + dataPoint => + new Point( + ActualIndependentAxis.GetPlotAreaCoordinate(dataPoint.ActualIndependentValue).Value, + maximum - ActualDependentRangeAxis.GetPlotAreaCoordinate(dataPoint.ActualDependentValue).Value); + + IEnumerable points = Enumerable.Empty(); + if (ValueHelper.CanGraph(maximum)) + { + if (ActualIndependentAxis is IRangeAxis) + { + points = DataPointsByIndependentValue.Select(createPoint); + } + else + { + points = + ActiveDataPoints + .Select(createPoint) + .OrderBy(point => point.X); + } + } + UpdateShapeFromPoints(points); + } + + /// + /// Updates the Series shape object from a collection of Points. + /// + /// Collection of Points. + protected abstract void UpdateShapeFromPoints(IEnumerable points); + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/LineSeries.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/LineSeries.cs new file mode 100644 index 00000000..dfc13b23 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/LineSeries.cs @@ -0,0 +1,150 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Windows.Media; +using System.Windows.Shapes; + +#if !DEFINITION_SERIES_COMPATIBILITY_MODE + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Represents a control that contains a data series to be rendered in X/Y + /// line format. + /// + /// Preview + [StyleTypedProperty(Property = DataPointStyleName, StyleTargetType = typeof(LineDataPoint))] + [StyleTypedProperty(Property = "LegendItemStyle", StyleTargetType = typeof(LegendItem))] + [StyleTypedProperty(Property = "PolylineStyle", StyleTargetType = typeof(Polyline))] + [TemplatePart(Name = DataPointSeries.PlotAreaName, Type = typeof(Canvas))] + [SuppressMessage("Microsoft.Maintainability", "CA1501:AvoidExcessiveInheritance", Justification = "Depth of hierarchy is necessary to avoid code duplication.")] + public partial class LineSeries : LineAreaBaseSeries + { + #region public PointCollection Points + /// + /// Gets the collection of points that make up the line. + /// + public PointCollection Points + { + get { return GetValue(PointsProperty) as PointCollection; } + private set { SetValue(PointsProperty, value); } + } + + /// + /// Identifies the Points dependency property. + /// + public static readonly DependencyProperty PointsProperty = + DependencyProperty.Register( + "Points", + typeof(PointCollection), + typeof(LineSeries), + null); + #endregion public PointCollection Points + + #region public Style PolylineStyle + /// + /// Gets or sets the style of the Polyline object that follows the data + /// points. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Polyline", Justification = "Matches System.Windows.Shapes.Polyline.")] + public Style PolylineStyle + { + get { return GetValue(PolylineStyleProperty) as Style; } + set { SetValue(PolylineStyleProperty, value); } + } + + /// + /// Identifies the PolylineStyle dependency property. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Polyline", Justification = "Matches System.Windows.Shapes.Polyline.")] + public static readonly DependencyProperty PolylineStyleProperty = + DependencyProperty.Register( + "PolylineStyle", + typeof(Style), + typeof(LineSeries), + null); + #endregion public Style PolylineStyle + +#if !SILVERLIGHT + /// + /// Initializes the static members of the LineSeries class. + /// + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Dependency properties are initialized in-line.")] + static LineSeries() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(LineSeries), new FrameworkPropertyMetadata(typeof(LineSeries))); + } + +#endif + /// + /// Initializes a new instance of the LineSeries class. + /// + public LineSeries() + { +#if SILVERLIGHT + this.DefaultStyleKey = typeof(LineSeries); +#endif + } + + /// + /// Acquire a horizontal linear axis and a vertical linear axis. + /// + /// The first data point. + protected override void GetAxes(DataPoint firstDataPoint) + { + GetAxes( + firstDataPoint, + (axis) => axis.Orientation == AxisOrientation.X, + () => + { + IAxis axis = CreateRangeAxisFromData(firstDataPoint.IndependentValue); + if (axis == null) + { + axis = new CategoryAxis(); + } + axis.Orientation = AxisOrientation.X; + return axis; + }, + (axis) => axis.Orientation == AxisOrientation.Y && axis is IRangeAxis, + () => + { + DisplayAxis axis = (DisplayAxis)CreateRangeAxisFromData(firstDataPoint.DependentValue); + if (axis == null) + { + throw new InvalidOperationException(Properties.Resources.DataPointSeriesWithAxes_NoSuitableAxisAvailableForPlottingDependentValue); + } + axis.ShowGridLines = true; + axis.Orientation = AxisOrientation.Y; + return axis; + }); + } + + /// + /// Updates the Series shape object from a collection of Points. + /// + /// Collection of Points. + protected override void UpdateShapeFromPoints(IEnumerable points) + { + if (points.Any()) + { + PointCollection pointCollection = new PointCollection(); + foreach (Point point in points) + { + pointCollection.Add(point); + } + Points = pointCollection; + } + else + { + Points = null; + } + } + } +} + +#endif diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/PieSeries.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/PieSeries.cs new file mode 100644 index 00000000..6760f4e4 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/PieSeries.cs @@ -0,0 +1,597 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Windows.Data; +using System.Windows.Media; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Represents a control that contains a data series to be rendered in pie + /// format. + /// + /// Preview + [StyleTypedProperty(Property = DataPointStyleName, StyleTargetType = typeof(PieDataPoint))] + [StyleTypedProperty(Property = "LegendItemStyle", StyleTargetType = typeof(LegendItem))] + [TemplatePart(Name = DataPointSeries.PlotAreaName, Type = typeof(Canvas))] + public partial class PieSeries : DataPointSeries, IResourceDictionaryDispenser, IRequireGlobalSeriesIndex + { + #region public Collection Palette + /// + /// Gets or sets a palette of ResourceDictionaries used by the series. + /// + [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "Want to allow this to be set from XAML.")] + public Collection Palette + { + get { return GetValue(PaletteProperty) as Collection; } + set { SetValue(PaletteProperty, value); } + } + + /// + /// Identifies the Palette dependency property. + /// + public static readonly DependencyProperty PaletteProperty = + DependencyProperty.Register( + "Palette", + typeof(Collection), + typeof(Series), + new PropertyMetadata(OnPalettePropertyChanged)); + + /// + /// PaletteProperty property changed handler. + /// + /// Parent that changed its Palette. + /// Event arguments. + private static void OnPalettePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + PieSeries source = d as PieSeries; + Collection newValue = e.NewValue as Collection; + source.OnPalettePropertyChanged(newValue); + } + + /// + /// PaletteProperty property changed handler. + /// + /// New value. + private void OnPalettePropertyChanged(Collection newValue) + { + ResourceDictionaryDispenser.ResourceDictionaries = newValue; + } + #endregion public Collection Palette + + /// + /// The pie data point style enumerator. + /// + private IEnumerator _resourceDictionaryEnumerator; + +#if !SILVERLIGHT + /// + /// Initializes the static members of the PieSeries class. + /// + static PieSeries() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(PieSeries), new FrameworkPropertyMetadata(typeof(PieSeries))); + } + +#endif + /// + /// Initializes a new instance of the PieSeries class. + /// + public PieSeries() + { +#if SILVERLIGHT + this.DefaultStyleKey = typeof(PieSeries); +#endif + this.ResourceDictionaryDispenser = new ResourceDictionaryDispenser(); + ResourceDictionaryDispenser.ResourceDictionariesChanged += delegate + { + OnResourceDictionariesChanged(EventArgs.Empty); + }; + } + + /// + /// Invokes the ResourceDictionariesChanged event. + /// + /// Event arguments. + protected virtual void OnResourceDictionariesChanged(EventArgs e) + { + // Update with new styles + Refresh(); + + // Forward event on to listeners + EventHandler handler = ResourceDictionariesChanged; + if (null != handler) + { + handler.Invoke(this, e); + } + } + + /// + /// A dictionary that links data points to their legend items. + /// + private Dictionary _dataPointLegendItems = new Dictionary(); + + /// + /// Accepts a ratio of a full rotation, the x and y length and returns + /// the 2D point using trigonometric functions. + /// + /// The ratio of a full rotation [0..1]. + /// The x radius. + /// The y radius. + /// The corresponding 2D point. + private static Point ConvertRatioOfRotationToPoint(double ratio, double radiusX, double radiusY) + { + double radians = (((ratio * 360) - 90) * (Math.PI / 180)); + return new Point(radiusX * Math.Cos(radians), radiusY * Math.Sin(radians)); + } + + /// + /// Creates a legend item for each data point. + /// + /// The data point added. + protected override void AddDataPoint(DataPoint dataPoint) + { + base.AddDataPoint(dataPoint); + PieDataPoint pieDataPoint = (PieDataPoint)dataPoint; + + int index = ActiveDataPoints.IndexOf(dataPoint) + 1; + LegendItem legendItem = CreatePieLegendItem(dataPoint, index); + + // Grab a style enumerator if we don't have one already. + if (_resourceDictionaryEnumerator == null) + { + _resourceDictionaryEnumerator = GetResourceDictionaryWithTargetType(this, typeof(PieDataPoint), true); + } + + if (_resourceDictionaryEnumerator.MoveNext()) + { + ResourceDictionary paletteResources = +#if SILVERLIGHT + _resourceDictionaryEnumerator.Current.ShallowCopy(); +#else + _resourceDictionaryEnumerator.Current; +#endif + pieDataPoint.PaletteResources = paletteResources; + pieDataPoint.Resources.MergedDictionaries.Add(paletteResources); + } + else + { + pieDataPoint.PaletteResources = null; + } + pieDataPoint.ActualDataPointStyle = DataPointStyle ?? pieDataPoint.Resources[DataPointStyleName] as Style; + pieDataPoint.SetBinding(PieDataPoint.StyleProperty, new Binding(PieDataPoint.ActualDataPointStyleName) { Source = pieDataPoint }); + pieDataPoint.ActualLegendItemStyle = LegendItemStyle ?? (pieDataPoint.Resources[LegendItemStyleName] as Style); + legendItem.SetBinding(LegendItem.StyleProperty, new Binding(ActualLegendItemStyleName) { Source = pieDataPoint }); + + _dataPointLegendItems[dataPoint] = legendItem; + LegendItems.Add(legendItem); + UpdateLegendItemIndexes(); + } + + /// + /// Removes data point's legend item when the data point is removed. + /// + /// The data point to remove. + protected override void RemoveDataPoint(DataPoint dataPoint) + { + base.RemoveDataPoint(dataPoint); + if (dataPoint != null) + { + LegendItem legendItem = _dataPointLegendItems[dataPoint]; + _dataPointLegendItems.Remove(dataPoint); + + LegendItems.Remove(legendItem); + UpdateLegendItemIndexes(); + } + } + + /// + /// Creates a data point. + /// + /// A data point. + protected override DataPoint CreateDataPoint() + { + return new PieDataPoint(); + } + + /// + /// Gets the active pie data points. + /// + private IEnumerable ActivePieDataPoints + { + get { return ActiveDataPoints.OfType(); } + } + + /// + /// Updates all ratios before data points are updated. + /// + protected override void OnBeforeUpdateDataPoints() + { + UpdateRatios(); + + base.OnBeforeUpdateDataPoints(); + } + + /// + /// Called after data points have been loaded from the items source. + /// + /// New active data points. + /// Old inactive data points. + protected override void OnDataPointsChanged(IList newDataPoints, IList oldDataPoints) + { + UpdateDataPoints(newDataPoints); + base.OnDataPointsChanged(newDataPoints, oldDataPoints); + } + + /// + /// Updates the indexes of all legend items when a change is made to the collection. + /// + private void UpdateLegendItemIndexes() + { + int index = 0; + foreach (DataPoint dataPoint in ActiveDataPoints) + { + LegendItem legendItem = _dataPointLegendItems[dataPoint]; + legendItem.Content = dataPoint.IndependentValue ?? (index + 1); + index++; + } + } + + /// + /// Updates the ratios of each data point. + /// + private void UpdateRatios() + { + double sum = ActivePieDataPoints.Select(pieDataPoint => Math.Abs(ValueHelper.ToDouble(pieDataPoint.DependentValue))).Sum(); + + // Priming the loop by calculating initial value of + // offset ratio and its corresponding points. + double offsetRatio = 0; + foreach (PieDataPoint dataPoint in ActivePieDataPoints) + { + double dependentValue = Math.Abs(ValueHelper.ToDouble(dataPoint.DependentValue)); + double ratio = dependentValue / sum; + if (!ValueHelper.CanGraph(ratio)) + { + ratio = 0.0; + } + dataPoint.Ratio = ratio; + dataPoint.OffsetRatio = offsetRatio; + offsetRatio += ratio; + } + } + + /// + /// Updates a data point. + /// + /// The data point to update. + protected override void UpdateDataPoint(DataPoint dataPoint) + { + PieDataPoint pieDataPoint = (PieDataPoint) dataPoint; + pieDataPoint.Width = ActualWidth; + pieDataPoint.Height = ActualHeight; + UpdatePieDataPointGeometry(pieDataPoint, ActualWidth, ActualHeight); + Canvas.SetLeft(pieDataPoint, 0); + Canvas.SetTop(pieDataPoint, 0); + } + + /// + /// Updates the PieDataPoint's Geometry property. + /// + /// PieDataPoint instance. + /// PlotArea width. + /// PlotArea height. + internal static void UpdatePieDataPointGeometry(PieDataPoint pieDataPoint, double plotAreaWidth, double plotAreaHeight) + { + double diameter = (plotAreaWidth < plotAreaHeight) ? plotAreaWidth : plotAreaHeight; + diameter *= 0.95; + double plotAreaRadius = diameter / 2; + double maxDistanceFromCenter = 0.0; + double sliceRadius = plotAreaRadius - maxDistanceFromCenter; + + Point translatePoint = new Point(plotAreaWidth / 2, plotAreaHeight / 2); + + if (pieDataPoint.ActualRatio == 1) + { + foreach (DependencyProperty dependencyProperty in new DependencyProperty[] { PieDataPoint.GeometryProperty, PieDataPoint.GeometrySelectionProperty, PieDataPoint.GeometryHighlightProperty }) + { + Geometry geometry = + new EllipseGeometry + { + Center = translatePoint, + RadiusX = sliceRadius, + RadiusY = sliceRadius + }; + pieDataPoint.SetValue(dependencyProperty, geometry); + } + } + else + { + if (pieDataPoint.ActualRatio == 0.0) + { + pieDataPoint.Geometry = null; + pieDataPoint.GeometryHighlight = null; + pieDataPoint.GeometrySelection = null; + } + else + { + double ratio = pieDataPoint.ActualRatio; + double offsetRatio = pieDataPoint.ActualOffsetRatio; + double currentRatio = offsetRatio + ratio; + + Point offsetRatioPoint = ConvertRatioOfRotationToPoint(offsetRatio, sliceRadius, sliceRadius); + + Point adjustedOffsetRatioPoint = offsetRatioPoint.Translate(translatePoint); + + // Calculate the last clockwise point in the pie slice + Point currentRatioPoint = + ConvertRatioOfRotationToPoint(currentRatio, sliceRadius, sliceRadius); + + // Adjust point using center of plot area as origin + // instead of 0,0 + Point adjustedCurrentRatioPoint = + currentRatioPoint.Translate(translatePoint); + + foreach (DependencyProperty dependencyProperty in new DependencyProperty[] { PieDataPoint.GeometryProperty, PieDataPoint.GeometrySelectionProperty, PieDataPoint.GeometryHighlightProperty }) + { + // Creating the pie slice geometry object + PathFigure pathFigure = new PathFigure { IsClosed = true }; + pathFigure.StartPoint = translatePoint; + pathFigure.Segments.Add(new LineSegment { Point = adjustedOffsetRatioPoint }); + bool isLargeArc = (currentRatio - offsetRatio) > 0.5; + pathFigure.Segments.Add( + new ArcSegment + { + Point = adjustedCurrentRatioPoint, + IsLargeArc = isLargeArc, + Size = new Size(sliceRadius, sliceRadius), + SweepDirection = SweepDirection.Clockwise + }); + + PathGeometry pathGeometry = new PathGeometry(); + pathGeometry.Figures.Add(pathFigure); + pieDataPoint.SetValue(dependencyProperty, pathGeometry); + } + } + } + } + + /// + /// Creates a legend item from a data point. + /// + /// The data point to use to create the legend item. + /// The 1-based index of the Control. + /// The series host legend item. + protected virtual LegendItem CreatePieLegendItem(DataPoint dataPoint, int index) + { + LegendItem legendItem = CreateLegendItem(this); + // Set the Content of the LegendItem + legendItem.Content = dataPoint.IndependentValue ?? index; + // Create a representative DataPoint for access to styled properties + DataPoint legendDataPoint = CreateDataPoint(); + legendDataPoint.DataContext = dataPoint.DataContext; + if (null != PlotArea) + { + // Bounce into the visual tree to get default Style applied + PlotArea.Children.Add(legendDataPoint); + PlotArea.Children.Remove(legendDataPoint); + } + legendDataPoint.SetBinding(DataPoint.StyleProperty, new Binding(PieDataPoint.ActualDataPointStyleName) { Source = dataPoint }); + legendItem.DataContext = legendDataPoint; + return legendItem; + } + + /// + /// Attach event handlers to a data point. + /// + /// The data point. + protected override void AttachEventHandlersToDataPoint(DataPoint dataPoint) + { + PieDataPoint pieDataPoint = dataPoint as PieDataPoint; + + pieDataPoint.ActualRatioChanged += OnPieDataPointActualRatioChanged; + pieDataPoint.ActualOffsetRatioChanged += OnPieDataPointActualOffsetRatioChanged; + pieDataPoint.RatioChanged += OnPieDataPointRatioChanged; + pieDataPoint.OffsetRatioChanged += OnPieDataPointOffsetRatioChanged; + + base.AttachEventHandlersToDataPoint(dataPoint); + } + + /// + /// Detaches event handlers from a data point. + /// + /// The data point. + protected override void DetachEventHandlersFromDataPoint(DataPoint dataPoint) + { + PieDataPoint pieDataPoint = dataPoint as PieDataPoint; + + pieDataPoint.ActualRatioChanged -= OnPieDataPointActualRatioChanged; + pieDataPoint.ActualOffsetRatioChanged -= OnPieDataPointActualOffsetRatioChanged; + pieDataPoint.RatioChanged -= OnPieDataPointRatioChanged; + pieDataPoint.OffsetRatioChanged -= OnPieDataPointOffsetRatioChanged; + + base.DetachEventHandlersFromDataPoint(dataPoint); + } + + /// + /// This method updates the global series index property. + /// + /// The global index of the series. + public void GlobalSeriesIndexChanged(int? globalIndex) + { + // Do nothing because we want to use up an index but do nothing + // with it. + } + + /// + /// Updates the data point when the dependent value is changed. + /// + /// The data point. + /// The old value. + /// The new value. + protected override void OnDataPointDependentValueChanged(DataPoint dataPoint, IComparable oldValue, IComparable newValue) + { + UpdateRatios(); + base.OnDataPointDependentValueChanged(dataPoint, oldValue, newValue); + } + + /// + /// Updates the data point when the independent value is changed. + /// + /// The data point. + /// The old value. + /// The new value. + protected override void OnDataPointIndependentValueChanged(DataPoint dataPoint, object oldValue, object newValue) + { + _dataPointLegendItems[dataPoint].Content = newValue; + base.OnDataPointIndependentValueChanged(dataPoint, oldValue, newValue); + } + + /// + /// Updates the data point when the pie data point's actual ratio is + /// changed. + /// + /// The source of the event. + /// Information about the event. + private void OnPieDataPointActualRatioChanged(object sender, RoutedPropertyChangedEventArgs args) + { + UpdateDataPoint(sender as DataPoint); + } + + /// + /// Updates the data point when the pie data point's actual offset ratio + /// is changed. + /// + /// The source of the event. + /// Information about the event. + private void OnPieDataPointActualOffsetRatioChanged(object sender, RoutedPropertyChangedEventArgs args) + { + UpdateDataPoint(sender as DataPoint); + } + + /// + /// Updates the data point when the pie data point's ratio is changed. + /// + /// The source of the event. + /// Information about the event. + private void OnPieDataPointRatioChanged(object sender, RoutedPropertyChangedEventArgs args) + { + DataPoint dataPoint = sender as DataPoint; + dataPoint.BeginAnimation(PieDataPoint.ActualRatioProperty, "ActualRatio", args.NewValue, TransitionDuration, this.TransitionEasingFunction); + } + + /// + /// Updates the data point when the pie data point's offset ratio is + /// changed. + /// + /// The source of the event. + /// Information about the event. + private void OnPieDataPointOffsetRatioChanged(object sender, RoutedPropertyChangedEventArgs args) + { + DataPoint dataPoint = sender as DataPoint; + dataPoint.BeginAnimation(PieDataPoint.ActualOffsetRatioProperty, "ActualOffsetRatio", args.NewValue, TransitionDuration, this.TransitionEasingFunction); + } + + /// + /// Gets or sets an object used to dispense styles from the style + /// palette. + /// + private ResourceDictionaryDispenser ResourceDictionaryDispenser { get; set; } + + /// + /// Event that is invoked when the ResourceDictionaryDispenser's collection has changed. + /// + public event EventHandler ResourceDictionariesChanged; + + /// + /// Returns a rotating enumerator of ResourceDictionary objects that coordinates + /// with the dispenser object to ensure that no two enumerators are on the same + /// item. If the dispenser is reset or its collection is changed then the + /// enumerators are also reset. + /// + /// A predicate that returns a value indicating + /// whether to return an item. + /// An enumerator of ResourceDictionaries. + public IEnumerator GetResourceDictionariesWhere(Func predicate) + { + return ResourceDictionaryDispenser.GetResourceDictionariesWhere(predicate); + } + + /// + /// Called when the value of the SeriesHost property changes. + /// + /// The value to be replaced. + /// The new series host value. + protected override void OnSeriesHostPropertyChanged(ISeriesHost oldValue, ISeriesHost newValue) + { + base.OnSeriesHostPropertyChanged(oldValue, newValue); + + if (null != oldValue) + { + oldValue.ResourceDictionariesChanged -= new EventHandler(SeriesHostResourceDictionariesChanged); + } + + if (null != newValue) + { + newValue.ResourceDictionariesChanged += new EventHandler(SeriesHostResourceDictionariesChanged); + } + else + { + // Dispose of the enumerator. + if (null != _resourceDictionaryEnumerator) + { + _resourceDictionaryEnumerator.Dispose(); + _resourceDictionaryEnumerator = null; + } + } + + this.ResourceDictionaryDispenser.Parent = newValue; + } + + /// + /// Handles the SeriesHost's ResourceDictionariesChanged event. + /// + /// ISeriesHost instance. + /// Event args. + private void SeriesHostResourceDictionariesChanged(object sender, EventArgs e) + { + Refresh(); + } + + /// + /// DataPointStyleProperty property changed handler. + /// + /// Old value. + /// New value. + protected override void OnDataPointStylePropertyChanged(Style oldValue, Style newValue) + { + // Propagate change + foreach (PieDataPoint pieDataPoint in ActiveDataPoints) + { + pieDataPoint.ActualDataPointStyle = newValue ?? (pieDataPoint.Resources[DataPointStyleName] as Style); + } + base.OnDataPointStylePropertyChanged(oldValue, newValue); + } + + /// + /// Called when the value of the LegendItemStyle property changes. + /// + /// Old value. + /// New value. + protected override void OnLegendItemStylePropertyChanged(Style oldValue, Style newValue) + { + // Propagate change + foreach (PieDataPoint pieDataPoint in ActiveDataPoints) + { + pieDataPoint.ActualLegendItemStyle = newValue ?? (pieDataPoint.Resources[LegendItemStyleName] as Style); + } + base.OnLegendItemStylePropertyChanged(oldValue, newValue); + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/ScatterSeries.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/ScatterSeries.cs new file mode 100644 index 00000000..ead69afc --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/ScatterSeries.cs @@ -0,0 +1,205 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; + +#if !DEFINITION_SERIES_COMPATIBILITY_MODE + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Represents a control that contains a data series to be rendered in X/Y scatter format. + /// + /// Preview + [StyleTypedProperty(Property = DataPointStyleName, StyleTargetType = typeof(ScatterDataPoint))] + [StyleTypedProperty(Property = "LegendItemStyle", StyleTargetType = typeof(LegendItem))] + [TemplatePart(Name = DataPointSeries.PlotAreaName, Type = typeof(Canvas))] + public partial class ScatterSeries : DataPointSingleSeriesWithAxes + { + /// + /// Initializes a new instance of the ScatterSeries class. + /// + public ScatterSeries() + { + } + + /// + /// Gets the dependent axis as a range axis. + /// + public IRangeAxis ActualDependentRangeAxis { get { return this.InternalActualDependentAxis as IRangeAxis; } } + + #region public IRangeAxis DependentRangeAxis + /// + /// Gets or sets the dependent range axis. + /// + public IRangeAxis DependentRangeAxis + { + get { return GetValue(DependentRangeAxisProperty) as IRangeAxis; } + set { SetValue(DependentRangeAxisProperty, value); } + } + + /// + /// Identifies the DependentRangeAxis dependency property. + /// + public static readonly DependencyProperty DependentRangeAxisProperty = + DependencyProperty.Register( + "DependentRangeAxis", + typeof(IRangeAxis), + typeof(ScatterSeries), + new PropertyMetadata(null, OnDependentRangeAxisPropertyChanged)); + + /// + /// DependentRangeAxisProperty property changed handler. + /// + /// ScatterSeries that changed its DependentRangeAxis. + /// Event arguments. + private static void OnDependentRangeAxisPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ScatterSeries source = (ScatterSeries)d; + IRangeAxis newValue = (IRangeAxis)e.NewValue; + source.OnDependentRangeAxisPropertyChanged(newValue); + } + + /// + /// DependentRangeAxisProperty property changed handler. + /// + /// New value. + private void OnDependentRangeAxisPropertyChanged(IRangeAxis newValue) + { + this.InternalDependentAxis = (IAxis)newValue; + } + #endregion public IRangeAxis DependentRangeAxis + + /// + /// Gets the independent axis as a range axis. + /// + public IAxis ActualIndependentAxis { get { return this.InternalActualIndependentAxis as IAxis; } } + + #region public IAxis IndependentAxis + /// + /// Gets or sets the independent range axis. + /// + public IAxis IndependentAxis + { + get { return GetValue(IndependentAxisProperty) as IAxis; } + set { SetValue(IndependentAxisProperty, value); } + } + + /// + /// Identifies the IndependentAxis dependency property. + /// + public static readonly DependencyProperty IndependentAxisProperty = + DependencyProperty.Register( + "IndependentAxis", + typeof(IAxis), + typeof(ScatterSeries), + new PropertyMetadata(null, OnIndependentAxisPropertyChanged)); + + /// + /// IndependentAxisProperty property changed handler. + /// + /// ScatterSeries that changed its IndependentAxis. + /// Event arguments. + private static void OnIndependentAxisPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ScatterSeries source = (ScatterSeries)d; + IAxis newValue = (IAxis)e.NewValue; + source.OnIndependentAxisPropertyChanged(newValue); + } + + /// + /// IndependentAxisProperty property changed handler. + /// + /// New value. + private void OnIndependentAxisPropertyChanged(IAxis newValue) + { + this.InternalIndependentAxis = (IAxis)newValue; + } + #endregion public IAxis IndependentAxis + + /// + /// Acquire a horizontal linear axis and a vertical linear axis. + /// + /// The first data point. + protected override void GetAxes(DataPoint firstDataPoint) + { + GetAxes( + firstDataPoint, + (axis) => axis.Orientation == AxisOrientation.X, + () => + { + IAxis axis = CreateRangeAxisFromData(firstDataPoint.IndependentValue); + if (axis == null) + { + axis = new CategoryAxis(); + } + axis.Orientation = AxisOrientation.X; + return axis; + }, + (axis) => axis.Orientation == AxisOrientation.Y && axis is IRangeAxis, + () => + { + DisplayAxis axis = (DisplayAxis)CreateRangeAxisFromData(firstDataPoint.DependentValue); + if (axis == null) + { + throw new InvalidOperationException(Properties.Resources.DataPointSeriesWithAxes_NoSuitableAxisAvailableForPlottingDependentValue); + } + axis.ShowGridLines = true; + axis.Orientation = AxisOrientation.Y; + return axis; + }); + } + + /// + /// Creates a new scatter data point. + /// + /// A scatter data point. + protected override DataPoint CreateDataPoint() + { + return new ScatterDataPoint(); + } + + /// + /// Returns the custom ResourceDictionary to use for necessary resources. + /// + /// + /// ResourceDictionary to use for necessary resources. + /// + protected override IEnumerator GetResourceDictionaryEnumeratorFromHost() + { + return GetResourceDictionaryWithTargetType(SeriesHost, typeof(ScatterDataPoint), true); + } + + /// + /// This method updates a single data point. + /// + /// The data point to update. + protected override void UpdateDataPoint(DataPoint dataPoint) + { + double PlotAreaHeight = ActualDependentRangeAxis.GetPlotAreaCoordinate(ActualDependentRangeAxis.Range.Maximum).Value; + double dataPointX = ActualIndependentAxis.GetPlotAreaCoordinate(dataPoint.ActualIndependentValue).Value; + double dataPointY = ActualDependentRangeAxis.GetPlotAreaCoordinate(dataPoint.ActualDependentValue).Value; + + if (ValueHelper.CanGraph(dataPointX) && ValueHelper.CanGraph(dataPointY)) + { + dataPoint.Visibility = Visibility.Visible; + + // Set the Position + Canvas.SetLeft( + dataPoint, + Math.Round(dataPointX - (dataPoint.ActualWidth / 2))); + Canvas.SetTop( + dataPoint, + Math.Round(PlotAreaHeight - (dataPointY + (dataPoint.ActualHeight / 2)))); + } + else + { + dataPoint.Visibility = Visibility.Collapsed; + } + } + } +} + +#endif diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Series.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Series.cs new file mode 100644 index 00000000..c73029d2 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Series.cs @@ -0,0 +1,113 @@ +// (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.ObjectModel; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Represents a control that contains a data series. + /// + /// Preview + public abstract partial class Series : Control, ISeries, IRequireSeriesHost + { + /// + /// The name of the Title property. + /// + protected const string TitleName = "Title"; + + #region public ISeriesHost SeriesHost + /// + /// Gets or sets the parent instance the Series belongs to. + /// + public ISeriesHost SeriesHost + { + get { return _seriesHost; } + set + { + ISeriesHost oldValue = _seriesHost; + _seriesHost = value; + if (oldValue != _seriesHost) + { + OnSeriesHostPropertyChanged(oldValue, _seriesHost); + } + } + } + + /// + /// Stores the Parent instance the Series belongs to. + /// + private ISeriesHost _seriesHost; + + /// + /// Called when the value of the SeriesHost property changes. + /// + /// The value to be replaced. + /// The new series host value. + protected virtual void OnSeriesHostPropertyChanged(ISeriesHost oldValue, ISeriesHost newValue) + { + if (newValue != null && oldValue != null) + { + throw new InvalidOperationException(Properties.Resources.Series_SeriesHost_SeriesHostPropertyNotNull); + } + } + #endregion public ISeriesHost SeriesHost + + #region public ObservableCollection LegendItems + /// + /// Gets the legend items to be added to the legend. + /// + public ObservableCollection LegendItems { get; private set; } + #endregion public ObservableCollection LegendItems + + #region public object Title + /// + /// Gets or sets the title content of the Series. + /// + public object Title + { + get { return GetValue(TitleProperty); } + set { SetValue(TitleProperty, value); } + } + + /// + /// Identifies the Title dependency property. + /// + public static readonly DependencyProperty TitleProperty = + DependencyProperty.Register( + TitleName, + typeof(object), + typeof(Series), + new PropertyMetadata(OnTitleChanged)); + + /// + /// TitleProperty property changed callback. + /// + /// Series for which the Title changed. + /// Event arguments. + private static void OnTitleChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + ((Series)o).OnTitleChanged(e.OldValue, e.NewValue); + } + + /// + /// Called when the Title property changes. + /// + /// The old value of the Title property. + /// The new value of the Title property. + protected virtual void OnTitleChanged(object oldValue, object newValue) + { + } + #endregion public object Title + + /// + /// Initializes a new instance of the Series class. + /// + protected Series() + { + LegendItems = new NoResetObservableCollection(); + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/SeriesDefinition.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/SeriesDefinition.cs new file mode 100644 index 00000000..4048c767 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/SeriesDefinition.cs @@ -0,0 +1,635 @@ +// (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.ObjectModel; +using System.Collections.Specialized; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Windows.Controls.Primitives; +using System.Windows.Data; +using System.Windows.Media.Animation; +using System.Windows.Shapes; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Defines the attributes of a series that is to be rendered by the DefinitionSeries class. + /// + /// Preview + [StyleTypedProperty(Property = DataPointStyleName, StyleTargetType = typeof(DataPoint))] + [StyleTypedProperty(Property = LegendItemStyleName, StyleTargetType = typeof(LegendItem))] + [StyleTypedProperty(Property = DataShapeStyleName, StyleTargetType = typeof(Shape))] + public class SeriesDefinition : FrameworkElement, ISeries, IRequireGlobalSeriesIndex + { + /// + /// Name of the DataPointStyle property. + /// + private const string DataPointStyleName = "DataPointStyle"; + + /// + /// Name of the LegendItemStyle property. + /// + private const string LegendItemStyleName = "LegendItemStyle"; + + /// + /// Name of the DataShapeStyle property. + /// + private const string DataShapeStyleName = "DataShapeStyle"; + + /// + /// Provides the store for the ISeries.LegendItems property. + /// + private readonly ObservableCollection _legendItems = new ObservableCollection(); + + /// + /// Represents the single LegendItem corresponding to the SeriesDefinition. + /// + private readonly LegendItem _legendItem; + + /// + /// Keeps a reference to the WeakEventListener used to prevent leaks of collections assigned to the ItemsSource property. + /// + private WeakEventListener _weakEventListener; + + /// + /// Gets or sets the index of the series definition. + /// + internal int Index { get; set; } + + /// + /// Initializes a new instance of the SeriesDefinition class. + /// + public SeriesDefinition() + { + _legendItem = new LegendItem { Owner = this }; + _legendItem.SetBinding(LegendItem.ContentProperty, new Binding("ActualTitle") { Source = this }); + _legendItem.SetBinding(LegendItem.StyleProperty, new Binding("ActualLegendItemStyle") { Source = this }); + _legendItems.Add(_legendItem); + } + + /// + /// Gets or sets a sequence that provides the content 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(SeriesDefinition), new PropertyMetadata(OnItemsSourceChanged)); + + /// + /// Handles changes to the ItemsSource dependency property. + /// + /// DependencyObject that changed. + /// Event data for the DependencyPropertyChangedEvent. + private static void OnItemsSourceChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + ((SeriesDefinition)o).OnItemsSourceChanged((IEnumerable)e.OldValue, (IEnumerable)e.NewValue); + } + + /// + /// Handles changes to the ItemsSource property. + /// + /// Old value. + /// New value. + private 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 (null != ParentDefinitionSeries) + { + ParentDefinitionSeries.SeriesDefinitionItemsSourceChanged(this, oldValue, newValue); + } + } + + /// + /// Handles the CollectionChanged event for the ItemsSource property. + /// + /// Event source. + /// Event arguments.. + private void ItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (null != ParentDefinitionSeries) + { + ParentDefinitionSeries.SeriesDefinitionItemsSourceCollectionChanged(this, e.Action, e.OldItems, e.OldStartingIndex, e.NewItems, e.NewStartingIndex); + } + } + + /// + /// Gets or sets the automatic title of the series definition. + /// + private object AutomaticTitle + { + get { return _automaticTitle; } + set + { + _automaticTitle = value; + ActualTitle = Title ?? _automaticTitle; + } + } + + /// + /// Stores the automatic title of the series definition. + /// + private object _automaticTitle; + + /// + /// Gets or sets the Title of the series definition. + /// + public object Title + { + get { return (object)GetValue(TitleProperty); } + set { SetValue(TitleProperty, value); } + } + + /// + /// Identifies the Title dependency property. + /// + public static readonly DependencyProperty TitleProperty = + DependencyProperty.Register("Title", typeof(object), typeof(SeriesDefinition), new PropertyMetadata(OnTitleChanged)); + + /// + /// Handles changes to the Title dependency property. + /// + /// DependencyObject that changed. + /// Event data for the DependencyPropertyChangedEvent. + private static void OnTitleChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + ((SeriesDefinition)o).OnTitleChanged((object)e.OldValue, (object)e.NewValue); + } + + /// + /// Handles changes to the Title property. + /// + /// Old value. + /// New value. + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "oldValue", Justification = "Parameter is part of the pattern for DependencyProperty change handlers.")] + private void OnTitleChanged(object oldValue, object newValue) + { + ActualTitle = newValue ?? _automaticTitle; + } + + /// + /// Gets the rendered Title of the series definition. + /// + public object ActualTitle + { + get { return (object)GetValue(ActualTitleProperty); } + protected set { SetValue(ActualTitleProperty, value); } + } + + /// + /// Identifies the ActualTitle dependency property. + /// + public static readonly DependencyProperty ActualTitleProperty = + DependencyProperty.Register("ActualTitle", typeof(object), typeof(SeriesDefinition), null); + + /// + /// Gets or sets the DataPoint Style from the SeriesHost's Palette. + /// + internal Style PaletteDataPointStyle + { + get { return _paletteDataPointStyle; } + set + { + _paletteDataPointStyle = value; + ActualDataPointStyle = DataPointStyle ?? _paletteDataPointStyle; + } + } + + /// + /// Stores the DataPoint Style from the SeriesHost's Palette. + /// + private Style _paletteDataPointStyle; + + /// + /// Gets or sets the DataPoint Style for the series definition. + /// + public Style DataPointStyle + { + get { return (Style)GetValue(DataPointStyleProperty); } + set { SetValue(DataPointStyleProperty, value); } + } + + /// + /// Identifies the DataPointStyle dependency property. + /// + public static readonly DependencyProperty DataPointStyleProperty = + DependencyProperty.Register(DataPointStyleName, typeof(Style), typeof(SeriesDefinition), new PropertyMetadata(OnDataPointStyleChanged)); + + /// + /// Handles changes to the DataPointStyle dependency property. + /// + /// DependencyObject that changed. + /// Event data for the DependencyPropertyChangedEvent. + private static void OnDataPointStyleChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + ((SeriesDefinition)o).OnDataPointStyleChanged((Style)e.OldValue, (Style)e.NewValue); + } + + /// + /// Handles changes to the DataPointStyle property. + /// + /// Old value. + /// New value. + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "oldValue", Justification = "Parameter is part of the pattern for DependencyProperty change handlers.")] + private void OnDataPointStyleChanged(Style oldValue, Style newValue) + { + ActualDataPointStyle = newValue ?? _paletteDataPointStyle; + } + + /// + /// Gets the rendered DataPoint Style for the series definition. + /// + public Style ActualDataPointStyle + { + get { return (Style)GetValue(ActualDataPointStyleProperty); } + protected set { SetValue(ActualDataPointStyleProperty, value); } + } + + /// + /// Identifies the ActualDataPointStyle dependency property. + /// + public static readonly DependencyProperty ActualDataPointStyleProperty = + DependencyProperty.Register("ActualDataPointStyle", typeof(Style), typeof(SeriesDefinition), null); + + /// + /// Gets or sets the LegendItem Style from the SeriesHost's Palette. + /// + internal Style PaletteLegendItemStyle + { + get { return _paletteLegendItemStyle; } + set + { + _paletteLegendItemStyle = value; + ActualLegendItemStyle = LegendItemStyle ?? _paletteLegendItemStyle; + } + } + + /// + /// Stores the LegendItem Style from the SeriesHost's Palette. + /// + private Style _paletteLegendItemStyle; + + /// + /// Gets or sets the LegendItem Style for the series definition. + /// + public Style LegendItemStyle + { + get { return (Style)GetValue(LegendItemStyleProperty); } + set { SetValue(LegendItemStyleProperty, value); } + } + + /// + /// Identifies the LegendItemStyle dependency property. + /// + public static readonly DependencyProperty LegendItemStyleProperty = + DependencyProperty.Register(LegendItemStyleName, typeof(Style), typeof(SeriesDefinition), new PropertyMetadata(OnLegendItemStyleChanged)); + + /// + /// Handles changes to the LegendItemStyle dependency property. + /// + /// DependencyObject that changed. + /// Event data for the DependencyPropertyChangedEvent. + private static void OnLegendItemStyleChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + ((SeriesDefinition)o).OnLegendItemStyleChanged((Style)e.OldValue, (Style)e.NewValue); + } + + /// + /// Handles changes to the LegendItemStyle property. + /// + /// Old value. + /// New value. + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "oldValue", Justification = "Parameter is part of the pattern for DependencyProperty change handlers.")] + private void OnLegendItemStyleChanged(Style oldValue, Style newValue) + { + ActualLegendItemStyle = newValue ?? _paletteLegendItemStyle; + } + + /// + /// Gets the rendered LegendItem Style for the series definition. + /// + public Style ActualLegendItemStyle + { + get { return (Style)GetValue(ActualLegendItemStyleProperty); } + protected set { SetValue(ActualLegendItemStyleProperty, value); } + } + + /// + /// Identifies the ActualDataPointStyle dependency property. + /// + public static readonly DependencyProperty ActualLegendItemStyleProperty = + DependencyProperty.Register("ActualLegendItemStyle", typeof(Style), typeof(SeriesDefinition), null); + + /// + /// Gets or sets the DataShape Style from the SeriesHost's Palette. + /// + internal Style PaletteDataShapeStyle + { + get { return _paletteDataShapeStyle; } + set + { + _paletteDataShapeStyle = value; + ActualDataShapeStyle = DataShapeStyle ?? _paletteDataShapeStyle; + } + } + + /// + /// Stores the DataShape Style from the SeriesHost's Palette. + /// + private Style _paletteDataShapeStyle; + + /// + /// Gets or sets the DataShape Style for the series definition. + /// + public Style DataShapeStyle + { + get { return (Style)GetValue(DataShapeStyleProperty); } + set { SetValue(DataShapeStyleProperty, value); } + } + + /// + /// Identifies the DataShapeStyle dependency property. + /// + public static readonly DependencyProperty DataShapeStyleProperty = + DependencyProperty.Register(DataShapeStyleName, typeof(Style), typeof(SeriesDefinition), new PropertyMetadata(OnDataShapeStyleChanged)); + + /// + /// Handles changes to the DataShapeStyle dependency property. + /// + /// DependencyObject that changed. + /// Event data for the DependencyPropertyChangedEvent. + private static void OnDataShapeStyleChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + ((SeriesDefinition)o).OnDataShapeStyleChanged((Style)e.OldValue, (Style)e.NewValue); + } + + /// + /// Handles changes to the DataShapeStyle property. + /// + /// Old value. + /// New value. + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "oldValue", Justification = "Parameter is part of the pattern for DependencyProperty change handlers.")] + private void OnDataShapeStyleChanged(Style oldValue, Style newValue) + { + ActualDataShapeStyle = newValue ?? _paletteDataShapeStyle; + } + + /// + /// Gets the rendered DataShape Style for the series definition. + /// + public Style ActualDataShapeStyle + { + get { return (Style)GetValue(ActualDataShapeStyleProperty); } + protected set { SetValue(ActualDataShapeStyleProperty, value); } + } + + /// + /// Identifies the ActualDataShapeStyle dependency property. + /// + public static readonly DependencyProperty ActualDataShapeStyleProperty = + DependencyProperty.Register("ActualDataShapeStyle", typeof(Style), typeof(SeriesDefinition), null); + + /// + /// Gets or sets the Binding to use for identifying the dependent value. + /// + public Binding DependentValueBinding + { + get + { + return _dependentValueBinding; + } + set + { + if (value != _dependentValueBinding) + { + _dependentValueBinding = value; + Reset(); + } + } + } + + /// + /// The binding used to identify the dependent value binding. + /// + private Binding _dependentValueBinding; + + /// + /// 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); + } + } + } + + /// + /// Gets or sets the Binding to use for identifying the independent value. + /// + public Binding IndependentValueBinding + { + get + { + return _independentValueBinding; + } + set + { + if (_independentValueBinding != value) + { + _independentValueBinding = value; + Reset(); + } + } + } + + /// + /// The binding used to identify the independent value binding. + /// + private Binding _independentValueBinding; + + /// + /// 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); + } + } + } + + /// + /// Resets the display of the series definition. + /// + private void Reset() + { + if (null != ParentDefinitionSeries) + { + ParentDefinitionSeries.SeriesDefinitionItemsSourceChanged(this, ItemsSource, ItemsSource); + } + } + + /// + /// Gets the SeriesHost as a DefinitionSeries instance. + /// + private DefinitionSeries ParentDefinitionSeries + { + get { return (DefinitionSeries)((ISeries)this).SeriesHost; } + } + + /// + /// Gets the collection of legend items for the series definition. + /// + ObservableCollection ISeries.LegendItems + { + get { return _legendItems; } + } + + /// + /// Gets or sets the SeriesHost for the series definition. + /// + ISeriesHost IRequireSeriesHost.SeriesHost + { + get { return _seriesHost; } + set + { + _seriesHost = value; + if (!(_seriesHost is DefinitionSeries) && (null != value)) + { + throw new NotSupportedException(Properties.Resources.SeriesDefinition_SeriesHost_InvalidParent); + } + + if (null != _seriesHost) + { + DataPoint legendItemDataPoint = ((DefinitionSeries)_seriesHost).InternalCreateDataPoint(); +#if SILVERLIGHT + // Apply default style (hard) + ContentPresenter container = new ContentPresenter { Content = legendItemDataPoint, Width = 1, Height = 1 }; + Popup popup = new Popup { Child = container }; + container.SizeChanged += delegate + { + popup.Child = null; + popup.IsOpen = false; + }; + popup.IsOpen = true; +#else + // Apply default style (easy) + ContentControl contentControl = new ContentControl(); + contentControl.Content = legendItemDataPoint; + contentControl.Content = null; +#endif + legendItemDataPoint.SetBinding(DataPoint.StyleProperty, new Binding("ActualDataPointStyle") { Source = this }); + _legendItem.DataContext = legendItemDataPoint; + } + } + } + + /// + /// Stores the SeriesHost for the series definition. + /// + private ISeriesHost _seriesHost; + + /// + /// Handles changes to the global series index of the series definition. + /// + /// New index. + void IRequireGlobalSeriesIndex.GlobalSeriesIndexChanged(int? globalIndex) + { + if (globalIndex.HasValue) + { + AutomaticTitle = string.Format(CultureInfo.CurrentCulture, Properties.Resources.Series_OnGlobalSeriesIndexPropertyChanged_UntitledSeriesFormatString, globalIndex + 1); + } + } + + /// + /// Gets or sets the TimeSpan to use for the duration of data transitions. + /// + 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(SeriesDefinition), new PropertyMetadata(TimeSpan.FromSeconds(0.5))); + +#if !NO_EASING_FUNCTIONS + /// + /// Gets or sets the IEasingFunction to use for data transitions. + /// + public IEasingFunction TransitionEasingFunction + { + get { return (IEasingFunction)GetValue(TransitionEasingFunctionProperty); } + set { SetValue(TransitionEasingFunctionProperty, value); } + } + + /// + /// Identifies the TransitionEasingFunction dependency property. + /// + public static readonly DependencyProperty TransitionEasingFunctionProperty = + DependencyProperty.Register("TransitionEasingFunction", typeof(IEasingFunction), typeof(SeriesDefinition), new PropertyMetadata(new QuadraticEase { EasingMode = EasingMode.EaseInOut })); +#else + /// + /// Gets or sets a placeholder for the TransitionEasingFunction dependency property. + /// + internal IEasingFunction TransitionEasingFunction { get; set; } +#endif + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/SeriesSelectionMode.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/SeriesSelectionMode.cs new file mode 100644 index 00000000..c3d6af3d --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/SeriesSelectionMode.cs @@ -0,0 +1,28 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Defines the selection behavior for a series. + /// + public enum SeriesSelectionMode + { + /// + /// Selection is disabled. + /// + None, + + /// + /// The user can select only one item at a time. + /// + Single, + + /// + /// The user can select multiple items without holding down a modifier key. + /// + Multiple, + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Stacked100AreaSeries.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Stacked100AreaSeries.cs new file mode 100644 index 00000000..7e873ecc --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Stacked100AreaSeries.cs @@ -0,0 +1,22 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Control that displays values as a 100% stacked area chart visualization. + /// + /// Preview + public class Stacked100AreaSeries : StackedAreaSeries + { + /// + /// Initializes a new instance of the Stacked100AreaSeries class. + /// + public Stacked100AreaSeries() + { + IsStacked100 = true; + } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Stacked100BarSeries.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Stacked100BarSeries.cs new file mode 100644 index 00000000..6c96babb --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Stacked100BarSeries.cs @@ -0,0 +1,22 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Control that displays values as a 100% stacked bar chart visualization. + /// + /// Preview + public class Stacked100BarSeries : StackedBarSeries + { + /// + /// Initializes a new instance of the Stacked100BarSeries class. + /// + public Stacked100BarSeries() + { + IsStacked100 = true; + } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Stacked100ColumnSeries.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Stacked100ColumnSeries.cs new file mode 100644 index 00000000..c1ebf8f8 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Stacked100ColumnSeries.cs @@ -0,0 +1,22 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Control that displays values as a 100% stacked column chart visualization. + /// + /// Preview + public class Stacked100ColumnSeries : StackedColumnSeries + { + /// + /// Initializes a new instance of the Stacked100ColumnSeries class. + /// + public Stacked100ColumnSeries() + { + IsStacked100 = true; + } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Stacked100LineSeries.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Stacked100LineSeries.cs new file mode 100644 index 00000000..99c6a968 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/Stacked100LineSeries.cs @@ -0,0 +1,22 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Control that displays values as a 100% stacked line chart visualization. + /// + /// Preview + public class Stacked100LineSeries : StackedLineSeries + { + /// + /// Initializes a new instance of the Stacked100LineSeries class. + /// + public Stacked100LineSeries() + { + IsStacked100 = true; + } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/StackedAreaLineSeries.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/StackedAreaLineSeries.cs new file mode 100644 index 00000000..b612ce0b --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/StackedAreaLineSeries.cs @@ -0,0 +1,354 @@ +// (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.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Windows.Data; +using System.Windows.Media; +using System.Windows.Shapes; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Control base class for displaying values as a stacked area/line chart visualization. + /// + /// Preview + public abstract class StackedAreaLineSeries : DefinitionSeries + { + /// + /// Gets the Shapes corresponding to each SeriesDefinition. + /// + protected Dictionary SeriesDefinitionShapes { get; private set; } + + /// + /// Initializes a new instance of the StackedAreaLineSeries class. + /// + protected StackedAreaLineSeries() + { + SeriesDefinitionShapes = new Dictionary(); + } + + /// + /// Builds the visual tree for the control when a new template is applied. + /// + public override void OnApplyTemplate() + { + SynchronizeSeriesDefinitionShapes(SeriesDefinitions, null); + base.OnApplyTemplate(); + SynchronizeSeriesDefinitionShapes(null, SeriesDefinitions); + } + + /// + /// Called when the SeriesDefinitions collection changes. + /// + /// Type of change. + /// Sequence of old items. + /// Starting index of old items. + /// Sequence of new items. + /// Starting index of new items. + protected override void SeriesDefinitionsCollectionChanged(NotifyCollectionChangedAction action, IList oldItems, int oldStartingIndex, IList newItems, int newStartingIndex) + { + base.SeriesDefinitionsCollectionChanged(action, oldItems, oldStartingIndex, newItems, newStartingIndex); + if (null != oldItems) + { + SynchronizeSeriesDefinitionShapes(oldItems.CastWrapper(), null); + foreach (SeriesDefinition oldDefinition in oldItems.CastWrapper()) + { + SeriesDefinitionShapes.Remove(oldDefinition); + } + } + if (null != newItems) + { + foreach (SeriesDefinition newDefinition in newItems.CastWrapper()) + { + Shape dataShape = CreateDataShape(); + dataShape.SetBinding(Shape.StyleProperty, new Binding("ActualDataShapeStyle") { Source = newDefinition }); + SeriesDefinitionShapes[newDefinition] = dataShape; + } + SynchronizeSeriesDefinitionShapes(null, newItems.CastWrapper()); + } + } + + /// + /// Acquires a dependent axis suitable for use with the data values of the series. + /// + /// Axis instance. + protected override IAxis AcquireDependentAxis() + { + IAxis dependentAxis = SeriesHost.Axes + .Where(a => (a.Orientation == AxisOrientation.Y) && (a is IRangeAxis) && DataItems.Any() && (a.CanPlot(DataItems.First().ActualDependentValue))) + .FirstOrDefault(); + if (null == dependentAxis) + { + LinearAxis linearAxis = new LinearAxis { Orientation = AxisOrientation.Y, ShowGridLines = true }; + if (IsStacked100) + { + Style style = new Style(typeof(AxisLabel)); + style.Setters.Add(new Setter(AxisLabel.StringFormatProperty, "{0}%")); + linearAxis.AxisLabelStyle = style; + } + dependentAxis = linearAxis; + } + return dependentAxis; + } + + /// + /// Acquires an independent axis suitable for use with the data values of the series. + /// + /// Axis instance. + protected override IAxis AcquireIndependentAxis() + { + IAxis independentAxis = SeriesHost.Axes + .Where(a => (a.Orientation == AxisOrientation.X) && ((a is IRangeAxis) || (a is ICategoryAxis)) && DataItems.Any() && (a.CanPlot(DataItems.First().ActualIndependentValue))) + .FirstOrDefault(); + if (null == independentAxis) + { + object probeValue = DataItems.Any() ? DataItems.First().ActualIndependentValue : null; + double convertedDouble; + DateTime convertedDateTime; + if ((null != probeValue) && ValueHelper.TryConvert(probeValue, out convertedDouble)) + { + independentAxis = new LinearAxis(); + } + else if ((null != probeValue) && ValueHelper.TryConvert(probeValue, out convertedDateTime)) + { + independentAxis = new DateTimeAxis(); + } + else + { + independentAxis = new CategoryAxis(); + } + independentAxis.Orientation = AxisOrientation.X; + } + return independentAxis; + } + + /// + /// Prepares a DataPoint for use. + /// + /// DataPoint instance. + protected override void PrepareDataPoint(DataPoint dataPoint) + { + base.PrepareDataPoint(dataPoint); + dataPoint.SizeChanged += new SizeChangedEventHandler(DataPointSizeChanged); + } + + /// + /// Handles the SizeChanged event of a DataPoint to update the value margins for the series. + /// + /// Event source. + /// Event arguments. + private void DataPointSizeChanged(object sender, SizeChangedEventArgs e) + { + DataPoint dataPoint = (DataPoint)sender; + DataItem dataItem = DataItemFromDataPoint(dataPoint); + + // Update placement + double newWidth = e.NewSize.Width; + double newHeight = e.NewSize.Height; + Canvas.SetLeft(dataItem.Container, Math.Round(dataItem.CenterPoint.X - (newWidth / 2))); + Canvas.SetTop(dataItem.Container, Math.Round(dataItem.CenterPoint.Y - (newHeight / 2))); + + // Update value margins + double heightMargin = newHeight * (3.0 / 4.0); + NotifyValueMarginsChanged(ActualDependentAxis, new ValueMargin[] { new ValueMargin(dataItem.ActualStackedDependentValue, heightMargin, heightMargin) }); + double widthMargin = newWidth * (3.0 / 4.0); + NotifyValueMarginsChanged(ActualIndependentAxis, new ValueMargin[] { new ValueMargin(dataPoint.ActualIndependentValue, widthMargin, widthMargin) }); + } + + /// + /// Creates a series-appropriate Shape for connecting the points of the series. + /// + /// Shape instance. + protected abstract Shape CreateDataShape(); + + /// + /// Synchronizes the SeriesDefinitionShapes dictionary with the contents of the SeriesArea Panel. + /// + /// SeriesDefinition being removed. + /// SeriesDefinition being added. + private void SynchronizeSeriesDefinitionShapes(IEnumerable oldDefinitions, IEnumerable newDefinitions) + { + if (null != SeriesArea) + { + if (null != oldDefinitions) + { + foreach (SeriesDefinition oldDefinition in oldDefinitions) + { + SeriesArea.Children.Remove(SeriesDefinitionShapes[oldDefinition]); + } + } + if (null != newDefinitions) + { + foreach (SeriesDefinition newDefinition in newDefinitions.OrderBy(sd => sd.Index)) + { + SeriesArea.Children.Insert(newDefinition.Index, SeriesDefinitionShapes[newDefinition]); + } + } + } + } + + /// + /// Returns the range for the data points of the series. + /// + /// Consumer of the range. + /// Range of values. + protected override Range IRangeProviderGetRange(IRangeConsumer rangeConsumer) + { + if (rangeConsumer == ActualDependentAxis) + { + IEnumerable> dependentValueRangesByIndependentValue = IndependentValueDependentValues + .Select(g => g.Where(d => ValueHelper.CanGraph(d))) + .Select(g => g.Scan(0.0, (s, t) => s + t).Skip(1).GetRange()) + .DefaultIfEmpty(new Range(0, 0)) + .ToArray(); + double minimum = dependentValueRangesByIndependentValue.Min(r => r.Minimum); + double maximum = dependentValueRangesByIndependentValue.Max(r => r.Maximum); + + if (IsStacked100) + { + minimum = Math.Min(minimum, 0); + maximum = Math.Max(maximum, 0); + } + + return new Range(minimum, maximum); + } + else + { + return base.IRangeProviderGetRange(rangeConsumer); + } + } + + /// + /// Returns the value margins for the data points of the series. + /// + /// Consumer of the value margins. + /// Sequence of value margins. + protected override IEnumerable IValueMarginProviderGetValueMargins(IValueMarginConsumer valueMarginConsumer) + { + if (IsStacked100 && (valueMarginConsumer == ActualDependentAxis)) + { + return Enumerable.Empty(); + } + else if ((valueMarginConsumer == ActualDependentAxis) || (valueMarginConsumer == ActualIndependentAxis)) + { + Range range = IRangeProviderGetRange((IRangeConsumer)valueMarginConsumer); + double margin = DataItems + .Select(di => + { + return (null != di.DataPoint) ? + (valueMarginConsumer == ActualDependentAxis) ? di.DataPoint.ActualHeight : di.DataPoint.ActualWidth : + 0; + }) + .Average() * (3.0 / 4.0); + return new ValueMargin[] + { + new ValueMargin(range.Minimum, margin, margin), + new ValueMargin(range.Maximum, margin, margin), + }; + } + else + { + return base.IValueMarginProviderGetValueMargins(valueMarginConsumer); + } + } + + /// + /// Updates the placement of the DataItems (data points) of the series. + /// + /// DataItems in need of an update. + protected override void UpdateDataItemPlacement(IEnumerable dataItems) + { + if ((null != ActualDependentAxis) && (null != ActualIndependentAxis)) + { + double plotAreaMaximumDependentCoordinate = ActualDependentAxis.GetPlotAreaCoordinate(ActualDependentRangeAxis.Range.Maximum).Value; + double lineTopBuffer = 1; + List[] points = new List[SeriesDefinitions.Count]; + for (int i = 0; i < points.Length; i++) + { + points[i] = new List(); + } + foreach (IndependentValueGroup group in IndependentValueGroupsOrderedByIndependentValue) + { + double sum = IsStacked100 ? + group.DataItems.Sum(di => Math.Abs(ValueHelper.ToDouble(di.DataPoint.ActualDependentValue))) : + 1; + if (0 == sum) + { + sum = 1; + } + double x = ActualIndependentAxis.GetPlotAreaCoordinate(group.IndependentValue).Value; + if (ValueHelper.CanGraph(x)) + { + double lastValue = 0; + Point lastPoint = new Point(x, Math.Max(plotAreaMaximumDependentCoordinate - ActualDependentRangeAxis.GetPlotAreaCoordinate(lastValue).Value, lineTopBuffer)); + int i = -1; + SeriesDefinition lastDefinition = null; + foreach (DataItem dataItem in group.DataItems) + { + if (lastDefinition != dataItem.SeriesDefinition) + { + i++; + } + + while (dataItem.SeriesDefinition != SeriesDefinitions[i]) + { + points[i].Add(lastPoint); + i++; + } + + DataPoint dataPoint = dataItem.DataPoint; + double value = IsStacked100 ? + (ValueHelper.ToDouble(dataItem.DataPoint.ActualDependentValue) * (100 / sum)) : + ValueHelper.ToDouble(dataItem.DataPoint.ActualDependentValue); + if (ValueHelper.CanGraph(value)) + { + value += lastValue; + dataItem.ActualStackedDependentValue = value; + double y = ActualDependentRangeAxis.GetPlotAreaCoordinate(value).Value; + lastValue = value; + lastPoint.Y = Math.Max(plotAreaMaximumDependentCoordinate - y, lineTopBuffer); + points[i].Add(lastPoint); + + dataItem.CenterPoint = new Point(x, plotAreaMaximumDependentCoordinate - y); + double left = dataItem.CenterPoint.X - (dataPoint.ActualWidth / 2); + double top = dataItem.CenterPoint.Y - (dataPoint.ActualHeight / 2); + + Canvas.SetLeft(dataItem.Container, Math.Round(left)); + Canvas.SetTop(dataItem.Container, Math.Round(top)); + dataPoint.Visibility = Visibility.Visible; + } + else + { + points[i].Add(lastPoint); + dataPoint.Visibility = Visibility.Collapsed; + } + + lastDefinition = dataItem.SeriesDefinition; + } + } + else + { + foreach (DataPoint dataPoint in group.DataItems.Select(di => di.DataPoint)) + { + dataPoint.Visibility = Visibility.Collapsed; + } + } + } + UpdateShape(points); + } + } + + /// + /// Updates the Shape for the series. + /// + /// Locations of the points of each SeriesDefinition in the series. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nesting is convenient way to represent data.")] + protected abstract void UpdateShape(IList> definitionPoints); + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/StackedAreaSeries.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/StackedAreaSeries.cs new file mode 100644 index 00000000..3edee997 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/StackedAreaSeries.cs @@ -0,0 +1,143 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Windows.Media; +using System.Windows.Shapes; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Control that displays values as a stacked area chart visualization. + /// + /// Preview + public class StackedAreaSeries : StackedAreaLineSeries, IAnchoredToOrigin + { + /// + /// Initializes a new instance of the StackedAreaSeries class. + /// + public StackedAreaSeries() + { + } + + /// + /// Creates a DataPoint for the series. + /// + /// Series-appropriate DataPoint instance. + protected override DataPoint CreateDataPoint() + { + return new AreaDataPoint(); + } + + /// + /// Creates a series-appropriate Shape for connecting the points of the series. + /// + /// Shape instance. + protected override Shape CreateDataShape() + { + return new Polygon(); + } + + /// + /// Updates the Shape for the series. + /// + /// Locations of the points of each SeriesDefinition in the series. + protected override void UpdateShape(IList> definitionPoints) + { + for (int i = SeriesDefinitions.Count - 1; 0 < i; i--) + { + PointCollection pointCollection = new PointCollection(); + IEnumerable topPoints = (ActualIndependentAxis is ICategoryAxis) ? definitionPoints[i].OrderBy(p => p.X) : definitionPoints[i]; + foreach (Point p in topPoints) + { + pointCollection.Add(p); + } + IEnumerable bottomPoints = (ActualIndependentAxis is ICategoryAxis) ? definitionPoints[i - 1].OrderByDescending(p => p.X) : definitionPoints[i - 1].Reverse(); + foreach (Point p in bottomPoints) + { + pointCollection.Add(p); + } + SetPolygonPointsProperty((Polygon)SeriesDefinitionShapes[SeriesDefinitions[i]], pointCollection); + } + if (1 <= SeriesDefinitions.Count) + { + double plotAreaMaximumDependentCoordinate = ActualDependentAxis.GetPlotAreaCoordinate(ActualDependentRangeAxis.Range.Maximum).Value; + IComparable zeroValue = ActualDependentRangeAxis.Origin ?? 0.0; + if (zeroValue.CompareTo(ActualDependentRangeAxis.Range.Minimum) < 0) + { + zeroValue = ActualDependentRangeAxis.Range.Minimum; + } + if (0 < zeroValue.CompareTo(ActualDependentRangeAxis.Range.Maximum)) + { + zeroValue = ActualDependentRangeAxis.Range.Maximum; + } + double zeroCoordinate = ActualDependentAxis.GetPlotAreaCoordinate(zeroValue).Value; + PointCollection pointCollection = new PointCollection(); + Point[] topPoints = ((ActualIndependentAxis is ICategoryAxis) ? definitionPoints[0].OrderBy(p => p.X) : definitionPoints[0]).ToArray(); + foreach (Point p in topPoints) + { + pointCollection.Add(p); + } + if (0 < topPoints.Length) + { + Point firstPoint = topPoints[0]; + Point lastPoint = topPoints[topPoints.Length - 1]; + pointCollection.Add(new Point(lastPoint.X, plotAreaMaximumDependentCoordinate - zeroCoordinate)); + pointCollection.Add(new Point(firstPoint.X, plotAreaMaximumDependentCoordinate - zeroCoordinate)); + } + SetPolygonPointsProperty((Polygon)SeriesDefinitionShapes[SeriesDefinitions[0]], pointCollection); + } + } + + /// + /// Sets the Points property of a Polygon to the specified PointCollection. + /// + /// Polygon to set the Points property of. + /// Specified PointCollection. + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Silverlight implementation is not static.")] + protected void SetPolygonPointsProperty(Polygon polygon, PointCollection pointCollection) + { +#if SILVERLIGHT + // Changing .Points during an Arrange pass can create a layout cycle on Silverlight + if (!polygon.Points.SequenceEqual(pointCollection)) + { +#endif + polygon.Points = pointCollection; +#if SILVERLIGHT + // In rare cases, Silverlight doesn't update the line visual to match the new points; + // calling InvalidateArrange works around that problem. + polygon.InvalidateArrange(); + } +#endif + } + + /// + /// Returns the value margins for the data points of the series. + /// + /// Consumer of the value margins. + /// Sequence of value margins. + protected override IEnumerable IValueMarginProviderGetValueMargins(IValueMarginConsumer valueMarginConsumer) + { + if (valueMarginConsumer == ActualIndependentAxis) + { + return Enumerable.Empty(); + } + else + { + return base.IValueMarginProviderGetValueMargins(valueMarginConsumer); + } + } + + /// + /// Gets the anchored axis for the series. + /// + IRangeAxis IAnchoredToOrigin.AnchoredAxis + { + get { return ActualDependentRangeAxis; } + } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/StackedBarColumnSeries.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/StackedBarColumnSeries.cs new file mode 100644 index 00000000..9dc617a2 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/StackedBarColumnSeries.cs @@ -0,0 +1,371 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Control base class for displaying values as a stacked bar/column chart visualization. + /// + /// Preview + public abstract class StackedBarColumnSeries : DefinitionSeries, IAnchoredToOrigin + { + /// + /// Gets or sets the orientation of the dependent axis. + /// + protected AxisOrientation DependentAxisOrientation { get; set; } + + /// + /// Gets or sets the orientation of the independent axis. + /// + protected AxisOrientation IndependentAxisOrientation { get; set; } + + /// + /// Initializes a new instance of the StackedBarColumnSeries class. + /// + protected StackedBarColumnSeries() + { + } + + /// + /// Acquires a dependent axis suitable for use with the data values of the series. + /// + /// Axis instance. + protected override IAxis AcquireDependentAxis() + { + IAxis dependentAxis = SeriesHost.Axes + .Where(a => (a.Orientation == DependentAxisOrientation) && (a is IRangeAxis) && DataItems.Any() && (a.CanPlot(DataItems.First().ActualDependentValue))) + .FirstOrDefault(); + if (null == dependentAxis) + { + LinearAxis linearAxis = new LinearAxis { Orientation = DependentAxisOrientation, ShowGridLines = true }; + if (IsStacked100) + { + Style style = new Style(typeof(AxisLabel)); + style.Setters.Add(new Setter(AxisLabel.StringFormatProperty, "{0}%")); + linearAxis.AxisLabelStyle = style; + } + dependentAxis = linearAxis; + } + return dependentAxis; + } + + /// + /// Acquires an independent axis suitable for use with the data values of the series. + /// + /// Axis instance. + protected override IAxis AcquireIndependentAxis() + { + IAxis independentAxis = SeriesHost.Axes + .Where(a => (a.Orientation == IndependentAxisOrientation) && ((a is ICategoryAxis) || (a is IRangeAxis)) && DataItems.Any() && (a.CanPlot(DataItems.First().ActualIndependentValue))) + .FirstOrDefault(); + if (null == independentAxis) + { + independentAxis = new CategoryAxis { Orientation = IndependentAxisOrientation }; + } + return independentAxis; + } + + /// + /// Returns the range for the data points of the series. + /// + /// Consumer of the range. + /// Range of values. + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Linq is artificially increasing the rating.")] + protected override Range IRangeProviderGetRange(IRangeConsumer rangeConsumer) + { + if (rangeConsumer == ActualDependentAxis) + { + var dependentValuesByIndependentValue = IndependentValueDependentValues.Select(e => e.ToArray()).ToArray(); + + var mostNegative = dependentValuesByIndependentValue + .Select(g => g.Where(v => v < 0) + .Sum()) + .Where(v => v < 0) + .ToArray(); + var leastNegative = dependentValuesByIndependentValue + .Select(g => g.Where(v => v <= 0) + .DefaultIfEmpty(1.0) + .First()) + .Where(v => v <= 0) + .ToArray(); + var mostPositive = dependentValuesByIndependentValue + .Select(g => g.Where(v => 0 < v) + .Sum()) + .Where(v => 0 < v) + .ToArray(); + var leastPositive = dependentValuesByIndependentValue + .Select(g => g.Where(v => 0 <= v) + .DefaultIfEmpty(-1.0) + .First()) + .Where(v => 0 <= v) + .ToArray(); + + // Compute minimum + double minimum = 0; + if (mostNegative.Any()) + { + minimum = mostNegative.Min(); + } + else if (leastPositive.Any()) + { + minimum = leastPositive.Min(); + } + + // Compute maximum + double maximum = 0; + if (mostPositive.Any()) + { + maximum = mostPositive.Max(); + } + else if (leastNegative.Any()) + { + maximum = leastNegative.Max(); + } + + if (IsStacked100) + { + minimum = Math.Min(minimum, 0); + maximum = Math.Max(maximum, 0); + } + + return new Range(minimum, maximum); + } + else if (rangeConsumer == ActualIndependentAxis) + { + // Using a non-ICategoryAxis for the independent axis + // Need to specifically adjust for slot size of bars/columns so they don't overlap + // Note: Calculation for slotSize is not perfect, but it's quick, close, and errs on the safe side + Range range = base.IRangeProviderGetRange(rangeConsumer); + int count = Math.Max(IndependentValueGroups.Count(), 1); + if (ActualIndependentAxis.CanPlot(0.0)) + { + double minimum = ValueHelper.ToDouble(range.Minimum); + double maximum = ValueHelper.ToDouble(range.Maximum); + double slotSize = (maximum - minimum) / count; + return new Range(minimum - slotSize, maximum + slotSize); + } + else + { + DateTime minimum = ValueHelper.ToDateTime(range.Minimum); + DateTime maximum = ValueHelper.ToDateTime(range.Maximum); + TimeSpan slotSize = TimeSpan.FromTicks((maximum - minimum).Ticks / count); + return new Range(minimum - slotSize, maximum + slotSize); + } + } + else + { + return base.IRangeProviderGetRange(rangeConsumer); + } + } + + /// + /// Returns the value margins for the data points of the series. + /// + /// Consumer of the value margins. + /// Sequence of value margins. + protected override IEnumerable IValueMarginProviderGetValueMargins(IValueMarginConsumer valueMarginConsumer) + { + if (valueMarginConsumer == ActualDependentAxis) + { + if (IsStacked100) + { + return Enumerable.Empty(); + } + else + { + Range range = IRangeProviderGetRange((IRangeConsumer)ActualDependentAxis); + double margin = ((AxisOrientation.Y == ActualDependentAxis.Orientation) ? ActualHeight : ActualWidth) / 10; + return new ValueMargin[] + { + new ValueMargin(range.Minimum, margin, margin), + new ValueMargin(range.Maximum, margin, margin), + }; + } + } + else if (valueMarginConsumer == ActualIndependentAxis) + { + // Using a non-ICategoryAxis for the independent axis + // Relevant space already accounted for by IRangeProviderGetRange + return Enumerable.Empty(); + } + else + { + return base.IValueMarginProviderGetValueMargins(valueMarginConsumer); + } + } + + /// + /// Updates the placement of the DataItems (data points) of the series. + /// + /// DataItems in need of an update. + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Linq is artificially increasing the rating.")] + protected override void UpdateDataItemPlacement(IEnumerable dataItems) + { + IAxis actualIndependentAxis = ActualIndependentAxis; + if ((null != ActualDependentAxis) && (null != actualIndependentAxis)) + { + double plotAreaMaximumDependentCoordinate = ActualDependentAxis.GetPlotAreaCoordinate(ActualDependentRangeAxis.Range.Maximum).Value; + double zeroCoordinate = ActualDependentAxis.GetPlotAreaCoordinate(ActualDependentRangeAxis.Origin ?? 0.0).Value; + ICategoryAxis actualIndependentCategoryAxis = actualIndependentAxis as ICategoryAxis; + double nonCategoryAxisRangeMargin = (null != actualIndependentCategoryAxis) ? 0 : GetMarginForNonCategoryAxis(actualIndependentAxis); + foreach (IndependentValueGroup group in IndependentValueGroups) + { + Range categoryRange = new Range(); + if (null != actualIndependentCategoryAxis) + { + categoryRange = actualIndependentCategoryAxis.GetPlotAreaCoordinateRange(group.IndependentValue); + } + else + { + UnitValue independentValueCoordinate = actualIndependentAxis.GetPlotAreaCoordinate(group.IndependentValue); + if (ValueHelper.CanGraph(independentValueCoordinate.Value)) + { + categoryRange = new Range(new UnitValue(independentValueCoordinate.Value - nonCategoryAxisRangeMargin, independentValueCoordinate.Unit), new UnitValue(independentValueCoordinate.Value + nonCategoryAxisRangeMargin, independentValueCoordinate.Unit)); + } + } + if (categoryRange.HasData) + { + double categoryMinimumCoordinate = categoryRange.Minimum.Value; + double categoryMaximumCoordinate = categoryRange.Maximum.Value; + double padding = 0.1 * (categoryMaximumCoordinate - categoryMinimumCoordinate); + categoryMinimumCoordinate += padding; + categoryMaximumCoordinate -= padding; + + double sum = IsStacked100 ? + group.DataItems.Sum(di => Math.Abs(ValueHelper.ToDouble(di.DataPoint.ActualDependentValue))) : + 1; + if (0 == sum) + { + sum = 1; + } + double ceiling = 0; + double floor = 0; + foreach (DataItem dataItem in group.DataItems) + { + DataPoint dataPoint = dataItem.DataPoint; + double value = IsStacked100 ? (ValueHelper.ToDouble(dataPoint.ActualDependentValue) * (100 / sum)) : ValueHelper.ToDouble(dataPoint.ActualDependentValue); + if (ValueHelper.CanGraph(value)) + { + double valueCoordinate = ActualDependentAxis.GetPlotAreaCoordinate(value).Value; + double fillerCoordinate = (0 <= value) ? ceiling : floor; + + double topCoordinate = 0, leftCoordinate = 0, height = 0, width = 0, deltaCoordinate = 0; + if (AxisOrientation.Y == ActualDependentAxis.Orientation) + { + topCoordinate = plotAreaMaximumDependentCoordinate - Math.Max(valueCoordinate + fillerCoordinate, zeroCoordinate + fillerCoordinate); + double bottomCoordinate = plotAreaMaximumDependentCoordinate - Math.Min(valueCoordinate + fillerCoordinate, zeroCoordinate + fillerCoordinate); + deltaCoordinate = bottomCoordinate - topCoordinate; + height = (0 < deltaCoordinate) ? deltaCoordinate + 1 : 0; + leftCoordinate = categoryMinimumCoordinate; + width = categoryMaximumCoordinate - categoryMinimumCoordinate + 1; + } + else + { + leftCoordinate = Math.Min(valueCoordinate + fillerCoordinate, zeroCoordinate + fillerCoordinate); + double rightCoordinate = Math.Max(valueCoordinate + fillerCoordinate, zeroCoordinate + fillerCoordinate); + deltaCoordinate = rightCoordinate - leftCoordinate; + width = (0 < deltaCoordinate) ? deltaCoordinate + 1 : 0; + topCoordinate = categoryMinimumCoordinate; + height = categoryMaximumCoordinate - categoryMinimumCoordinate + 1; + } + + double roundedTopCoordinate = Math.Round(topCoordinate); + Canvas.SetTop(dataItem.Container, roundedTopCoordinate); + dataPoint.Height = Math.Round(topCoordinate + height - roundedTopCoordinate); + double roundedLeftCoordinate = Math.Round(leftCoordinate); + Canvas.SetLeft(dataItem.Container, roundedLeftCoordinate); + dataPoint.Width = Math.Round(leftCoordinate + width - roundedLeftCoordinate); + dataPoint.Visibility = Visibility.Visible; + + if (0 <= value) + { + ceiling += deltaCoordinate; + } + else + { + floor -= deltaCoordinate; + } + } + else + { + dataPoint.Visibility = Visibility.Collapsed; + } + } + } + else + { + foreach (DataPoint dataPoint in group.DataItems.Select(di => di.DataPoint)) + { + dataPoint.Visibility = Visibility.Collapsed; + } + } + } + } + } + + /// + /// Gets the margin to use for an independent axis that does not implement ICategoryAxis. + /// + /// Axis to get the margin for. + /// Margin for axis. + private double GetMarginForNonCategoryAxis(IAxis axis) + { + Debug.Assert(!(axis is ICategoryAxis), "This method is unnecessary for ICategoryAxis."); + + // Find the smallest distance between two independent value plot area coordinates + double smallestDistance = double.MaxValue; + double lastCoordinate = double.NaN; + foreach (double coordinate in + IndependentValueGroupsOrderedByIndependentValue + .Select(g => axis.GetPlotAreaCoordinate(g.IndependentValue).Value) + .Where(v => ValueHelper.CanGraph(v))) + { + if (!double.IsNaN(lastCoordinate)) + { + double distance = coordinate - lastCoordinate; + if (distance < smallestDistance) + { + smallestDistance = distance; + } + } + lastCoordinate = coordinate; + } + // Return the margin + if (double.MaxValue == smallestDistance) + { + // No smallest distance because <= 1 independent values to plot + FrameworkElement element = axis as FrameworkElement; + if (null != element) + { + // Use width of provided axis so single column scenario looks good + return element.GetMargin(axis); + } + else + { + // No information to work with; no idea what margin to return + throw new NotSupportedException(); + } + } + else + { + // Found the smallest distance; margin is half of that + return smallestDistance / 2; + } + } + + /// + /// Gets the anchored axis for the series. + /// + IRangeAxis IAnchoredToOrigin.AnchoredAxis + { + get { return ActualDependentRangeAxis; } + } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/StackedBarSeries.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/StackedBarSeries.cs new file mode 100644 index 00000000..d451e034 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/StackedBarSeries.cs @@ -0,0 +1,32 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Control that displays values as a stacked bar chart visualization. + /// + /// Preview + public class StackedBarSeries : StackedBarColumnSeries + { + /// + /// Initializes a new instance of the StackedBarSeries class. + /// + public StackedBarSeries() + { + DependentAxisOrientation = AxisOrientation.X; + IndependentAxisOrientation = AxisOrientation.Y; + } + + /// + /// Creates a DataPoint for the series. + /// + /// Series-appropriate DataPoint instance. + protected override DataPoint CreateDataPoint() + { + return new BarDataPoint(); + } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/StackedColumnSeries.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/StackedColumnSeries.cs new file mode 100644 index 00000000..8f9bab24 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/StackedColumnSeries.cs @@ -0,0 +1,32 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Control that displays values as a stacked column chart visualization. + /// + /// Preview + public class StackedColumnSeries : StackedBarColumnSeries + { + /// + /// Initializes a new instance of the StackedColumnSeries class. + /// + public StackedColumnSeries() + { + DependentAxisOrientation = AxisOrientation.Y; + IndependentAxisOrientation = AxisOrientation.X; + } + + /// + /// Creates a DataPoint for the series. + /// + /// Series-appropriate DataPoint instance. + protected override DataPoint CreateDataPoint() + { + return new ColumnDataPoint(); + } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/StackedLineSeries.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/StackedLineSeries.cs new file mode 100644 index 00000000..ed078c93 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/Series/StackedLineSeries.cs @@ -0,0 +1,86 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Windows.Media; +using System.Windows.Shapes; + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// Control that displays values as a stacked line chart visualization. + /// + /// Preview + public class StackedLineSeries : StackedAreaLineSeries + { + /// + /// Initializes a new instance of the StackedLineSeries class. + /// + public StackedLineSeries() + { + } + + /// + /// Creates a DataPoint for the series. + /// + /// Series-appropriate DataPoint instance. + protected override DataPoint CreateDataPoint() + { + return new LineDataPoint(); + } + + /// + /// Creates a series-appropriate Shape for connecting the points of the series. + /// + /// Shape instance. + protected override Shape CreateDataShape() + { + return new Polyline { Fill = null }; + } + + /// + /// Updates the shape for the series. + /// + /// Locations of the points of each SeriesDefinition in the series. + protected override void UpdateShape(IList> definitionPoints) + { + for (int i = 0; i < SeriesDefinitions.Count; i++) + { + PointCollection pointCollection = new PointCollection(); + foreach (Point p in ((ActualIndependentAxis is ICategoryAxis) ? definitionPoints[i].OrderBy(p => p.X) : definitionPoints[i])) + { + pointCollection.Add(p); + } + SetPolylinePointsProperty((Polyline)SeriesDefinitionShapes[SeriesDefinitions[i]], pointCollection); + } + } + + /// + /// Sets the Points property of a Polyline to the specified PointCollection. + /// + /// Polyline to set the Points property of. + /// Specified PointCollection. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Polyline", Justification = "Matches spelling of same-named framework class.")] + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "polyline", Justification = "Matches spelling of same-named framework class.")] + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Silverlight implementation is not static.")] + protected void SetPolylinePointsProperty(Polyline polyline, PointCollection pointCollection) + { +#if SILVERLIGHT + // Changing .Points during an Arrange pass can create a layout cycle on Silverlight + if (!polyline.Points.SequenceEqual(pointCollection)) + { +#endif + polyline.Points = pointCollection; +#if SILVERLIGHT + // In rare cases, Silverlight doesn't update the line visual to match the new points; + // calling InvalidateArrange works around that problem. + polyline.InvalidateArrange(); + } +#endif + } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/ValueMarginCoordinateAndOverlap.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/ValueMarginCoordinateAndOverlap.cs new file mode 100644 index 00000000..45e6ba27 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Charting/ValueMarginCoordinateAndOverlap.cs @@ -0,0 +1,41 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization.Charting +{ + /// + /// A class used to calculate axis range. + /// + internal class ValueMarginCoordinateAndOverlap + { + /// + /// Gets or sets the value margin object. + /// + public ValueMargin ValueMargin { get; set; } + + /// + /// Gets or sets the coordinate. + /// + public double Coordinate { get; set; } + + /// + /// Gets or sets the left overlap. + /// + public double LeftOverlap { get; set; } + + /// + /// Gets or sets the right overlap. + /// + public double RightOverlap { get; set; } + + /// + /// Initializes a new instance of the ValueMarginCoordinateAndOverlap + /// class. + /// + public ValueMarginCoordinateAndOverlap() + { + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Collections/LeftLeaningRedBlackTree.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Collections/LeftLeaningRedBlackTree.cs new file mode 100644 index 00000000..f99aabaf --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Collections/LeftLeaningRedBlackTree.cs @@ -0,0 +1,815 @@ +// (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. + +// Uncomment this to enable the following debugging aids: +// LeftLeaningRedBlackTree.HtmlFragment +// LeftLeaningRedBlackTree.Node.HtmlFragment +// LeftLeaningRedBlackTree.AssertInvariants +// #define DEBUGGING + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +namespace System.Windows.Controls.DataVisualization.Collections +{ + /// + /// Implements a left-leaning red-black tree. + /// + /// + /// Based on the research paper "Left-leaning Red-Black Trees" + /// by Robert Sedgewick. More information available at: + /// http://www.cs.princeton.edu/~rs/talks/LLRB/RedBlack.pdf + /// http://www.cs.princeton.edu/~rs/talks/LLRB/08Penn.pdf + /// + /// Type of keys. + /// Type of values. + internal class LeftLeaningRedBlackTree + { + /// + /// Stores the key comparison function. + /// + private Comparison _keyComparison; + + /// + /// Stores the value comparison function. + /// + private Comparison _valueComparison; + + /// + /// Stores the root node of the tree. + /// + private Node _rootNode; + + /// + /// Represents a node of the tree. + /// + /// + /// Using fields instead of properties drops execution time by about 40%. + /// + [DebuggerDisplay("Key={Key}, Value={Value}, Siblings={Siblings}")] + private class Node + { + /// + /// Gets or sets the node's key. + /// + public TKey Key; + + /// + /// Gets or sets the node's value. + /// + public TValue Value; + + /// + /// Gets or sets the left node. + /// + public Node Left; + + /// + /// Gets or sets the right node. + /// + public Node Right; + + /// + /// Gets or sets the color of the node. + /// + public bool IsBlack; + + /// + /// Gets or sets the number of "siblings" (nodes with the same key/value). + /// + public int Siblings; + +#if DEBUGGING + /// + /// Gets an HTML fragment representing the node and its children. + /// + public string HtmlFragment + { + get + { + return + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "
" + Key + ", " + Value + " [" + Siblings + "]
" + (null != Left ? Left.HtmlFragment : "[null]") + "" + (null != Right ? Right.HtmlFragment : "[null]") + "
"; + } + } +#endif + } + + /// + /// Initializes a new instance of the LeftLeaningRedBlackTree class implementing a normal dictionary. + /// + /// The key comparison function. + public LeftLeaningRedBlackTree(Comparison keyComparison) + { + if (null == keyComparison) + { + throw new ArgumentNullException("keyComparison"); + } + _keyComparison = keyComparison; + } + + /// + /// Initializes a new instance of the LeftLeaningRedBlackTree class implementing an ordered multi-dictionary. + /// + /// The key comparison function. + /// The value comparison function. + public LeftLeaningRedBlackTree(Comparison keyComparison, Comparison valueComparison) + : this(keyComparison) + { + if (null == valueComparison) + { + throw new ArgumentNullException("valueComparison"); + } + _valueComparison = valueComparison; + } + + /// + /// Gets a value indicating whether the tree is acting as an ordered multi-dictionary. + /// + private bool IsMultiDictionary + { + get { return null != _valueComparison; } + } + + /// + /// Adds a key/value pair to the tree. + /// + /// Key to add. + /// Value to add. + public void Add(TKey key, TValue value) + { + _rootNode = Add(_rootNode, key, value); + _rootNode.IsBlack = true; +#if DEBUGGING + AssertInvariants(); +#endif + } + + /// + /// Removes a key (and its associated value) from a normal (non-multi) dictionary. + /// + /// Key to remove. + /// True if key present and removed. + public bool Remove(TKey key) + { + if (IsMultiDictionary) + { + throw new InvalidOperationException("Remove is only supported when acting as a normal (non-multi) dictionary."); + } + return Remove(key, default(TValue)); + } + + /// + /// Removes a key/value pair from the tree. + /// + /// Key to remove. + /// Value to remove. + /// True if key/value present and removed. + public bool Remove(TKey key, TValue value) + { + int initialCount = Count; + if (null != _rootNode) + { + _rootNode = Remove(_rootNode, key, value); + if (null != _rootNode) + { + _rootNode.IsBlack = true; + } + } +#if DEBUGGING + AssertInvariants(); +#endif + return initialCount != Count; + } + + /// + /// Removes all nodes in the tree. + /// + public void Clear() + { + _rootNode = null; + Count = 0; +#if DEBUGGING + AssertInvariants(); +#endif + } + + /// + /// Gets a sorted list of keys in the tree. + /// + /// Sorted list of keys. + public IEnumerable GetKeys() + { + TKey lastKey = default(TKey); + bool lastKeyValid = false; + return Traverse( + _rootNode, + n => !lastKeyValid || !object.Equals(lastKey, n.Key), + n => + { + lastKey = n.Key; + lastKeyValid = true; + return lastKey; + }); + } + + /// + /// Gets the value associated with the specified key in a normal (non-multi) dictionary. + /// + /// Specified key. + /// Value associated with the specified key. + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "GetValueForKey", Justification = "Method name.")] + public TValue GetValueForKey(TKey key) + { + if (IsMultiDictionary) + { + throw new InvalidOperationException("GetValueForKey is only supported when acting as a normal (non-multi) dictionary."); + } + Node node = GetNodeForKey(key); + if (null != node) + { + return node.Value; + } + else + { + throw new KeyNotFoundException(); + } + } + + /// + /// Gets a sequence of the values associated with the specified key. + /// + /// Specified key. + /// Sequence of values. + public IEnumerable GetValuesForKey(TKey key) + { + return Traverse(GetNodeForKey(key), n => 0 == _keyComparison(n.Key, key), n => n.Value); + } + + /// + /// Gets a sequence of all the values in the tree. + /// + /// Sequence of all values. + public IEnumerable GetValuesForAllKeys() + { + return Traverse(_rootNode, n => true, n => n.Value); + } + + /// + /// Gets the count of key/value pairs in the tree. + /// + public int Count { get; private set; } + + /// + /// Gets the minimum key in the tree. + /// + public TKey MinimumKey + { + get { return GetExtreme(_rootNode, n => n.Left, n => n.Key); } + } + + /// + /// Gets the maximum key in the tree. + /// + public TKey MaximumKey + { + get { return GetExtreme(_rootNode, n => n.Right, n => n.Key); } + } + + /// + /// Gets the minimum key's minimum value. + /// + public TValue MinimumValue + { + get { return GetExtreme(_rootNode, n => n.Left, n => n.Value); } + } + + /// + /// Gets the maximum key's maximum value. + /// + public TValue MaximumValue + { + get { return GetExtreme(_rootNode, n => n.Right, n => n.Value); } + } + + /// + /// Returns true if the specified node is red. + /// + /// Specified node. + /// True if specified node is red. + private static bool IsRed(Node node) + { + if (null == node) + { + // "Virtual" leaf nodes are always black + return false; + } + return !node.IsBlack; + } + + /// + /// Adds the specified key/value pair below the specified root node. + /// + /// Specified node. + /// Key to add. + /// Value to add. + /// New root node. + private Node Add(Node node, TKey key, TValue value) + { + if (null == node) + { + // Insert new node + Count++; + return new Node { Key = key, Value = value }; + } + + if (IsRed(node.Left) && IsRed(node.Right)) + { + // Split node with two red children + FlipColor(node); + } + + // Find right place for new node + int comparisonResult = KeyAndValueComparison(key, value, node.Key, node.Value); + if (comparisonResult < 0) + { + node.Left = Add(node.Left, key, value); + } + else if (0 < comparisonResult) + { + node.Right = Add(node.Right, key, value); + } + else + { + if (IsMultiDictionary) + { + // Store the presence of a "duplicate" node + node.Siblings++; + Count++; + } + else + { + // Replace the value of the existing node + node.Value = value; + } + } + + if (IsRed(node.Right)) + { + // Rotate to prevent red node on right + node = RotateLeft(node); + } + + if (IsRed(node.Left) && IsRed(node.Left.Left)) + { + // Rotate to prevent consecutive red nodes + node = RotateRight(node); + } + + return node; + } + + /// + /// Removes the specified key/value pair from below the specified node. + /// + /// Specified node. + /// Key to remove. + /// Value to remove. + /// True if key/value present and removed. + private Node Remove(Node node, TKey key, TValue value) + { + int comparisonResult = KeyAndValueComparison(key, value, node.Key, node.Value); + if (comparisonResult < 0) + { + // * Continue search if left is present + if (null != node.Left) + { + if (!IsRed(node.Left) && !IsRed(node.Left.Left)) + { + // Move a red node over + node = MoveRedLeft(node); + } + + // Remove from left + node.Left = Remove(node.Left, key, value); + } + } + else + { + if (IsRed(node.Left)) + { + // Flip a 3 node or unbalance a 4 node + node = RotateRight(node); + } + if ((0 == KeyAndValueComparison(key, value, node.Key, node.Value)) && (null == node.Right)) + { + // Remove leaf node + Debug.Assert(null == node.Left, "About to remove an extra node."); + Count--; + if (0 < node.Siblings) + { + // Record the removal of the "duplicate" node + Debug.Assert(IsMultiDictionary, "Should not have siblings if tree is not a multi-dictionary."); + node.Siblings--; + return node; + } + else + { + // Leaf node is gone + return null; + } + } + // * Continue search if right is present + if (null != node.Right) + { + if (!IsRed(node.Right) && !IsRed(node.Right.Left)) + { + // Move a red node over + node = MoveRedRight(node); + } + if (0 == KeyAndValueComparison(key, value, node.Key, node.Value)) + { + // Remove leaf node + Count--; + if (0 < node.Siblings) + { + // Record the removal of the "duplicate" node + Debug.Assert(IsMultiDictionary, "Should not have siblings if tree is not a multi-dictionary."); + node.Siblings--; + } + else + { + // Find the smallest node on the right, swap, and remove it + Node m = GetExtreme(node.Right, n => n.Left, n => n); + node.Key = m.Key; + node.Value = m.Value; + node.Siblings = m.Siblings; + node.Right = DeleteMinimum(node.Right); + } + } + else + { + // Remove from right + node.Right = Remove(node.Right, key, value); + } + } + } + + // Maintain invariants + return FixUp(node); + } + + /// + /// Flip the colors of the specified node and its direct children. + /// + /// Specified node. + private static void FlipColor(Node node) + { + node.IsBlack = !node.IsBlack; + node.Left.IsBlack = !node.Left.IsBlack; + node.Right.IsBlack = !node.Right.IsBlack; + } + + /// + /// Rotate the specified node "left". + /// + /// Specified node. + /// New root node. + private static Node RotateLeft(Node node) + { + Node x = node.Right; + node.Right = x.Left; + x.Left = node; + x.IsBlack = node.IsBlack; + node.IsBlack = false; + return x; + } + + /// + /// Rotate the specified node "right". + /// + /// Specified node. + /// New root node. + private static Node RotateRight(Node node) + { + Node x = node.Left; + node.Left = x.Right; + x.Right = node; + x.IsBlack = node.IsBlack; + node.IsBlack = false; + return x; + } + + /// + /// Moves a red node from the right child to the left child. + /// + /// Parent node. + /// New root node. + private static Node MoveRedLeft(Node node) + { + FlipColor(node); + if (IsRed(node.Right.Left)) + { + node.Right = RotateRight(node.Right); + node = RotateLeft(node); + FlipColor(node); + + // * Avoid creating right-leaning nodes + if (IsRed(node.Right.Right)) + { + node.Right = RotateLeft(node.Right); + } + } + return node; + } + + /// + /// Moves a red node from the left child to the right child. + /// + /// Parent node. + /// New root node. + private static Node MoveRedRight(Node node) + { + FlipColor(node); + if (IsRed(node.Left.Left)) + { + node = RotateRight(node); + FlipColor(node); + } + return node; + } + + /// + /// Deletes the minimum node under the specified node. + /// + /// Specified node. + /// New root node. + private Node DeleteMinimum(Node node) + { + if (null == node.Left) + { + // Nothing to do + return null; + } + + if (!IsRed(node.Left) && !IsRed(node.Left.Left)) + { + // Move red node left + node = MoveRedLeft(node); + } + + // Recursively delete + node.Left = DeleteMinimum(node.Left); + + // Maintain invariants + return FixUp(node); + } + + /// + /// Maintains invariants by adjusting the specified nodes children. + /// + /// Specified node. + /// New root node. + private static Node FixUp(Node node) + { + if (IsRed(node.Right)) + { + // Avoid right-leaning node + node = RotateLeft(node); + } + + if (IsRed(node.Left) && IsRed(node.Left.Left)) + { + // Balance 4-node + node = RotateRight(node); + } + + if (IsRed(node.Left) && IsRed(node.Right)) + { + // Push red up + FlipColor(node); + } + + // * Avoid leaving behind right-leaning nodes + if ((null != node.Left) && IsRed(node.Left.Right) && !IsRed(node.Left.Left)) + { + node.Left = RotateLeft(node.Left); + if (IsRed(node.Left)) + { + // Balance 4-node + node = RotateRight(node); + } + } + + return node; + } + + /// + /// Gets the (first) node corresponding to the specified key. + /// + /// Key to search for. + /// Corresponding node or null if none found. + private Node GetNodeForKey(TKey key) + { + // Initialize + Node node = _rootNode; + while (null != node) + { + // Compare keys and go left/right + int comparisonResult = _keyComparison(key, node.Key); + if (comparisonResult < 0) + { + node = node.Left; + } + else if (0 < comparisonResult) + { + node = node.Right; + } + else + { + // Match; return node + return node; + } + } + + // No match found + return null; + } + + /// + /// Gets an extreme (ex: minimum/maximum) value. + /// + /// Type of value. + /// Node to start from. + /// Successor function. + /// Selector function. + /// Extreme value. + private static T GetExtreme(Node node, Func successor, Func selector) + { + // Initialize + T extreme = default(T); + Node current = node; + while (null != current) + { + // Go to extreme + extreme = selector(current); + current = successor(current); + } + return extreme; + } + + /// + /// Traverses a subset of the sequence of nodes in order and selects the specified nodes. + /// + /// Type of elements. + /// Starting node. + /// Condition method. + /// Selector method. + /// Sequence of selected nodes. + private IEnumerable Traverse(Node node, Func condition, Func selector) + { + // Create a stack to avoid recursion + Stack stack = new Stack(); + Node current = node; + while (null != current) + { + if (null != current.Left) + { + // Save current state and go left + stack.Push(current); + current = current.Left; + } + else + { + do + { + for (int i = 0; i <= current.Siblings; i++) + { + // Select current node if relevant + if (condition(current)) + { + yield return selector(current); + } + } + // Go right - or up if nothing to the right + current = current.Right; + } + while ((null == current) && + (0 < stack.Count) && + (null != (current = stack.Pop()))); + } + } + } + + /// + /// Compares the specified keys (primary) and values (secondary). + /// + /// The left key. + /// The left value. + /// The right key. + /// The right value. + /// CompareTo-style results: -1 if left is less, 0 if equal, and 1 if greater than right. + private int KeyAndValueComparison(TKey leftKey, TValue leftValue, TKey rightKey, TValue rightValue) + { + // Compare keys + int comparisonResult = _keyComparison(leftKey, rightKey); + if ((0 == comparisonResult) && (null != _valueComparison)) + { + // Keys match; compare values + comparisonResult = _valueComparison(leftValue, rightValue); + } + return comparisonResult; + } + +#if DEBUGGING + /// + /// Asserts that tree invariants are not violated. + /// + private void AssertInvariants() + { + // Root is black + Debug.Assert((null == _rootNode) || _rootNode.IsBlack, "Root is not black"); + // Every path contains the same number of black nodes + Dictionary parents = new Dictionary.Node, LeftLeaningRedBlackTree.Node>(); + foreach (Node node in Traverse(_rootNode, n => true, n => n)) + { + if (null != node.Left) + { + parents[node.Left] = node; + } + if (null != node.Right) + { + parents[node.Right] = node; + } + } + if (null != _rootNode) + { + parents[_rootNode] = null; + } + int treeCount = -1; + foreach (Node node in Traverse(_rootNode, n => (null == n.Left) || (null == n.Right), n => n)) + { + int pathCount = 0; + Node current = node; + while (null != current) + { + if (current.IsBlack) + { + pathCount++; + } + current = parents[current]; + } + Debug.Assert((-1 == treeCount) || (pathCount == treeCount), "Not all paths have the same number of black nodes."); + treeCount = pathCount; + } + // Verify node properties... + foreach (Node node in Traverse(_rootNode, n => true, n => n)) + { + // Left node is less + if (null != node.Left) + { + Debug.Assert(0 > KeyAndValueComparison(node.Left.Key, node.Left.Value, node.Key, node.Value), "Left node is greater than its parent."); + } + // Right node is greater + if (null != node.Right) + { + Debug.Assert(0 < KeyAndValueComparison(node.Right.Key, node.Right.Value, node.Key, node.Value), "Right node is less than its parent."); + } + // Both children of a red node are black + Debug.Assert(!IsRed(node) || (!IsRed(node.Left) && !IsRed(node.Right)), "Red node has a red child."); + // Always left-leaning + Debug.Assert(!IsRed(node.Right) || IsRed(node.Left), "Node is not left-leaning."); + // No consecutive reds (subset of previous rule) + //Debug.Assert(!(IsRed(node) && IsRed(node.Left))); + } + } + + /// + /// Gets an HTML fragment representing the tree. + /// + public string HtmlDocument + { + get + { + return + "" + + "" + + (null != _rootNode ? _rootNode.HtmlFragment : "[null]") + + "" + + ""; + } + } +#endif + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Collections/MultipleDictionary.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Collections/MultipleDictionary.cs new file mode 100644 index 00000000..584d1d1c --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Collections/MultipleDictionary.cs @@ -0,0 +1,101 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace System.Windows.Controls.DataVisualization.Collections +{ + /// + /// Implements a dictionary that can store multiple values for the same key. + /// + /// Type for keys. + /// Type for values. + internal class MultipleDictionary + { + /// + /// Gets or sets the BinaryTree instance used to store the dictionary values. + /// + protected LeftLeaningRedBlackTree BinaryTree { get; set; } + + /// + /// Initializes a new instance of the MultipleDictionary class. + /// + protected MultipleDictionary() + { + } + + /// + /// Initializes a new instance of the MultipleDictionary class. + /// + /// The parameter is not used. + /// The parameter is not used. + /// The parameter is not used. + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "allowDuplicateValues", Justification = "Unused parameter exists for API compatibility.")] + public MultipleDictionary(bool allowDuplicateValues, IEqualityComparer keyEqualityComparer, IEqualityComparer valueEqualityComparer) + { + Debug.Assert(null != keyEqualityComparer, "keyEqualityComparer must not be null."); + Debug.Assert(null != valueEqualityComparer, "valueEqualityComparer must not be null."); + BinaryTree = new LeftLeaningRedBlackTree( + (left, right) => keyEqualityComparer.GetHashCode(left).CompareTo(keyEqualityComparer.GetHashCode(right)), + (left, right) => valueEqualityComparer.GetHashCode(left).CompareTo(valueEqualityComparer.GetHashCode(right))); + } + + /// + /// Adds a key/value pair to the dictionary. + /// + /// Key to add. + /// Value to add. + public void Add(TKey key, TValue value) + { + BinaryTree.Add(key, value); + } + + /// + /// Removes a key/value pair from the dictionary. + /// + /// Key to remove. + /// Value to remove. + /// True if the value was present and removed. + public bool Remove(TKey key, TValue value) + { + return BinaryTree.Remove(key, value); + } + + /// + /// Gets the count of values in the dictionary. + /// + public int Count + { + get + { + return BinaryTree.Count; + } + } + + /// + /// Returns the collection of values corresponding to a key. + /// + /// Specified key. + /// Collection of values. + public ICollection this[TKey key] + { + get + { + return BinaryTree.GetValuesForKey(key).ToList(); + } + } + + /// + /// Clears the items in the dictionary. + /// + public void Clear() + { + BinaryTree.Clear(); + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Collections/OrderedMultipleDictionary.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Collections/OrderedMultipleDictionary.cs new file mode 100644 index 00000000..1ff4dfd5 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Collections/OrderedMultipleDictionary.cs @@ -0,0 +1,86 @@ +// (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.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace System.Windows.Controls.DataVisualization.Collections +{ + /// + /// Implements a dictionary that can store multiple values for the same key and sorts the values. + /// + /// Type for keys. + /// Type for values. + internal class OrderedMultipleDictionary : MultipleDictionary, IEnumerable + where TKey : IComparable + { + /// + /// Initializes a new instance of the MultipleDictionary class. + /// + /// The parameter is not used. + /// Key comparison class. + /// Value comparison class. + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "allowDuplicateValues", Justification = "Unused parameter exists for API compatibility.")] + public OrderedMultipleDictionary(bool allowDuplicateValues, Comparison keyComparison, Comparison valueComparison) + { + Debug.Assert(null != keyComparison, "keyComparison must not be null."); + Debug.Assert(null != valueComparison, "valueComparison must not be null."); + BinaryTree = new LeftLeaningRedBlackTree(keyComparison, valueComparison); + } + + /// + /// Gets a Range corresponding to the keys in the dictionary. + /// + /// Range of keys. + public Range GetKeyRange() + { + if (0 < BinaryTree.Count) + { + return new Range(BinaryTree.MinimumKey, BinaryTree.MaximumKey); + } + else + { + return new Range(); + } + } + + /// + /// Gets the largest and smallest key's extreme values from the dictionary. + /// + /// Tuple of the largest and smallest values. + public Tuple GetLargestAndSmallestValues() + { + if (0 < BinaryTree.Count) + { + return new Tuple(BinaryTree.MinimumValue, BinaryTree.MaximumValue); + } + else + { + return null; + } + } + + /// + /// Gets an enumerator for the values in the dictionary. + /// + /// Enumerator for values. + public IEnumerator GetEnumerator() + { + return BinaryTree.GetValuesForAllKeys().GetEnumerator(); + } + + /// + /// Gets an enumerator for the values in the dictionary. + /// + /// Enumerator for the values. + IEnumerator IEnumerable.GetEnumerator() + { + return BinaryTree.GetValuesForAllKeys().GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/DependencyPropertyAnimationHelper.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/DependencyPropertyAnimationHelper.cs new file mode 100644 index 00000000..6ea60ab2 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/DependencyPropertyAnimationHelper.cs @@ -0,0 +1,192 @@ +// (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; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Windows; +using System.Windows.Media.Animation; + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// Represents a control that can animate the transitions between its specified + /// dependency property. + /// + internal static class DependencyPropertyAnimationHelper + { + /// + /// Number of key frames per second to generate the date time animations. + /// + public const int KeyFramesPerSecond = 20; + + /// + /// The pattern used to ensure unique keys for the storyboards stored in + /// a framework element's resource dictionary. + /// + private const string StoryboardKeyPattern = "__{0}__"; + + /// + /// Returns a unique key for a storyboard. + /// + /// The property path of the property that + /// the storyboard animates. + /// A unique key for a storyboard. + private static string GetStoryboardKey(string propertyPath) + { + return string.Format(CultureInfo.InvariantCulture, StoryboardKeyPattern, propertyPath); + } + + /// + /// Starts animating a dependency property of a framework element to a + /// target value. + /// + /// The element to animate. + /// The dependency property to + /// animate. + /// The path of the dependency property to + /// animate. + /// The value to animate the dependency + /// property to. + /// The duration of the animation. + /// The easing function to uses to + /// transition the data points. + public static void BeginAnimation( + this FrameworkElement target, + DependencyProperty animatingDependencyProperty, + string propertyPath, + object targetValue, + TimeSpan timeSpan, + IEasingFunction easingFunction) + { + Storyboard storyBoard = target.Resources[GetStoryboardKey(propertyPath)] as Storyboard; + + if (storyBoard != null) + { +#if SILVERLIGHT + // Save current value + object currentValue = target.GetValue(animatingDependencyProperty); +#endif + storyBoard.Stop(); +#if SILVERLIGHT + // Restore that value so it doesn't snap back to its starting value + target.SetValue(animatingDependencyProperty, currentValue); +#endif + target.Resources.Remove(GetStoryboardKey(propertyPath)); + } + + storyBoard = CreateStoryboard(target, animatingDependencyProperty, propertyPath, ref targetValue, timeSpan, easingFunction); + + storyBoard.Completed += + (source, args) => + { + storyBoard.Stop(); + target.SetValue(animatingDependencyProperty, targetValue); + target.Resources.Remove(GetStoryboardKey(propertyPath)); + }; + + target.Resources.Add(GetStoryboardKey(propertyPath), storyBoard); + storyBoard.Begin(); + } + + /// + /// Creates a story board that animates a dependency property to a + /// value. + /// + /// The element that is the target of the + /// storyboard. + /// The dependency property + /// to animate. + /// The property path of the dependency + /// property to animate. + /// The value to animate the dependency property + /// to. + /// The duration of the animation. + /// + /// The easing function to use to + /// transition the data points. + /// The story board that animates the property. + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "easingFunction", Justification = "This parameter is used in Silverlight.")] + private static Storyboard CreateStoryboard( + FrameworkElement target, + DependencyProperty animatingDependencyProperty, + string propertyPath, + ref object toValue, + TimeSpan durationTimeSpan, + IEasingFunction easingFunction) + { + object fromValue = target.GetValue(animatingDependencyProperty); + + double fromDoubleValue; + double toDoubleValue; + + DateTime fromDateTime; + DateTime toDateTime; + + Storyboard storyBoard = new Storyboard(); + Storyboard.SetTarget(storyBoard, target); + Storyboard.SetTargetProperty(storyBoard, new PropertyPath(propertyPath)); + + if ((fromValue != null && toValue != null)) + { + if (ValueHelper.TryConvert(fromValue, out fromDoubleValue) && ValueHelper.TryConvert(toValue, out toDoubleValue)) + { + DoubleAnimation doubleAnimation = new DoubleAnimation(); +#if !NO_EASING_FUNCTIONS + doubleAnimation.EasingFunction = easingFunction; +#endif + doubleAnimation.Duration = durationTimeSpan; + doubleAnimation.To = ValueHelper.ToDouble(toValue); + toValue = doubleAnimation.To; + + storyBoard.Children.Add(doubleAnimation); + } + else if (ValueHelper.TryConvert(fromValue, out fromDateTime) && ValueHelper.TryConvert(toValue, out toDateTime)) + { + ObjectAnimationUsingKeyFrames keyFrameAnimation = new ObjectAnimationUsingKeyFrames(); + keyFrameAnimation.Duration = durationTimeSpan; + + long intervals = (long)(durationTimeSpan.TotalSeconds * KeyFramesPerSecond); + if (intervals < 2L) + { + intervals = 2L; + } + + IEnumerable timeSpanIntervals = + ValueHelper.GetTimeSpanIntervalsInclusive(durationTimeSpan, intervals); + + IEnumerable dateTimeIntervals = + ValueHelper.GetDateTimesBetweenInclusive(fromDateTime, toDateTime, intervals); + + IEnumerable keyFrames = + EnumerableFunctions.Zip( + dateTimeIntervals, + timeSpanIntervals, + (dateTime, timeSpan) => new DiscreteObjectKeyFrame() { Value = dateTime, KeyTime = timeSpan }); + + foreach (DiscreteObjectKeyFrame keyFrame in keyFrames) + { + keyFrameAnimation.KeyFrames.Add(keyFrame); + toValue = keyFrame.Value; + } + + storyBoard.Children.Add(keyFrameAnimation); + } + } + + if (storyBoard.Children.Count == 0) + { + ObjectAnimationUsingKeyFrames keyFrameAnimation = new ObjectAnimationUsingKeyFrames(); + DiscreteObjectKeyFrame endFrame = new DiscreteObjectKeyFrame() { Value = toValue, KeyTime = new TimeSpan(0, 0, 0) }; + keyFrameAnimation.KeyFrames.Add(endFrame); + + storyBoard.Children.Add(keyFrameAnimation); + } + + return storyBoard; + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/DesignerProperties.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/DesignerProperties.cs new file mode 100644 index 00000000..6092f98e --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/DesignerProperties.cs @@ -0,0 +1,44 @@ +// (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.Diagnostics.CodeAnalysis; + +namespace System.Windows.Controls +{ + /// + /// Provides a custom implementation of DesignerProperties.GetIsInDesignMode + /// to work around an issue. + /// + internal static class DesignerProperties + { + /// + /// Returns whether the control is in design mode (running under Blend + /// or Visual Studio). + /// + /// The element from which the property value is + /// read. + /// True if in design mode. + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "element", Justification = "Matching declaration of System.ComponentModel.DesignerProperties.GetIsInDesignMode (which has a bug and is not reliable).")] + public static bool GetIsInDesignMode(DependencyObject element) + { + if (!_isInDesignMode.HasValue) + { +#if SILVERLIGHT + _isInDesignMode = + (null == Application.Current) || + Application.Current.GetType() == typeof(Application); +#else + _isInDesignMode = System.ComponentModel.DesignerProperties.GetIsInDesignMode(element); +#endif + } + return _isInDesignMode.Value; + } + + /// + /// Stores the computed InDesignMode value. + /// + private static bool? _isInDesignMode; + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/EnumerableFunctions.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/EnumerableFunctions.cs new file mode 100644 index 00000000..f13152f8 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/EnumerableFunctions.cs @@ -0,0 +1,307 @@ +// (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; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// This class contains general purpose functions to manipulate the generic + /// IEnumerable type. + /// + internal static class EnumerableFunctions + { + /// + /// Attempts to cast IEnumerable to a list in order to retrieve a count + /// in order one. It attempts to cast fail the sequence is enumerated. + /// + /// The sequence. + /// The number of elements in the sequence. + public static int FastCount(this IEnumerable that) + { + IList list = that as IList; + if (list != null) + { + return list.Count; + } + return that.CastWrapper().Count(); + } + + /// + /// Returns the minimum value in the stream based on the result of a + /// project function. + /// + /// The stream type. + /// The stream. + /// The function that transforms the + /// item. + /// The minimum value or null. + public static T MinOrNull(this IEnumerable that, Func projectionFunction) + where T : class + { + IComparable result = null; + T minimum = default(T); + if (!that.Any()) + { + return minimum; + } + + minimum = that.First(); + result = projectionFunction(minimum); + foreach (T item in that.Skip(1)) + { + IComparable currentResult = projectionFunction(item); + if (result.CompareTo(currentResult) > 0) + { + result = currentResult; + minimum = item; + } + } + + return minimum; + } + + /// + /// Returns the sum of all values in the sequence or the default value. + /// + /// The stream. + /// The sum of all values or the default value. + public static double SumOrDefault(this IEnumerable that) + { + if (!that.Any()) + { + return 0.0; + } + else + { + return that.Sum(); + } + } + + /// + /// Returns the maximum value in the stream based on the result of a + /// project function. + /// + /// The stream type. + /// The stream. + /// The function that transforms the + /// item. + /// The maximum value or null. + public static T MaxOrNull(this IEnumerable that, Func projectionFunction) + where T : class + { + IComparable result = null; + T maximum = default(T); + if (!that.Any()) + { + return maximum; + } + + maximum = that.First(); + result = projectionFunction(maximum); + foreach (T item in that.Skip(1)) + { + IComparable currentResult = projectionFunction(item); + if (result.CompareTo(currentResult) < 0) + { + result = currentResult; + maximum = item; + } + } + + return maximum; + } + + /// + /// Accepts two sequences and applies a function to the corresponding + /// values in the two sequences. + /// + /// The type of the first sequence. + /// The type of the second sequence. + /// The return type of the function. + /// The first sequence. + /// The second sequence. + /// The function to apply to the corresponding values + /// from the two sequences. + /// A sequence of transformed values from both sequences. + public static IEnumerable Zip(IEnumerable enumerable0, IEnumerable enumerable1, Func func) + { + IEnumerator enumerator0 = enumerable0.GetEnumerator(); + IEnumerator enumerator1 = enumerable1.GetEnumerator(); + while (enumerator0.MoveNext() && enumerator1.MoveNext()) + { + yield return func(enumerator0.Current, enumerator1.Current); + } + } + + /// + /// Creates a sequence of values by accepting an initial value, an + /// iteration function, and apply the iteration function recursively. + /// + /// The type of the sequence. + /// The initial value. + /// The function to apply to the value. + /// + /// A sequence of the iterated values. + public static IEnumerable Iterate(T value, Func nextFunction) + { + yield return value; + while (true) + { + value = nextFunction(value); + yield return value; + } + } + + /// + /// Returns the index of an item in a sequence. + /// + /// The sequence. + /// The item to search for. + /// The index of the item or -1 if not found. + public static int IndexOf(this IEnumerable that, object value) + { + int index = 0; + foreach (object item in that) + { + if (object.ReferenceEquals(value, item) || value.Equals(item)) + { + return index; + } + index++; + } + return -1; + } + + /// + /// Executes an action for each item and a sequence, passing in the + /// index of that item to the action procedure. + /// + /// The type of the sequence. + /// The sequence. + /// A function that accepts a sequence item and its + /// index in the sequence. + public static void ForEachWithIndex(this IEnumerable that, Action action) + { + int index = 0; + foreach (T item in that) + { + action(item, index); + index++; + } + } + + /// + /// Returns the maximum value or null if sequence is empty. + /// + /// The type of the sequence. + /// The sequence to retrieve the maximum value from. + /// + /// The maximum value or null. + public static T? MaxOrNullable(this IEnumerable that) + where T : struct, IComparable + { + if (!that.Any()) + { + return null; + } + return that.Max(); + } + + /// + /// Returns the minimum value or null if sequence is empty. + /// + /// The type of the sequence. + /// The sequence to retrieve the minimum value from. + /// + /// The minimum value or null. + public static T? MinOrNullable(this IEnumerable that) + where T : struct, IComparable + { + if (!that.Any()) + { + return null; + } + return that.Min(); + } + + /// + /// Attempts to retrieve an element at an index by testing whether a + /// sequence is randomly accessible. If not, performance degrades to a + /// linear search. + /// + /// The type of the elements in the sequence. + /// The sequence. + /// The index of the element in the sequence. + /// The element at the given index. + public static T FastElementAt(this IEnumerable that, int index) + { + { + IList list = that as IList; + if (list != null) + { + return list[index]; + } + } + { + IList list = that as IList; + if (list != null) + { + return (T) list[index]; + } + } + return that.CastWrapper().ElementAt(index); + } + + /// + /// Applies an accumulator function over a sequence and returns each intermediate result. + /// + /// Type of elements in source sequence. + /// Type of elements in result sequence. + /// Sequence to scan. + /// Initial accumulator value. + /// Function used to generate the result sequence. + /// Sequence of intermediate results. + public static IEnumerable Scan(this IEnumerable that, S seed, Func accumulator) + { + S value = seed; + yield return seed; + foreach (T t in that) + { + value = accumulator(value, t); + yield return value; + } + yield break; + } + + /// + /// Converts the elements of an System.Collections.IEnumerable to the specified type. + /// + /// + /// A wrapper for the Enumerable.Cast(T) method that works around a limitation on some platforms. + /// + /// The type to convert the elements of source to. + /// The System.Collections.IEnumerable that contains the elements to be converted. + /// + /// An System.Collections.Generic.IEnumerable(T) that contains each element of the source sequence converted to the specified type. + /// + public static IEnumerable CastWrapper(this IEnumerable source) + { +#if SILVERLIGHT + // Certain flavors of this platform have a bug which causes Cast to raise an exception incorrectly. + // Work around that by using the more general OfType method instead for no loss of functionality. + return source.OfType(); +#else + // No issues on this platform - call directly through to Cast + return source.Cast(); +#endif + } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/GenericEqualityComparer.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/GenericEqualityComparer.cs new file mode 100644 index 00000000..ce77ec14 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/GenericEqualityComparer.cs @@ -0,0 +1,60 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// A generic equality comparer. + /// + /// The type of the objects being compared. + internal class GenericEqualityComparer : EqualityComparer + { + /// + /// Gets or sets a function which determines whether two items are equal. + /// + public Func EqualityFunction { get; set; } + + /// + /// Gets or sets a function that returns a hash code for an object. + /// + public Func HashCodeFunction { get; set; } + + /// + /// Initializes a new instance of the GenericEqualityComparer class. + /// + /// A function which determines whether + /// two items are equal. + /// A function that returns a hash code + /// for an object. + public GenericEqualityComparer(Func equalityFunction, Func hashCodeFunction) + { + this.EqualityFunction = equalityFunction; + this.HashCodeFunction = hashCodeFunction; + } + + /// + /// A function which determines whether two items are equal. + /// + /// The left object. + /// The right object. + /// A value indicating whether the objects. are equal. + public override bool Equals(T x, T y) + { + return EqualityFunction(x, y); + } + + /// + /// A function that returns a hash code for an object. + /// + /// The object to returns a hash code for. + /// The hash code for the object. + public override int GetHashCode(T obj) + { + return HashCodeFunction(obj); + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/GlobalSuppressions.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/GlobalSuppressions.cs new file mode 100644 index 00000000..fcc53ce8 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/GlobalSuppressions.cs @@ -0,0 +1,131 @@ +// (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.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames", Justification = "Unofficial release.")] + +[assembly: SuppressMessage("General", "SWC1001:XmlDocumentationCommentShouldBeSpelledCorrectly", MessageId = "Headered", Justification = "Part of HeaderedItemsControl.")] +[assembly: SuppressMessage("General", "SWC1001:XmlDocumentationCommentShouldBeSpelledCorrectly", MessageId = "Polyline", Justification = "Matches System.Windows.Shapes.Polyline.")] +[assembly: SuppressMessage("General", "SWC1001:XmlDocumentationCommentShouldBeSpelledCorrectly", MessageId = "Intelli", Justification = "Part of IntelliSense.")] +[assembly: SuppressMessage("General", "SWC1001:XmlDocumentationCommentShouldBeSpelledCorrectly", MessageId = "Silverlight", Justification = "Product name.")] +[assembly: SuppressMessage("General", "SWC1001:XmlDocumentationCommentShouldBeSpelledCorrectly", MessageId = "tuple's", Justification = "Consistent with MSDN documentation for .NET 4.")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.RangeAxis.#System.Windows.Controls.DataVisualization.Charting.IRangeAxis.ActualMaximum")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.RangeAxis.#System.Windows.Controls.DataVisualization.Charting.IRangeAxis.ActualMinimum")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.RangeAxis.#System.Windows.Controls.DataVisualization.Charting.IRangeAxis.GetPlotAreaCoordinate(System.IComparable)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.RangeAxis.#System.Windows.Controls.DataVisualization.Charting.IRangeAxis.RangeChanged(System.Windows.Controls.DataVisualization.Charting.IRangeAxisInformationProvider,System.Windows.Controls.DataVisualization.Range`1)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.RangeAxis.#System.Windows.Controls.DataVisualization.Charting.IRangeAxis.GetPlotAreaCoordinateValueRange(System.Double)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.DataPointSeriesWithAxes.#System.Windows.Controls.DataVisualization.Charting.ICategoryAxisInformationProvider.GetCategories(System.Windows.Controls.DataVisualization.Charting.ICategoryAxis)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.DataPointSeriesWithAxes.#System.Windows.Controls.DataVisualization.Charting.IRangeAxisInformationProvider.GetActualRange(System.Windows.Controls.DataVisualization.Charting.IRangeAxis)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.DataPointSeriesWithAxes.#System.Windows.Controls.DataVisualization.Charting.IRangeAxisInformationProvider.GetDesiredRange(System.Windows.Controls.DataVisualization.Charting.IRangeAxis)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.Axis.#System.Windows.Controls.DataVisualization.Charting.IAxis.Register(System.Object)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.Axis.#System.Windows.Controls.DataVisualization.Charting.IAxis.Unregister(System.Object)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.RangeAxis.#System.Windows.Controls.DataVisualization.Charting.IRangeAxis.Range")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.DataPointSeriesWithAxes.#System.Windows.Controls.DataVisualization.Charting.IAxisListener.AxisInvalidated(System.Windows.Controls.DataVisualization.Charting.IAxis)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.DataPointSeriesWithAxes.#System.Windows.Controls.DataVisualization.Charting.IDataProvider.GetData(System.Windows.Controls.DataVisualization.Charting.IDataConsumer)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.DataPointSeriesWithAxes.#System.Windows.Controls.DataVisualization.Charting.IRangeProvider.GetRange(System.Windows.Controls.DataVisualization.Charting.IRangeConsumer)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.CollectionBase`1.#System.Collections.ICollection.SyncRoot")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.CollectionBase`1.#System.Collections.Generic.ICollection`1.IsReadOnly")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.ListBaseCollection`1.#System.Collections.IList.IsFixedSize")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.ListBaseCollection`1.#System.Collections.IList.IsReadOnly")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.CollectionBase`1.#System.Collections.ICollection.IsSynchronized")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.ColumnBarBaseSeries`1.#System.Windows.Controls.DataVisualization.Charting.IAnchoredToOrigin.AnchoredAxis")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.RangeAxis.#System.Windows.Controls.DataVisualization.Charting.IRangeConsumer.RangeChanged(System.Windows.Controls.DataVisualization.Charting.IRangeProvider,System.Windows.Controls.DataVisualization.Range`1)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.RangeAxis.#System.Windows.Controls.DataVisualization.Charting.IValueMarginConsumer.ValueMarginsChanged(System.Windows.Controls.DataVisualization.Charting.IValueMarginProvider,System.Collections.Generic.IEnumerable`1)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.DefinitionSeries.#System.Windows.Controls.DataVisualization.Charting.IAxisListener.AxisInvalidated(System.Windows.Controls.DataVisualization.Charting.IAxis)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.DefinitionSeries.#System.Windows.Controls.DataVisualization.Charting.IRangeProvider.GetRange(System.Windows.Controls.DataVisualization.Charting.IRangeConsumer)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.DefinitionSeries.#System.Windows.Controls.DataVisualization.IResourceDictionaryDispenser.GetResourceDictionariesWhere(System.Func`2)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.DefinitionSeries.#System.Windows.Controls.DataVisualization.IResourceDictionaryDispenser.ResourceDictionariesChanged")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.DefinitionSeries.#System.Windows.Controls.DataVisualization.IResourceDictionaryDispenser.ResourceDictionariesChanged")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.DefinitionSeries.#System.Windows.Controls.DataVisualization.Charting.ISeriesHost.Axes")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.DefinitionSeries.#System.Windows.Controls.DataVisualization.Charting.ISeriesHost.BackgroundElements")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.DefinitionSeries.#System.Windows.Controls.DataVisualization.Charting.ISeriesHost.ForegroundElements")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.DefinitionSeries.#System.Windows.Controls.DataVisualization.Charting.ISeriesHost.Series")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.DefinitionSeries.#System.Windows.Controls.DataVisualization.Charting.IValueMarginProvider.GetValueMargins(System.Windows.Controls.DataVisualization.Charting.IValueMarginConsumer)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.SeriesDefinition.#System.Windows.Controls.DataVisualization.Charting.IRequireGlobalSeriesIndex.GlobalSeriesIndexChanged(System.Nullable`1)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.SeriesDefinition.#System.Windows.Controls.DataVisualization.Charting.IRequireSeriesHost.SeriesHost")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.SeriesDefinition.#System.Windows.Controls.DataVisualization.Charting.IRequireSeriesHost.SeriesHost")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.SeriesDefinition.#System.Windows.Controls.DataVisualization.Charting.ISeries.LegendItems")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.StackedAreaSeries.#System.Windows.Controls.DataVisualization.Charting.IAnchoredToOrigin.AnchoredAxis")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.StackedBarColumnSeries.#System.Windows.Controls.DataVisualization.Charting.IAnchoredToOrigin.AnchoredAxis")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.StackedBarColumnSeries.#System.Windows.Controls.DataVisualization.Charting.IDataProvider.GetData(System.Windows.Controls.DataVisualization.Charting.IDataConsumer)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.DefinitionSeries.#System.Windows.Controls.DataVisualization.Charting.IDataProvider.GetData(System.Windows.Controls.DataVisualization.Charting.IDataConsumer)")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.PieSeries.#.cctor()")] +[assembly: SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Scope = "member", Target = "System.Windows.Controls.DataVisualization.Charting.SeriesHostAxesCollection.#.ctor(System.Windows.Controls.DataVisualization.Charting.ISeriesHost)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "XamlGeneratedNamespace")] +[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "System.Windows.Controls.DataVisualization.Charting.Primitives")] +[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "System.Windows.Controls.DataVisualization.Charting.Compatible")] + +// Suppress "Silverlight-only class or assembly" warning for all DataVisualization classes because there is currently no corresponding WPF implementation. +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.AnimationSequence")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.Axis")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.AxisLabel")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.AxisLocation")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.AxisOrientation")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.BarDataPoint")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.BarSeries")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.BubbleDataPoint")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.BubbleSeries")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.CategoryAxis")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.CategorySortOrder")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.Chart")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.ColumnDataPoint")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.ColumnSeries")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.DataPoint")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.DataPointSeries")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.DataPointSeriesWithAxes")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.DataPointSingleSeriesWithAxes")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.DataPointState")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.DateTimeAxis")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.DateTimeAxisLabel")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.DateTimeIntervalType")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.IAxis")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.IAxisGridLinesElementProvider")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.ICategoryAxis")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.ICategoryAxisInformationProvider")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.IRangeAxis")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.IRangeAxisInformationProvider")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.IRequireGlobalSeriesIndex")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.ISeriesHost")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.LegendItem")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.LineDataPoint")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.LineSeries")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.LinearAxis")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.NumericAxis")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.NumericAxisLabel")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.PieDataPoint")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.PieSeries")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.RangeAxis")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.ScatterDataPoint")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.ScatterSeries")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.Series")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.IStyleDispenser")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Legend")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Range`1")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.StringFormatConverter")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.ResourceDictionaryCollection")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Title")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.ColumnBarBaseSeries`1")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.DisplayAxis")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.IAnnotationProvider")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.IAxisListener")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.IDataConsumer")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.IDataProvider")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.IRangeConsumer")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.IRangeProvider")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.IRequireSeriesHost")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.IScrollService")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.IValueMarginConsumer")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.IValueMarginProvider")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.PointOrientedPanel")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.RadialPanel")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.ValueMargin")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.IScrollServiceInformationProvider")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Unit")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.UnitValue")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.ListBaseCollection`1")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.CollectionBase`1")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.Primitives.Edge")] +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "System.Windows.Controls.DataVisualization.Charting.Primitives.EdgePanel")] diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/GridExtensions.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/GridExtensions.cs new file mode 100644 index 00000000..2cedf573 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/GridExtensions.cs @@ -0,0 +1,54 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using System.Windows.Controls; + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// A set of extension methods for the Grid container. + /// + internal static class GridExtensions + { + /// + /// Mirrors the grid either horizontally or vertically. + /// + /// The grid to mirror. + /// The orientation to mirror the grid along. + /// + public static void Mirror(this Grid grid, Orientation orientation) + { + if (orientation == Orientation.Horizontal) + { + IList rows = grid.RowDefinitions.Reverse().ToList(); + grid.RowDefinitions.Clear(); + foreach (FrameworkElement child in grid.Children.OfType()) + { + Grid.SetRow(child, (rows.Count - 1) - Grid.GetRow(child)); + } + foreach (RowDefinition row in rows) + { + grid.RowDefinitions.Add(row); + } + } + else if (orientation == Orientation.Vertical) + { + IList columns = grid.ColumnDefinitions.Reverse().ToList(); + grid.ColumnDefinitions.Clear(); + foreach (FrameworkElement child in grid.Children.OfType()) + { + Grid.SetColumn(child, (columns.Count - 1) - Grid.GetColumn(child)); + } + foreach (ColumnDefinition column in columns) + { + grid.ColumnDefinitions.Add(column); + } + } + } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/IResourceDictionaryDispenser.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/IResourceDictionaryDispenser.cs new file mode 100644 index 00000000..94f1a88d --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/IResourceDictionaryDispenser.cs @@ -0,0 +1,32 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// Represents a service that dispenses ResourceDictionaries. + /// + /// Preview + public interface IResourceDictionaryDispenser + { + /// + /// Returns a rotating enumerator of ResourceDictionaries coordinated with + /// the style dispenser object to ensure that no two enumerators are + /// currently on the same one if possible. If the dispenser is reset or + /// its collection is changed then the enumerators will also be reset. + /// + /// A predicate that returns a value + /// indicating whether to return a ResourceDictionary. + /// An enumerator of ResourceDictionaries. + IEnumerator GetResourceDictionariesWhere(Func predicate); + + /// + /// Event that is invoked when the StyleDispenser's ResourceDictionaries have changed. + /// + event EventHandler ResourceDictionariesChanged; + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/LayoutTransformControl.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/LayoutTransformControl.cs new file mode 100644 index 00000000..4444f60d --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/LayoutTransformControl.cs @@ -0,0 +1,653 @@ +// (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; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Markup; +using System.Windows.Media; +#if !SILVERLIGHT +using System.IO; +using System.Text; +#endif + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// Control that implements support for transformations as if applied by + /// LayoutTransform (which does not exist in Silverlight). + /// + [ContentProperty("Child")] + internal class LayoutTransformControl : Control + { + #region public FrameworkElement Child + /// + /// Gets or sets the single child of the LayoutTransformControl. + /// + /// + /// Corresponds to Windows Presentation Foundation's Decorator.Child + /// property. + /// + public FrameworkElement Child + { + get { return (FrameworkElement)GetValue(ContentProperty); } + set { SetValue(ContentProperty, value); } + } + + /// + /// Identifies the ContentProperty. + /// + public static readonly DependencyProperty ContentProperty = DependencyProperty.Register( + "Child", typeof(FrameworkElement), typeof(LayoutTransformControl), new PropertyMetadata(ChildChanged)); + #endregion public FrameworkElement Child + + #region public Transform Transform + /// + /// Gets or sets the Transform of the LayoutTransformControl. + /// + /// + /// Corresponds to UIElement.RenderTransform. + /// + public Transform Transform + { + get { return (Transform)GetValue(TransformProperty); } + set { SetValue(TransformProperty, value); } + } + + /// + /// Identifies the TransformProperty dependency property. + /// + public static readonly DependencyProperty TransformProperty = DependencyProperty.Register( + "Transform", typeof(Transform), typeof(LayoutTransformControl), new PropertyMetadata(TransformChanged)); + #endregion + + /// + /// Value used to work around double arithmetic rounding issues in + /// Silverlight. + /// + private const double AcceptableDelta = 0.0001; + + /// + /// Value used to work around double arithmetic rounding issues in + /// Silverlight. + /// + private const int DecimalsAfterRound = 4; + + /// + /// Host panel for Child element. + /// + private Panel _layoutRoot; + + /// + /// RenderTransform/MatrixTransform applied to layout root. + /// + private MatrixTransform _matrixTransform; + + /// + /// Transformation matrix corresponding to matrix transform. + /// + private Matrix _transformation; + + /// + /// Actual DesiredSize of Child element. + /// + private Size _childActualSize = Size.Empty; + + /// + /// Initializes a new instance of the LayoutTransformControl class. + /// + public LayoutTransformControl() + { + // Can't tab to LayoutTransformControl + IsTabStop = false; +#if SILVERLIGHT + // Disable layout rounding because its rounding of values confuses + // things. + UseLayoutRounding = false; +#endif + // Hard coded template is never meant to be changed and avoids the + // need for generic.xaml. + string templateXaml = + @"" + + "" + + "" + + "" + + "" + + "" + + ""; +#if SILVERLIGHT + Template = (ControlTemplate)XamlReader.Load(templateXaml); +#else + using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(templateXaml))) + { + Template = (ControlTemplate)XamlReader.Load(stream); + } +#endif + } + + /// + /// Called whenever the control's template changes. + /// + public override void OnApplyTemplate() + { + // Save existing content and remove it from the visual tree + FrameworkElement savedContent = Child; + Child = null; + // Apply new template + base.OnApplyTemplate(); + // Find template parts + _layoutRoot = GetTemplateChild("LayoutRoot") as Panel; + _matrixTransform = GetTemplateChild("MatrixTransform") as MatrixTransform; + // Restore saved content + Child = savedContent; + // Apply the current transform + TransformUpdated(); + } + + /// + /// Handle changes to the child dependency property. + /// + /// The source of the event. + /// Information about the event. + private static void ChildChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + // Casts are safe because Silverlight is enforcing the types + ((LayoutTransformControl)o).OnChildChanged((FrameworkElement)e.NewValue); + } + + /// + /// Updates content when the child property is changed. + /// + /// The new child. + private void OnChildChanged(FrameworkElement newContent) + { + if (null != _layoutRoot) + { + // Clear current child + _layoutRoot.Children.Clear(); + if (null != newContent) + { + // Add the new child to the tree + _layoutRoot.Children.Add(newContent); + } + // New child means re-layout is necessary + InvalidateMeasure(); + } + } + + /// + /// Handles changes to the Transform DependencyProperty. + /// + /// The source of the event. + /// Information about the event. + private static void TransformChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + // Casts are safe because Silverlight is enforcing the types + ((LayoutTransformControl)o).OnTransformChanged((Transform)e.NewValue); + } + + /// + /// Processes the transform when the transform is changed. + /// + /// The transform to process. + private void OnTransformChanged(Transform newValue) + { + ProcessTransform(newValue); + } + + /// + /// Notifies the LayoutTransformControl that some aspect of its + /// Transform property has changed. + /// + /// + /// Call this to update the LayoutTransform in cases where + /// LayoutTransformControl wouldn't otherwise know to do so. + /// + public void TransformUpdated() + { + ProcessTransform(Transform); + } + + /// + /// Processes the current transform to determine the corresponding + /// matrix. + /// + /// The transform to use to determine the + /// matrix. + private void ProcessTransform(Transform transform) + { + // Get the transform matrix and apply it + _transformation = RoundMatrix(GetTransformMatrix(transform), DecimalsAfterRound); + if (null != _matrixTransform) + { + _matrixTransform.Matrix = _transformation; + } + // New transform means re-layout is necessary + InvalidateMeasure(); + } + + /// + /// Walks the Transform and returns the corresponding matrix. + /// + /// The transform to create a matrix for. + /// + /// The matrix calculated from the transform. + private Matrix GetTransformMatrix(Transform transform) + { + if (null != transform) + { + // WPF equivalent of this entire method: + // return transform.Value; + + // Process the TransformGroup + TransformGroup transformGroup = transform as TransformGroup; + if (null != transformGroup) + { + Matrix groupMatrix = Matrix.Identity; + foreach (Transform child in transformGroup.Children) + { + groupMatrix = MatrixMultiply(groupMatrix, GetTransformMatrix(child)); + } + return groupMatrix; + } + + // Process the RotateTransform + RotateTransform rotateTransform = transform as RotateTransform; + if (null != rotateTransform) + { + double angle = rotateTransform.Angle; + double angleRadians = (2 * Math.PI * angle) / 360; + double sine = Math.Sin(angleRadians); + double cosine = Math.Cos(angleRadians); + return new Matrix(cosine, sine, -sine, cosine, 0, 0); + } + + // Process the ScaleTransform + ScaleTransform scaleTransform = transform as ScaleTransform; + if (null != scaleTransform) + { + double scaleX = scaleTransform.ScaleX; + double scaleY = scaleTransform.ScaleY; + return new Matrix(scaleX, 0, 0, scaleY, 0, 0); + } + + // Process the SkewTransform + SkewTransform skewTransform = transform as SkewTransform; + if (null != skewTransform) + { + double angleX = skewTransform.AngleX; + double angleY = skewTransform.AngleY; + double angleXRadians = (2 * Math.PI * angleX) / 360; + double angleYRadians = (2 * Math.PI * angleY) / 360; + return new Matrix(1, angleYRadians, angleXRadians, 1, 0, 0); + } + + // Process the MatrixTransform + MatrixTransform matrixTransform = transform as MatrixTransform; + if (null != matrixTransform) + { + return matrixTransform.Matrix; + } + + // TranslateTransform has no effect in LayoutTransform + } + + // Fall back to no-op transformation + return Matrix.Identity; + } + + /// + /// Provides the behavior for the "Measure" pass of layout. + /// + /// The available size that this element can + /// give to child elements. Infinity can be specified as a value to + /// indicate that the element will size to whatever content is + /// available. + /// The size that this element determines it needs during + /// layout, based on its calculations of child element sizes. + protected override Size MeasureOverride(Size availableSize) + { + FrameworkElement child = Child; + if ((null == _layoutRoot) || (null == child)) + { + // No content, no size + return Size.Empty; + } + + Size measureSize; + if (_childActualSize == Size.Empty) + { + // Determine the largest size after the transformation + measureSize = ComputeLargestTransformedSize(availableSize); + } + else + { + // Previous measure/arrange pass determined that + // Child.DesiredSize was larger than believed. + measureSize = _childActualSize; + } + + // Perform a mesaure on the _layoutRoot (containing Child) + _layoutRoot.Measure(measureSize); + + // WPF equivalent of _childActualSize technique (much simpler, but + // doesn't work on Silverlight 2). If the child is going to render + // larger than the available size, re-measure according to that size. + ////child.Arrange(new Rect()); + ////if (child.RenderSize != child.DesiredSize) + ////{ + //// _layoutRoot.Measure(child.RenderSize); + ////} + + // Transform DesiredSize to find its width/height + Rect transformedDesiredRect = RectTransform(new Rect(0, 0, _layoutRoot.DesiredSize.Width, _layoutRoot.DesiredSize.Height), _transformation); + Size transformedDesiredSize = new Size(transformedDesiredRect.Width, transformedDesiredRect.Height); + + // Return result to allocate enough space for the transformation + return transformedDesiredSize; + } + + /// + /// Provides the behavior for the "Arrange" pass of layout. + /// + /// The final area within the parent that this + /// element should use to arrange itself and its children. + /// The actual size used. + /// + /// Using the WPF paramater name finalSize instead of Silverlight's + /// finalSize for clarity. + /// + protected override Size ArrangeOverride(Size finalSize) + { + FrameworkElement child = Child; + if ((null == _layoutRoot) || (null == child)) + { + // No child, use whatever was given + return finalSize; + } + + // Determine the largest available size after the transformation + Size finalSizeTransformed = ComputeLargestTransformedSize(finalSize); + if (IsSizeSmaller(finalSizeTransformed, _layoutRoot.DesiredSize)) + { + // Some elements do not like being given less space than they asked for (ex: TextBlock) + // Bump the working size up to do the right thing by them + finalSizeTransformed = _layoutRoot.DesiredSize; + } + + // Transform the working size to find its width/height + Rect transformedRect = RectTransform(new Rect(0, 0, finalSizeTransformed.Width, finalSizeTransformed.Height), _transformation); + // Create the Arrange rect to center the transformed content + Rect finalRect = new Rect( + -transformedRect.Left + ((finalSize.Width - transformedRect.Width) / 2), + -transformedRect.Top + ((finalSize.Height - transformedRect.Height) / 2), + finalSizeTransformed.Width, + finalSizeTransformed.Height); + + // Perform an Arrange on _layoutRoot (containing Child) + _layoutRoot.Arrange(finalRect); + + // This is the first opportunity under Silverlight to find out the Child's true DesiredSize + if (IsSizeSmaller(finalSizeTransformed, child.RenderSize) && (Size.Empty == _childActualSize)) + { + // Unfortunately, all the work so far is invalid because the wrong DesiredSize was used + // Make a note of the actual DesiredSize + _childActualSize = new Size(child.ActualWidth, child.ActualHeight); + + // Force a new measure/arrange pass + InvalidateMeasure(); + } + else + { + // Clear the "need to measure/arrange again" flag + _childActualSize = Size.Empty; + } + + // Return result to perform the transformation + return finalSize; + } + + /// + /// Computes the largest usable size after applying the transformation + /// to the specified bounds. + /// + /// The size to arrange within. + /// The size required. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Closely corresponds to WPF's FrameworkElement.FindMaximalAreaLocalSpaceRect.")] + private Size ComputeLargestTransformedSize(Size arrangeBounds) + { + // Computed largest transformed size + Size computedSize = Size.Empty; + + // Detect infinite bounds and constrain the scenario + bool infiniteWidth = double.IsInfinity(arrangeBounds.Width); + if (infiniteWidth) + { + arrangeBounds.Width = arrangeBounds.Height; + } + bool infiniteHeight = double.IsInfinity(arrangeBounds.Height); + if (infiniteHeight) + { + arrangeBounds.Height = arrangeBounds.Width; + } + + // Capture the matrix parameters + double a = _transformation.M11; + double b = _transformation.M12; + double c = _transformation.M21; + double d = _transformation.M22; + + // Compute maximum possible transformed width/height based on starting width/height + // These constraints define two lines in the positive x/y quadrant + double maxWidthFromWidth = Math.Abs(arrangeBounds.Width / a); + double maxHeightFromWidth = Math.Abs(arrangeBounds.Width / c); + double maxWidthFromHeight = Math.Abs(arrangeBounds.Height / b); + double maxHeightFromHeight = Math.Abs(arrangeBounds.Height / d); + + // The transformed width/height that maximize the area under each segment is its midpoint + // At most one of the two midpoints will satisfy both constraints + double idealWidthFromWidth = maxWidthFromWidth / 2; + double idealHeightFromWidth = maxHeightFromWidth / 2; + double idealWidthFromHeight = maxWidthFromHeight / 2; + double idealHeightFromHeight = maxHeightFromHeight / 2; + + // Compute slope of both constraint lines + double slopeFromWidth = -(maxHeightFromWidth / maxWidthFromWidth); + double slopeFromHeight = -(maxHeightFromHeight / maxWidthFromHeight); + + if ((0 == arrangeBounds.Width) || (0 == arrangeBounds.Height)) + { + // Check for empty bounds + computedSize = new Size(0, 0); + } + else if (infiniteWidth && infiniteHeight) + { + // Check for completely unbound scenario + computedSize = new Size(double.PositiveInfinity, double.PositiveInfinity); + } + else if (!MatrixHasInverse(_transformation)) + { + // Check for singular matrix + computedSize = new Size(0, 0); + } + else if ((0 == b) || (0 == c)) + { + // Check for 0/180 degree special cases + double maxHeight = (infiniteHeight ? double.PositiveInfinity : maxHeightFromHeight); + double maxWidth = (infiniteWidth ? double.PositiveInfinity : maxWidthFromWidth); + + if ((0 == b) && (0 == c)) + { + // No constraints + computedSize = new Size(maxWidth, maxHeight); + } + else if (0 == b) + { + // Constrained by width + double computedHeight = Math.Min(idealHeightFromWidth, maxHeight); + computedSize = new Size( + maxWidth - Math.Abs((c * computedHeight) / a), + computedHeight); + } + else if (0 == c) + { + // Constrained by height + double computedWidth = Math.Min(idealWidthFromHeight, maxWidth); + computedSize = new Size( + computedWidth, + maxHeight - Math.Abs((b * computedWidth) / d)); + } + } + else if ((0 == a) || (0 == d)) + { + // Check for 90/270 degree special cases + + double maxWidth = (infiniteHeight ? double.PositiveInfinity : maxWidthFromHeight); + double maxHeight = (infiniteWidth ? double.PositiveInfinity : maxHeightFromWidth); + + if ((0 == a) && (0 == d)) + { + // No constraints + computedSize = new Size(maxWidth, maxHeight); + } + else if (0 == a) + { + // Constrained by width + double computedHeight = Math.Min(idealHeightFromHeight, maxHeight); + computedSize = new Size( + maxWidth - Math.Abs((d * computedHeight) / b), + computedHeight); + } + else if (0 == d) + { + // Constrained by height. + double computedWidth = Math.Min(idealWidthFromWidth, maxWidth); + computedSize = new Size( + computedWidth, + maxHeight - Math.Abs((a * computedWidth) / c)); + } + } + else if (idealHeightFromWidth <= ((slopeFromHeight * idealWidthFromWidth) + maxHeightFromHeight)) + { + // Check the width midpoint for viability (by being below the + // height constraint line). + computedSize = new Size(idealWidthFromWidth, idealHeightFromWidth); + } + else if (idealHeightFromHeight <= ((slopeFromWidth * idealWidthFromHeight) + maxHeightFromWidth)) + { + // Check the height midpoint for viability (by being below the + // width constraint line). + computedSize = new Size(idealWidthFromHeight, idealHeightFromHeight); + } + else + { + // Neither midpoint is viable; use the intersection of the two + // constraint lines instead. + + // Compute width by setting heights equal (m1*x+c1=m2*x+c2). + double computedWidth = (maxHeightFromHeight - maxHeightFromWidth) / (slopeFromWidth - slopeFromHeight); + // Compute height from width constraint line (y=m*x+c; using + // height would give same result). + computedSize = new Size( + computedWidth, + (slopeFromWidth * computedWidth) + maxHeightFromWidth); + } + + return computedSize; + } + + /// + /// Return true if Size a is smaller than Size b in either dimension. + /// + /// The left size. + /// The right size. + /// A value indicating whether the left size is smaller than + /// the right. + private static bool IsSizeSmaller(Size a, Size b) + { + // WPF equivalent of following code: + // return ((a.Width < b.Width) || (a.Height < b.Height)); + return ((a.Width + AcceptableDelta < b.Width) || (a.Height + AcceptableDelta < b.Height)); + } + + /// + /// Rounds the non-offset elements of a matrix to avoid issues due to + /// floating point imprecision. + /// + /// The matrix to round. + /// The number of decimals after the + /// round. + /// The rounded matrix. + private static Matrix RoundMatrix(Matrix matrix, int decimalsAfterRound) + { + return new Matrix( + Math.Round(matrix.M11, decimalsAfterRound), + Math.Round(matrix.M12, decimalsAfterRound), + Math.Round(matrix.M21, decimalsAfterRound), + Math.Round(matrix.M22, decimalsAfterRound), + matrix.OffsetX, + matrix.OffsetY); + } + + /// + /// Implement Windows Presentation Foundation's Rect.Transform on + /// Silverlight. + /// + /// The rectangle to transform. + /// The matrix to use to transform the rectangle. + /// + /// The transformed rectangle. + private static Rect RectTransform(Rect rectangle, Matrix matrix) + { + // WPF equivalent of following code: + // var rectTransformed = Rect.Transform(rect, matrix); + Point leftTop = matrix.Transform(new Point(rectangle.Left, rectangle.Top)); + Point rightTop = matrix.Transform(new Point(rectangle.Right, rectangle.Top)); + Point leftBottom = matrix.Transform(new Point(rectangle.Left, rectangle.Bottom)); + Point rightBottom = matrix.Transform(new Point(rectangle.Right, rectangle.Bottom)); + double left = Math.Min(Math.Min(leftTop.X, rightTop.X), Math.Min(leftBottom.X, rightBottom.X)); + double top = Math.Min(Math.Min(leftTop.Y, rightTop.Y), Math.Min(leftBottom.Y, rightBottom.Y)); + double right = Math.Max(Math.Max(leftTop.X, rightTop.X), Math.Max(leftBottom.X, rightBottom.X)); + double bottom = Math.Max(Math.Max(leftTop.Y, rightTop.Y), Math.Max(leftBottom.Y, rightBottom.Y)); + Rect rectTransformed = new Rect(left, top, right - left, bottom - top); + return rectTransformed; + } + + /// + /// Implements Windows Presentation Foundation's Matrix.Multiply on + /// Silverlight. + /// + /// The left matrix. + /// The right matrix. + /// The product of the two matrices. + private static Matrix MatrixMultiply(Matrix matrix1, Matrix matrix2) + { + // WPF equivalent of following code: + // return Matrix.Multiply(matrix1, matrix2); + return new Matrix( + (matrix1.M11 * matrix2.M11) + (matrix1.M12 * matrix2.M21), + (matrix1.M11 * matrix2.M12) + (matrix1.M12 * matrix2.M22), + (matrix1.M21 * matrix2.M11) + (matrix1.M22 * matrix2.M21), + (matrix1.M21 * matrix2.M12) + (matrix1.M22 * matrix2.M22), + ((matrix1.OffsetX * matrix2.M11) + (matrix1.OffsetY * matrix2.M21)) + matrix2.OffsetX, + ((matrix1.OffsetX * matrix2.M12) + (matrix1.OffsetY * matrix2.M22)) + matrix2.OffsetY); + } + + /// + /// Implements Windows Presentation Foundation's Matrix.HasInverse on + /// Silverlight. + /// + /// The matrix. + /// True if matrix has an inverse. + private static bool MatrixHasInverse(Matrix matrix) + { + // WPF equivalent of following code: + // return matrix.HasInverse; + return (0 != ((matrix.M11 * matrix.M22) - (matrix.M12 * matrix.M21))); + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Legend/Legend.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Legend/Legend.cs new file mode 100644 index 00000000..c451251a --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Legend/Legend.cs @@ -0,0 +1,110 @@ +// (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.Specialized; +using System.Diagnostics.CodeAnalysis; +using System.Windows.Data; + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// Represents a control that displays a list of items and has a title. + /// + /// Preview + [StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(ContentPresenter))] + [StyleTypedProperty(Property = "TitleStyle", StyleTargetType = typeof(Title))] + public partial class Legend : HeaderedItemsControl + { +#if !SILVERLIGHT + /// + /// Initializes the static members of the Legend class. + /// + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Dependency properties are initialized in-line.")] + static Legend() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(Legend), new FrameworkPropertyMetadata(typeof(Legend))); + } + +#endif + /// + /// Initializes a new instance of the Legend class. + /// + public Legend() + { +#if SILVERLIGHT + DefaultStyleKey = typeof(Legend); +#endif + // By default, the Visibility property should follow ContentVisibility - but users can override it + SetBinding(VisibilityProperty, new Binding("ContentVisibility") { Source = this }); + } + + /// + /// Gets or sets the Style of the ISeriesHost's Title. + /// + public Style TitleStyle + { + get { return GetValue(TitleStyleProperty) as Style; } + set { SetValue(TitleStyleProperty, value); } + } + + /// + /// Identifies the TitleStyle dependency property. + /// + public static readonly DependencyProperty TitleStyleProperty = + DependencyProperty.Register( + "TitleStyle", + typeof(Style), + typeof(Legend), + null); + + /// + /// Gets the Visibility of the Legend's content (title and items). + /// + public Visibility ContentVisibility + { + get { return (Visibility)GetValue(ContentVisibilityProperty); } + protected set { SetValue(ContentVisibilityProperty, value); } + } + + /// + /// Identifies the ContentVisibility dependency property. + /// + public static readonly DependencyProperty ContentVisibilityProperty = + DependencyProperty.Register( + "ContentVisibility", + typeof(Visibility), + typeof(Legend), + null); + + /// + /// Handles the OnHeaderChanged event for HeaderedItemsControl. + /// + /// Old header. + /// New header. + protected override void OnHeaderChanged(object oldHeader, object newHeader) + { + base.OnHeaderChanged(oldHeader, newHeader); + UpdateContentVisibility(); + } + + /// + /// Handles the CollectionChanged event for HeaderedItemsControl's ItemsSource. + /// + /// Event arguments. + protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) + { + base.OnItemsChanged(e); + UpdateContentVisibility(); + } + + /// + /// Updates the ContentVisibility property to reflect the presence of content. + /// + private void UpdateContentVisibility() + { + ContentVisibility = (Header != null || Items.Count > 0) ? Visibility.Visible : Visibility.Collapsed; + } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/NoResetObservableCollection.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/NoResetObservableCollection.cs new file mode 100644 index 00000000..98250808 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/NoResetObservableCollection.cs @@ -0,0 +1,42 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Collections; + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// An observable collection that cannot be reset. When clear is called + /// items are removed individually, giving listeners the chance to detect + /// each remove event and perform operations such as unhooking event + /// handlers. + /// + /// The type of item in the collection. + internal class NoResetObservableCollection : ObservableCollection + { + /// + /// Instantiates a new instance of the NoResetObservableCollection + /// class. + /// + public NoResetObservableCollection() + { + } + + /// + /// Clears all items in the collection by removing them individually. + /// + protected override void ClearItems() + { + IList items = new List(this); + foreach (T item in items) + { + Remove(item); + } + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/ObjectPool.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/ObjectPool.cs new file mode 100644 index 00000000..a118baad --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/ObjectPool.cs @@ -0,0 +1,148 @@ +// (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; +using System.Collections.Generic; + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// A pool of objects that can be reused. + /// + /// The type of object in the pool. + internal class ObjectPool + { + /// + /// The default minimum number objects to keep in the pool. + /// + private const int DefaultMinimumObjectsInThePool = 10; + +#if DEBUG + /// + /// A value indicating whether the pool is being traversed. + /// + private bool _traversing = false; + +#endif + /// + /// A function which creates objects. + /// + private Func _createObject; + + /// + /// The list of objects. + /// + private List _objects; + + /// + /// The index of the current item in the list. + /// + private int currentIndex = 0; + + /// + /// The minimum number of objects to keep in the pool. + /// + private int minimumObjectsInThePool; + + /// + /// Initializes a new instance of the ObjectPool class. + /// + /// The minimum number of objects + /// to keep in the pool. + /// The function that creates the objects. + /// + public ObjectPool( + int minimumObjectsInThePool, + Func createObject) + { + this._objects = new List(minimumObjectsInThePool); + this.minimumObjectsInThePool = minimumObjectsInThePool; + this._createObject = createObject; + + Reset(); + } + + /// + /// Initializes a new instance of the ObjectPool class. + /// + /// The function that creates the objects. + /// + public ObjectPool(Func createObject) + : this(DefaultMinimumObjectsInThePool, createObject) + { + } + + /// + /// Performs an operation on the subsequent, already-created objects + /// in the pool. + /// + /// The action to perform on the remaining objects. + /// + public void ForEachRemaining(Action action) + { + for (var cnt = currentIndex; cnt < _objects.Count; cnt++) + { + action(_objects[cnt]); + } + } + + /// + /// Creates a new object or reuses an existing object in the pool. + /// + /// A new or existing object in the pool. + public T Next() + { + if (currentIndex == _objects.Count) + { + _objects.Add(_createObject()); + } + T item = _objects[currentIndex]; + currentIndex++; + return item; + } + + /// + /// Resets the pool of objects. + /// + public void Reset() + { +#if DEBUG + _traversing = true; +#endif + currentIndex = 0; + } + + /// + /// Finishes the object creation process. + /// + /// + /// If there are substantially more remaining objects in the pool those + /// objects may be removed. + /// + public void Done() + { +#if DEBUG + _traversing = false; +#endif + // Remove the rest of the objects if we are using less than + // half of the pool. + if (currentIndex != 0 && _objects.Count > 0 && currentIndex >= minimumObjectsInThePool && currentIndex < _objects.Count / 2) + { + _objects.RemoveRange(currentIndex, _objects.Count - currentIndex); + } + } + + /// + /// Removes the objects from the pool. + /// + public void Clear() + { +#if DEBUG + System.Diagnostics.Debug.Assert(!_traversing, "Cannot clear an object pool while it is being traversed."); +#endif + _objects.Clear(); + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/ObservableCollectionListAdapter.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/ObservableCollectionListAdapter.cs new file mode 100644 index 00000000..97bd99a8 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/ObservableCollectionListAdapter.cs @@ -0,0 +1,178 @@ +// (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; +using System.Windows.Controls; + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// An object that synchronizes changes in an observable collection to + /// a list. + /// + /// The type of the objects in the collection. + /// + internal class ObservableCollectionListAdapter + where T : class + { + /// + /// The collection to synchronize with a list. + /// + private IEnumerable _collection; + + /// + /// Gets or sets the collection to synchronize with a list. + /// + public IEnumerable Collection + { + get + { + return _collection; + } + set + { + IEnumerable oldValue = _collection; + INotifyCollectionChanged oldObservableCollection = oldValue as INotifyCollectionChanged; + INotifyCollectionChanged newObservableCollection = value as INotifyCollectionChanged; + _collection = value; + + if (oldObservableCollection != null) + { + oldObservableCollection.CollectionChanged -= OnCollectionChanged; + } + + if (value == null && TargetList != null) + { + TargetList.Clear(); + } + if (newObservableCollection != null) + { + newObservableCollection.CollectionChanged += OnCollectionChanged; + } + } + } + + /// + /// Gets or sets the panel to synchronize with the collection. + /// + public IList TargetList { get; set; } + + /// + /// Method that synchronizes the panel's child collection with the + /// contents of the observable collection when it changes. + /// + /// The source of the event. + /// Information about the event. + public void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (TargetList != null) + { + if (e.Action == NotifyCollectionChangedAction.Reset) + { + TargetList.Clear(); + } + else if (e.Action == NotifyCollectionChangedAction.Replace) + { + for (int cnt = 0; cnt < e.OldItems.Count; cnt++) + { + T oldItem = e.OldItems[cnt] as T; + T newItem = e.NewItems[cnt] as T; + + int index = TargetList.IndexOf(oldItem); + + if (index != -1) + { + TargetList[index] = newItem; + } + else + { + TargetList.Remove(oldItem); + TargetList.Add(newItem); + } + } + } + else + { + if (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems != null) + { + foreach (T element in e.OldItems) + { + TargetList.Remove(element); + } + } + else if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null) + { + int startingIndex = e.NewStartingIndex; + if (startingIndex != 0) + { + T previousElement = Collection.FastElementAt(startingIndex - 1); + int targetListIndex = TargetList.IndexOf(previousElement); + if (targetListIndex != -1) + { + startingIndex = targetListIndex + 1; + } + } + else if (Collection.FastCount() > 1) + { + T nextElement = Collection.FastElementAt(startingIndex + 1); + int targetListIndex = TargetList.IndexOf(nextElement); + if (targetListIndex == -1) + { + startingIndex = 0; + } + else + { + startingIndex = targetListIndex; + } + } + + e.NewItems + .OfType() + .ForEachWithIndex((item, index) => + { + TargetList.Insert(startingIndex + index, item); + }); + } + } + } + } + + /// + /// A method that populates a panel with the items in the collection. + /// + public void Populate() + { + if (TargetList != null) + { + if (Collection != null) + { + foreach (T item in Collection) + { + TargetList.Add(item); + } + } + else + { + TargetList.Clear(); + } + } + } + + /// + /// Removes the items in the adapted list from the target list. + /// + public void ClearItems() + { + foreach (T item in this.Collection) + { + this.TargetList.Remove(item); + } + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/OrientedPanel.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/OrientedPanel.cs new file mode 100644 index 00000000..d697482a --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/OrientedPanel.cs @@ -0,0 +1,671 @@ +// (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; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Media; +using System.Windows.Shapes; + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// A panel that plots elements on a one dimensional plane. In order to + /// minimize collisions it moves elements further and further from the edge + /// of the plane based on their priority. Elements that have the same + /// priority level are always the same distance from the edge. + /// + internal class OrientedPanel : Panel + { + #region public double ActualMinimumDistanceBetweenChildren + /// + /// Gets the actual minimum distance between children. + /// + public double ActualMinimumDistanceBetweenChildren + { + get { return (double)GetValue(ActualMinimumDistanceBetweenChildrenProperty); } + private set { SetValue(ActualMinimumDistanceBetweenChildrenProperty, value); } + } + + /// + /// Identifies the ActualMinimumDistanceBetweenChildren dependency property. + /// + public static readonly DependencyProperty ActualMinimumDistanceBetweenChildrenProperty = + DependencyProperty.Register( + "ActualMinimumDistanceBetweenChildren", + typeof(double), + typeof(OrientedPanel), + new PropertyMetadata(0.0)); + + #endregion public double ActualMinimumDistanceBetweenChildren + + #region public double MinimumDistanceBetweenChildren + /// + /// Gets or sets the minimum distance between children. + /// + public double MinimumDistanceBetweenChildren + { + get { return (double)GetValue(MinimumDistanceBetweenChildrenProperty); } + set { SetValue(MinimumDistanceBetweenChildrenProperty, value); } + } + + /// + /// Identifies the MinimumDistanceBetweenChildren dependency property. + /// + public static readonly DependencyProperty MinimumDistanceBetweenChildrenProperty = + DependencyProperty.Register( + "MinimumDistanceBetweenChildren", + typeof(double), + typeof(OrientedPanel), + new PropertyMetadata(0.0, OnMinimumDistanceBetweenChildrenPropertyChanged)); + + /// + /// MinimumDistanceBetweenChildrenProperty property changed handler. + /// + /// OrientedPanel that changed its MinimumDistanceBetweenChildren. + /// Event arguments. + private static void OnMinimumDistanceBetweenChildrenPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + OrientedPanel source = (OrientedPanel)d; + double oldValue = (double)e.OldValue; + double newValue = (double)e.NewValue; + source.OnMinimumDistanceBetweenChildrenPropertyChanged(oldValue, newValue); + } + + /// + /// MinimumDistanceBetweenChildrenProperty property changed handler. + /// + /// Old value. + /// New value. + protected virtual void OnMinimumDistanceBetweenChildrenPropertyChanged(double oldValue, double newValue) + { + InvalidateMeasure(); + } + #endregion public double MinimumDistanceBetweenChildren + + #region public double ActualLength + /// + /// Gets the actual length of the panel. + /// + public double ActualLength + { + get { return (double)GetValue(ActualLengthProperty); } + } + + /// + /// Identifies the ActualLength dependency property. + /// + public static readonly DependencyProperty ActualLengthProperty = + DependencyProperty.Register( + "ActualLength", + typeof(double), + typeof(OrientedPanel), + new PropertyMetadata(0.0)); + #endregion public double ActualLength + + #region public attached double CenterCoordinate + /// + /// Gets the value of the CenterCoordinate attached property for a specified UIElement. + /// + /// The UIElement from which the property value is read. + /// The CenterCoordinate property value for the UIElement. + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "This is an attached property and is only intended to be set on UIElement's")] + public static double GetCenterCoordinate(UIElement element) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + return (double) element.GetValue(CenterCoordinateProperty); + } + + /// + /// Sets the value of the CenterCoordinate attached property to a specified UIElement. + /// + /// The UIElement to which the attached property is written. + /// The needed CenterCoordinate value. + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "This is an attached property and is only intended to be set on UIElement's")] + public static void SetCenterCoordinate(UIElement element, double value) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + element.SetValue(CenterCoordinateProperty, value); + } + + /// + /// Identifies the CenterCoordinate dependency property. + /// + public static readonly DependencyProperty CenterCoordinateProperty = + DependencyProperty.RegisterAttached( + "CenterCoordinate", + typeof(double), + typeof(OrientedPanel), + new PropertyMetadata(OnCenterCoordinatePropertyChanged)); + + /// + /// CenterCoordinateProperty property changed handler. + /// + /// UIElement that changed its CenterCoordinate. + /// Event arguments. + public static void OnCenterCoordinatePropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs) + { + UIElement source = dependencyObject as UIElement; + if (source == null) + { + throw new ArgumentNullException("dependencyObject"); + } + OrientedPanel parent = VisualTreeHelper.GetParent(source) as OrientedPanel; + if (parent != null) + { + parent.InvalidateMeasure(); + } + } + #endregion public attached double CenterCoordinate + + #region public double OffsetPadding + /// + /// Gets or sets the amount of offset padding to add between items. + /// + public double OffsetPadding + { + get { return (double)GetValue(OffsetPaddingProperty); } + set { SetValue(OffsetPaddingProperty, value); } + } + + /// + /// Identifies the OffsetPadding dependency property. + /// + public static readonly DependencyProperty OffsetPaddingProperty = + DependencyProperty.Register( + "OffsetPadding", + typeof(double), + typeof(OrientedPanel), + new PropertyMetadata(0.0, OnOffsetPaddingPropertyChanged)); + + /// + /// OffsetPaddingProperty property changed handler. + /// + /// OrientedPanel that changed its OffsetPadding. + /// Event arguments. + private static void OnOffsetPaddingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + OrientedPanel source = (OrientedPanel)d; + double oldValue = (double)e.OldValue; + double newValue = (double)e.NewValue; + source.OnOffsetPaddingPropertyChanged(oldValue, newValue); + } + + /// + /// OffsetPaddingProperty property changed handler. + /// + /// Old value. + /// New value. + protected virtual void OnOffsetPaddingPropertyChanged(double oldValue, double newValue) + { + this.InvalidateMeasure(); + } + #endregion public double OffsetPadding + + #region public attached int Priority + /// + /// Gets the value of the Priority attached property for a specified UIElement. + /// + /// The UIElement from which the property value is read. + /// The Priority property value for the UIElement. + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "This is an attached property and is only intended to be set on UIElement's")] + public static int GetPriority(UIElement element) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + return (int) element.GetValue(PriorityProperty); + } + + /// + /// Sets the value of the Priority attached property to a specified UIElement. + /// + /// The UIElement to which the attached property is written. + /// The needed Priority value. + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "This is an attached property and is only intended to be set on UIElement's")] + public static void SetPriority(UIElement element, int value) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + element.SetValue(PriorityProperty, value); + } + + /// + /// Identifies the Priority dependency property. + /// + public static readonly DependencyProperty PriorityProperty = + DependencyProperty.RegisterAttached( + "Priority", + typeof(int), + typeof(OrientedPanel), + new PropertyMetadata(OnPriorityPropertyChanged)); + + /// + /// PriorityProperty property changed handler. + /// + /// UIElement that changed its Priority. + /// Event arguments. + public static void OnPriorityPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs) + { + UIElement source = dependencyObject as UIElement; + if (source == null) + { + throw new ArgumentNullException("dependencyObject"); + } + OrientedPanel parent = VisualTreeHelper.GetParent(source) as OrientedPanel; + if (parent != null) + { + parent.InvalidateMeasure(); + } + } + #endregion public attached int Priority + + #region public bool IsInverted + /// + /// Gets or sets a value indicating whether the panel is inverted. + /// + public bool IsInverted + { + get { return (bool)GetValue(IsInvertedProperty); } + set { SetValue(IsInvertedProperty, value); } + } + + /// + /// Identifies the IsInverted dependency property. + /// + public static readonly DependencyProperty IsInvertedProperty = + DependencyProperty.Register( + "IsInverted", + typeof(bool), + typeof(OrientedPanel), + new PropertyMetadata(false, OnIsInvertedPropertyChanged)); + + /// + /// IsInvertedProperty property changed handler. + /// + /// OrientedPanel that changed its IsInverted. + /// Event arguments. + private static void OnIsInvertedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + OrientedPanel source = (OrientedPanel)d; + bool oldValue = (bool)e.OldValue; + bool newValue = (bool)e.NewValue; + source.OnIsInvertedPropertyChanged(oldValue, newValue); + } + + /// + /// IsInvertedProperty property changed handler. + /// + /// Old value. + /// New value. + protected virtual void OnIsInvertedPropertyChanged(bool oldValue, bool newValue) + { + InvalidateMeasure(); + } + #endregion public bool IsInverted + + #region public bool IsReversed + /// + /// Gets or sets a value indicating whether the direction is reversed. + /// + public bool IsReversed + { + get { return (bool)GetValue(IsReversedProperty); } + set { SetValue(IsReversedProperty, value); } + } + + /// + /// Identifies the IsReversed dependency property. + /// + public static readonly DependencyProperty IsReversedProperty = + DependencyProperty.Register( + "IsReversed", + typeof(bool), + typeof(OrientedPanel), + new PropertyMetadata(false, OnIsReversedPropertyChanged)); + + /// + /// IsReversedProperty property changed handler. + /// + /// OrientedPanel that changed its IsReversed. + /// Event arguments. + private static void OnIsReversedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + OrientedPanel source = (OrientedPanel)d; + bool oldValue = (bool)e.OldValue; + bool newValue = (bool)e.NewValue; + source.OnIsReversedPropertyChanged(oldValue, newValue); + } + + /// + /// IsReversedProperty property changed handler. + /// + /// Old value. + /// New value. + protected virtual void OnIsReversedPropertyChanged(bool oldValue, bool newValue) + { + InvalidateMeasure(); + } + #endregion public bool IsReversed + + #region public Orientation Orientation + /// + /// Gets or sets the orientation of the panel. + /// + public Orientation Orientation + { + get { return (Orientation)GetValue(OrientationProperty); } + set { SetValue(OrientationProperty, value); } + } + + /// + /// Identifies the Orientation dependency property. + /// + public static readonly DependencyProperty OrientationProperty = + DependencyProperty.Register( + "Orientation", + typeof(Orientation), + typeof(OrientedPanel), + new PropertyMetadata(Orientation.Horizontal, OnOrientationPropertyChanged)); + + /// + /// OrientationProperty property changed handler. + /// + /// OrientedPanel that changed its Orientation. + /// Event arguments. + private static void OnOrientationPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + OrientedPanel source = (OrientedPanel)d; + Orientation newValue = (Orientation)e.NewValue; + source.OnOrientationPropertyChanged(newValue); + } + + /// + /// OrientationProperty property changed handler. + /// + /// New value. + protected virtual void OnOrientationPropertyChanged(Orientation newValue) + { + UpdateActualLength(); + InvalidateMeasure(); + } + #endregion public Orientation Orientation + + /// + /// Gets or sets the offset of the edge to use for each priority group. + /// + private IDictionary PriorityOffsets { get; set; } + + /// + /// Instantiates a new instance of the OrientedPanel class. + /// + public OrientedPanel() + { + UpdateActualLength(); + } + + /// + /// Updates the actual length property. + /// + private void UpdateActualLength() + { + this.SetBinding(ActualLengthProperty, new Binding((Orientation == Orientation.Horizontal) ? "ActualWidth" : "ActualHeight") { Source = this }); + } + + /// + /// Returns a sequence of ranges for a given sequence of children and a + /// length selector. + /// + /// A sequence of children. + /// A function that returns a length given + /// a UIElement. + /// A sequence of ranges. + private static IEnumerable> GetRanges(IEnumerable children, Func lengthSelector) + { + return + children + .Select(child => + { + double centerCoordinate = GetCenterCoordinate(child); + double halfLength = lengthSelector(child) / 2; + return new Range(centerCoordinate - halfLength, centerCoordinate + halfLength); + }); + } + + /// + /// Measures children and determines necessary size. + /// + /// The available size. + /// The necessary size. + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Linq use artificially increases cyclomatic complexity. Linq functions are well-understood.")] + protected override Size MeasureOverride(Size availableSize) + { + double offset = 0.0; + if (Children.Count > 0) + { + Size totalSize = new Size(double.PositiveInfinity, double.PositiveInfinity); + foreach (UIElement child in this.Children) + { + child.Measure(totalSize); + } + + Func lengthSelector = null; + Func offsetSelector = null; + + if (Orientation == Orientation.Horizontal) + { + lengthSelector = child => GetCorrectedDesiredSize(child).Width; + offsetSelector = child => GetCorrectedDesiredSize(child).Height; + } + else + { + lengthSelector = child => GetCorrectedDesiredSize(child).Height; + offsetSelector = child => GetCorrectedDesiredSize(child).Width; + } + + IEnumerable> priorityGroups = + from child in Children.CastWrapper() + group child by GetPriority(child) into priorityGroup + select priorityGroup; + + ActualMinimumDistanceBetweenChildren = + (from priorityGroup in priorityGroups + let orderedElements = + (from element in priorityGroup + orderby GetCenterCoordinate(element) ascending + select element).ToList() + where orderedElements.Count >= 2 + select + (EnumerableFunctions.Zip( + orderedElements, + orderedElements.Skip(1), + (leftElement, rightElement) => + { + double halfLeftLength = lengthSelector(leftElement) / 2; + double leftCenterCoordinate = GetCenterCoordinate(leftElement); + + double halfRightLength = lengthSelector(rightElement) / 2; + double rightCenterCoordinate = GetCenterCoordinate(rightElement); + + return (rightCenterCoordinate - halfRightLength) - (leftCenterCoordinate + halfLeftLength); + })) + .Min()) + .MinOrNullable() ?? MinimumDistanceBetweenChildren; + + IEnumerable priorities = + Children + .CastWrapper() + .Select(child => GetPriority(child)).Distinct().OrderBy(priority => priority).ToList(); + + PriorityOffsets = new Dictionary(); + foreach (int priority in priorities) + { + PriorityOffsets[priority] = 0.0; + } + + IEnumerable> priorityPairs = + EnumerableFunctions.Zip(priorities, priorities.Skip(1), (previous, next) => new Tuple(previous, next)); + + foreach (Tuple priorityPair in priorityPairs) + { + IEnumerable currentPriorityChildren = Children.CastWrapper().Where(child => GetPriority(child) == priorityPair.Item1).ToList(); + + IEnumerable> currentPriorityRanges = + GetRanges(currentPriorityChildren, lengthSelector); + + IEnumerable nextPriorityChildren = Children.CastWrapper().Where(child => GetPriority(child) == priorityPair.Item2).ToList(); + + IEnumerable> nextPriorityRanges = + GetRanges(nextPriorityChildren, lengthSelector); + + bool intersects = + (from currentPriorityRange in currentPriorityRanges + from nextPriorityRange in nextPriorityRanges + select currentPriorityRange.IntersectsWith(nextPriorityRange)) + .Any(value => value); + + if (intersects) + { + double maxCurrentPriorityChildOffset = + currentPriorityChildren + .Select(child => offsetSelector(child)) + .MaxOrNullable() ?? 0.0; + + offset += maxCurrentPriorityChildOffset + OffsetPadding; + } + PriorityOffsets[priorityPair.Item2] = offset; + } + + offset = + (Children + .CastWrapper() + .GroupBy(child => GetPriority(child)) + .Select( + group => + group + .Select(child => PriorityOffsets[group.Key] + offsetSelector(child)) + .MaxOrNullable())) + .Where(num => num.HasValue) + .Select(num => num.Value) + .MaxOrNullable() ?? 0.0; + } + + if (Orientation == Orientation.Horizontal) + { + return new Size(0, offset); + } + else + { + return new Size(offset, 0); + } + } + + /// + /// Arranges items according to position and priority. + /// + /// The final size of the panel. + /// The final size of the control. + protected override Size ArrangeOverride(Size finalSize) + { + foreach (UIElement child in Children) + { + double x = 0.0; + double y = 0.0; + + x = GetCenterCoordinate(child); + y = PriorityOffsets[GetPriority(child)]; + + double totalLength = 0.0; + double totalOffsetLength = 0.0; + double length = 0.0; + double offsetLength = 0.0; + Size childCorrectedDesiredSize = GetCorrectedDesiredSize(child); + if (Orientation == Orientation.Horizontal) + { + totalLength = finalSize.Width; + length = childCorrectedDesiredSize.Width; + offsetLength = childCorrectedDesiredSize.Height; + totalOffsetLength = finalSize.Height; + } + else if (Orientation == Orientation.Vertical) + { + totalLength = finalSize.Height; + length = childCorrectedDesiredSize.Height; + offsetLength = childCorrectedDesiredSize.Width; + totalOffsetLength = finalSize.Width; + } + + double halfLength = length / 2; + + double left = 0.0; + double top = 0.0; + if (!IsReversed) + { + left = x - halfLength; + } + else + { + left = totalLength - Math.Round(x + halfLength); + } + if (!IsInverted) + { + top = y; + } + else + { + top = totalOffsetLength - Math.Round(y + offsetLength); + } + + left = Math.Min(Math.Round(left), totalLength - 1); + top = Math.Round(top); + if (Orientation == Orientation.Horizontal) + { + child.Arrange(new Rect(left, top, length, offsetLength)); + } + else if (Orientation == Orientation.Vertical) + { + child.Arrange(new Rect(top, left, offsetLength, length)); + } + } + + return finalSize; + } + + /// + /// Gets the "corrected" DesiredSize (for Line instances); one that is + /// more consistent with how the elements actually render. + /// + /// UIElement to get the size for. + /// Corrected size. + private static Size GetCorrectedDesiredSize(UIElement element) + { + Line elementAsLine = element as Line; + if (null != elementAsLine) + { + return new Size( + Math.Max(elementAsLine.StrokeThickness, elementAsLine.X2 - elementAsLine.X1), + Math.Max(elementAsLine.StrokeThickness, elementAsLine.Y2 - elementAsLine.Y1)); + } + else + { + return element.DesiredSize; + } + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Properties/AssemblyInfo.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..54127dbb --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Properties/AssemblyInfo.cs @@ -0,0 +1,60 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; +using System; +using System.Windows.Markup; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Extended WPF Toolkit DataVisualization")] +[assembly: AssemblyDescription("WPF Toolkit Data Visualization Controls")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Extended WPF Toolkit")] +[assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: CLSCompliant(true)] +[assembly: NeutralResourcesLanguage("en-US")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("4.0.0.0")] +[assembly: AssemblyFileVersion("4.0.0.0")] diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Properties/Resources.Designer.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Properties/Resources.Designer.cs new file mode 100644 index 00000000..d6769dfc --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Properties/Resources.Designer.cs @@ -0,0 +1,352 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.1 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace System.Windows.Controls.DataVisualization.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("System.Windows.Controls.DataVisualization.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Changing the Chart's Axes property is not supported; changes to the collection should be made with its .Add/.Remove methods instead.. + /// + internal static string Chart_Axes_SetterNotSupported { + get { + return ResourceManager.GetString("Chart_Axes_SetterNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Axis location cannot be changed to auto when hosted inside of a series host.. + /// + internal static string Chart_AxisLocationChanged_CantBeChangedToAutoWhenHostedInsideOfASeriesHost { + get { + return ResourceManager.GetString("Chart_AxisLocationChanged_CantBeChangedToAutoWhenHostedInsideOfASeriesHost", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Changing the Chart's Series property is not supported; changes to the collection should be made with its .Add/.Remove methods instead.. + /// + internal static string Chart_Series_SetterNotSupported { + get { + return ResourceManager.GetString("Chart_Series_SetterNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The content property must be set to a Chart object.. + /// + internal static string DataPointSeriesDropTarget_set_Content_ContentMustBeAChart { + get { + return ResourceManager.GetString("DataPointSeriesDropTarget_set_Content_ContentMustBeAChart", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Assigned dependent axis cannot be used. This may be due to an unset Orientation property for the axis or a type mismatch between the values being plotted and those supported by the axis.. + /// + internal static string DataPointSeriesWithAxes_GetAxes_AssignedDependentAxisCannotBeUsed { + get { + return ResourceManager.GetString("DataPointSeriesWithAxes_GetAxes_AssignedDependentAxisCannotBeUsed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Assigned independent axis cannot be used. This may be due to an unset Orientation property for the axis.. + /// + internal static string DataPointSeriesWithAxes_GetAxes_AssignedIndependentAxisCannotBeUsed { + get { + return ResourceManager.GetString("DataPointSeriesWithAxes_GetAxes_AssignedIndependentAxisCannotBeUsed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No suitable axis is available for plotting the dependent value.. + /// + internal static string DataPointSeriesWithAxes_NoSuitableAxisAvailableForPlottingDependentValue { + get { + return ResourceManager.GetString("DataPointSeriesWithAxes_NoSuitableAxisAvailableForPlottingDependentValue", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This series does not support radial axes.. + /// + internal static string DataPointSeriesWithAxes_ThisSeriesDoesNotSupportRadialAxes { + get { + return ResourceManager.GetString("DataPointSeriesWithAxes_ThisSeriesDoesNotSupportRadialAxes", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A SeriesDefinition does not have its DependentValueBinding or DependentValuePath properties set; unable to determine the source for dependent values. Please use either of these properties to identify the data model property corresponding to the dependent values.. + /// + internal static string DefinitionSeries_EnsureAxes_MissingDependentValueBinding { + get { + return ResourceManager.GetString("DefinitionSeries_EnsureAxes_MissingDependentValueBinding", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A SeriesDefinition does not have its IndependentValueBinding or IndependentValuePath properties set; unable to determine the source for independent values. Please use either of these properties to identify the data model property corresponding to the independent values.. + /// + internal static string DefinitionSeries_EnsureAxes_MissingIndependentValueBinding { + get { + return ResourceManager.GetString("DefinitionSeries_EnsureAxes_MissingIndependentValueBinding", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Changing the SeriesDefinitions property of a DefinitionSeries is not supported; changes to the collection should be made with its .Add/.Remove methods instead.. + /// + internal static string DefinitionSeries_SeriesDefinitions_SetterNotSupported { + get { + return ResourceManager.GetString("DefinitionSeries_SeriesDefinitions_SetterNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot determine the size of an axis without an orientation.. + /// + internal static string DisplayAxis_GetLength_CannotDetermineTheLengthOfAnAxisWithAnOrientationOfNone { + get { + return ResourceManager.GetString("DisplayAxis_GetLength_CannotDetermineTheLengthOfAnAxisWithAnOrientationOfNone", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Attempt to set Edge property to an invalid value.. + /// + internal static string EdgePanel_OnEdgePropertyChanged { + get { + return ResourceManager.GetString("EdgePanel_OnEdgePropertyChanged", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The DataRangeBinding property of the Interpolator must be bound to an IConvertible object.. + /// + internal static string Interpolator_IncludeInRange_DataRangeBindingNotIConvertible { + get { + return ResourceManager.GetString("Interpolator_IncludeInRange_DataRangeBindingNotIConvertible", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The maximum value must be larger than or equal to the minimum value.. + /// + internal static string Range_ctor_MaximumValueMustBeLargerThanOrEqualToMinimumValue { + get { + return ResourceManager.GetString("Range_ctor_MaximumValueMustBeLargerThanOrEqualToMinimumValue", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to "Cannot read the Maximum of an empty range.". + /// + internal static string Range_get_Maximum_CannotReadTheMaximumOfAnEmptyRange { + get { + return ResourceManager.GetString("Range_get_Maximum_CannotReadTheMaximumOfAnEmptyRange", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to "Cannot read the Minimum of an empty range.". + /// + internal static string Range_get_Minimum_CannotReadTheMinimumOfAnEmptyRange { + get { + return ResourceManager.GetString("Range_get_Minimum_CannotReadTheMinimumOfAnEmptyRange", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to [{0},{1}]. + /// + internal static string Range_ToString_Data { + get { + return ResourceManager.GetString("Range_ToString_Data", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No Data.. + /// + internal static string Range_ToString_NoData { + get { + return ResourceManager.GetString("Range_ToString_NoData", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The maximum value must be larger than or equal to the minimum value.. + /// + internal static string RangeAxis_MaximumValueMustBeLargerThanOrEqualToMinimumValue { + get { + return ResourceManager.GetString("RangeAxis_MaximumValueMustBeLargerThanOrEqualToMinimumValue", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The minimum value must be smaller than or equal to the maximum value.. + /// + internal static string RangeAxis_MinimumValueMustBeLargerThanOrEqualToMaximumValue { + get { + return ResourceManager.GetString("RangeAxis_MinimumValueMustBeLargerThanOrEqualToMaximumValue", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Collection is read-only.. + /// + internal static string ReadOnlyObservableCollection_CollectionIsReadOnly { + get { + return ResourceManager.GetString("ReadOnlyObservableCollection_CollectionIsReadOnly", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot reset a ResourceDictionaryEnumerator; reset the ResourceDictionaryDispenser instead.. + /// + internal static string ResourceDictionaryEnumerator_CantResetEnumeratorResetDispenserInstead { + get { + return ResourceManager.GetString("ResourceDictionaryEnumerator_CantResetEnumeratorResetDispenserInstead", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Series {0}. + /// + internal static string Series_OnGlobalSeriesIndexPropertyChanged_UntitledSeriesFormatString { + get { + return ResourceManager.GetString("Series_OnGlobalSeriesIndexPropertyChanged_UntitledSeriesFormatString", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A series cannot be added to more than one series host.. + /// + internal static string Series_SeriesHost_SeriesHostPropertyNotNull { + get { + return ResourceManager.GetString("Series_SeriesHost_SeriesHostPropertyNotNull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A SeriesDefinition instance's SeriesHost property may only be set to an instance of the DefinitionSeries class; other SeriesHost types are not supported.. + /// + internal static string SeriesDefinition_SeriesHost_InvalidParent { + get { + return ResourceManager.GetString("SeriesDefinition_SeriesHost_InvalidParent", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid attempt to remove permanent axis from axis collection.. + /// + internal static string SeriesHostAxesCollection_InvalidAttemptToRemovePermanentAxisFromSeriesHost { + get { + return ResourceManager.GetString("SeriesHostAxesCollection_InvalidAttemptToRemovePermanentAxisFromSeriesHost", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An axis cannot be removed from a series host when one or more hosted series is listening to it.. + /// + internal static string SeriesHostAxesCollection_RemoveItem_AxisCannotBeRemovedFromASeriesHostWhenOneOrMoreSeriesAreListeningToIt { + get { + return ResourceManager.GetString("SeriesHostAxesCollection_RemoveItem_AxisCannotBeRemovedFromASeriesHostWhenOneOrMo" + + "reSeriesAreListeningToIt", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Either ItemTemplate must be set or TreeMapItemDefinitionSelector must return a non-null template.. + /// + internal static string TreeMap_BuildTreeMapTree_TemplateNotSet { + get { + return ResourceManager.GetString("TreeMap_BuildTreeMapTree_TemplateNotSet", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The value of PropertyBinding property of an interpolator cannot be null.. + /// + internal static string TreeMap_CreateChildren_InterpolatorBindingNotSet { + get { + return ResourceManager.GetString("TreeMap_CreateChildren_InterpolatorBindingNotSet", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Changing the TreeMap's Interpolators Property is not supported; changes to the collection should be made with its .Add/.Remove methods instead.. + /// + internal static string TreeMap_Interpolators_SetterNotSupported { + get { + return ResourceManager.GetString("TreeMap_Interpolators_SetterNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid attempt to insert a duplicate item.. + /// + internal static string UniqueObservableCollection_InvalidAttemptToInsertADuplicateItem { + get { + return ResourceManager.GetString("UniqueObservableCollection_InvalidAttemptToInsertADuplicateItem", resourceCulture); + } + } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Properties/Resources.resx b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Properties/Resources.resx new file mode 100644 index 00000000..5055ff3c --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Properties/Resources.resx @@ -0,0 +1,248 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Changing the Chart's Axes property is not supported; changes to the collection should be made with its .Add/.Remove methods instead. + Setter is public to work around a limitation with the XAML editing tools. + + + Axis location cannot be changed to auto when hosted inside of a series host. + Exception message. + + + Changing the Chart's Series property is not supported; changes to the collection should be made with its .Add/.Remove methods instead. + Setter is public to work around a limitation with the XAML editing tools. + + + The content property must be set to a Chart object. + Exception message. + + + Assigned dependent axis cannot be used. This may be due to an unset Orientation property for the axis or a type mismatch between the values being plotted and those supported by the axis. + Exception message. + + + Assigned independent axis cannot be used. This may be due to an unset Orientation property for the axis. + Exception message. + + + No suitable axis is available for plotting the dependent value. + Exception message. + + + This series does not support radial axes. + Exception message. + + + A SeriesDefinition does not have its DependentValueBinding or DependentValuePath properties set; unable to determine the source for dependent values. Please use either of these properties to identify the data model property corresponding to the dependent values. + Exception message. + + + A SeriesDefinition does not have its IndependentValueBinding or IndependentValuePath properties set; unable to determine the source for independent values. Please use either of these properties to identify the data model property corresponding to the independent values. + Exception message. + + + Changing the SeriesDefinitions property of a DefinitionSeries is not supported; changes to the collection should be made with its .Add/.Remove methods instead. + Setter is public to work around a limitation with the XAML editing tools. + + + Cannot determine the size of an axis without an orientation. + Exception message. + + + Attempt to set Edge property to an invalid value. + Exception message. + + + The DataRangeBinding property of the Interpolator must be bound to an IConvertible object. + Exception thrown when an interpolator is bound to a value which is not IConvertible. + + + The maximum value must be larger than or equal to the minimum value. + Exception message. + + + The minimum value must be smaller than or equal to the maximum value. + Exception message. + + + The maximum value must be larger than or equal to the minimum value. + Exception message. + + + "Cannot read the Maximum of an empty range." + Exception message. + + + "Cannot read the Minimum of an empty range." + Exception message. + + + [{0},{1}] + Range text for ToString method when data is present. + + + No Data. + Range text for ToString method when no data is present. + + + Collection is read-only. + Exception message. + + + Cannot reset a ResourceDictionaryEnumerator; reset the ResourceDictionaryDispenser instead. + Exception message. + + + A SeriesDefinition instance's SeriesHost property may only be set to an instance of the DefinitionSeries class; other SeriesHost types are not supported. + Exception message. + + + Invalid attempt to remove permanent axis from axis collection. + Exception message. + + + An axis cannot be removed from a series host when one or more hosted series is listening to it. + Exception message. + + + Series {0} + Used to format the string returned by the Title property if a Title is not specified. + + + A series cannot be added to more than one series host. + Exception message. + + + Either ItemTemplate must be set or TreeMapItemDefinitionSelector must return a non-null template. + Exception thrown when the template for the current item is null. + + + The value of PropertyBinding property of an interpolator cannot be null. + Exception thrown when the PropertyBinding property of one of the interpolators is null. + + + Changing the TreeMap's Interpolators Property is not supported; changes to the collection should be made with its .Add/.Remove methods instead. + Setter is public to work around a limitation with the XAML editing tools. + + + Invalid attempt to insert a duplicate item. + Exception message. + + \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Properties/Settings.Designer.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Properties/Settings.Designer.cs new file mode 100644 index 00000000..56ac64f2 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.1 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace System.Windows.Controls.DataVisualization.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "10.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Properties/Settings.settings b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Properties/Settings.settings new file mode 100644 index 00000000..8f2fd95d --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Range.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Range.cs new file mode 100644 index 00000000..e1ebada5 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Range.cs @@ -0,0 +1,271 @@ +// (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; +using System.Collections.Generic; +using System.Globalization; + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// A range of values. + /// + /// The type of the values. + /// Preview + public struct Range + where T : IComparable + { + /// + /// A flag that determines whether the range is empty or not. + /// + private bool _hasData; + + /// + /// Gets a value indicating whether the range is empty or not. + /// + public bool HasData + { + get + { + return _hasData; + } + } + + /// + /// The maximum value in the range. + /// + private T _maximum; + + /// + /// Gets the maximum value in the range. + /// + public T Maximum + { + get + { + if (!HasData) + { + throw new InvalidOperationException(Properties.Resources.Range_get_Maximum_CannotReadTheMaximumOfAnEmptyRange); + } + return _maximum; + } + } + + /// + /// The minimum value in the range. + /// + private T _minimum; + + /// + /// Gets the minimum value in the range. + /// + public T Minimum + { + get + { + if (!HasData) + { + throw new InvalidOperationException(Properties.Resources.Range_get_Minimum_CannotReadTheMinimumOfAnEmptyRange); + } + return _minimum; + } + } + + /// + /// Initializes a new instance of the Range class. + /// + /// The minimum value. + /// The maximum value. + public Range(T minimum, T maximum) + { + if (minimum == null) + { + throw new ArgumentNullException("minimum"); + } + if (maximum == null) + { + throw new ArgumentNullException("maximum"); + } + + _hasData = true; + _minimum = minimum; + _maximum = maximum; + + int compareValue = ValueHelper.Compare(minimum, maximum); + if (compareValue == 1) + { + throw new InvalidOperationException(Properties.Resources.Range_ctor_MaximumValueMustBeLargerThanOrEqualToMinimumValue); + } + } + + /// + /// Compare two ranges and return a value indicating whether they are + /// equal. + /// + /// Left-hand side range. + /// Right-hand side range. + /// A value indicating whether the ranges are equal. + public static bool operator ==(Range leftRange, Range rightRange) + { + if (!leftRange.HasData) + { + return !rightRange.HasData; + } + if (!rightRange.HasData) + { + return !leftRange.HasData; + } + + return leftRange.Minimum.Equals(rightRange.Minimum) && leftRange.Maximum.Equals(rightRange.Maximum); + } + + /// + /// Compare two ranges and return a value indicating whether they are + /// not equal. + /// + /// Left-hand side range. + /// Right-hand side range. + /// A value indicating whether the ranges are not equal. + /// + public static bool operator !=(Range leftRange, Range rightRange) + { + return !(leftRange == rightRange); + } + + /// + /// Adds a range to the current range. + /// + /// A range to add to the current range. + /// A new range that encompasses the instance range and the + /// range parameter. + public Range Add(Range range) + { + if (!this.HasData) + { + return range; + } + else if (!range.HasData) + { + return this; + } + T minimum = ValueHelper.Compare(this.Minimum, range.Minimum) == -1 ? this.Minimum : range.Minimum; + T maximum = ValueHelper.Compare(this.Maximum, range.Maximum) == 1 ? this.Maximum : range.Maximum; + return new Range(minimum, maximum); + } + + /// + /// Compares the range to another range. + /// + /// A different range. + /// A value indicating whether the ranges are equal. + public bool Equals(Range range) + { + return this == range; + } + + /// + /// Compares the range to an object. + /// + /// Another object. + /// A value indicating whether the other object is a range, + /// and if so, whether that range is equal to the instance range. + /// + public override bool Equals(object obj) + { + Range range = (Range)obj; + if (range == null) + { + return false; + } + return this == range; + } + + /// + /// Returns a value indicating whether a value is within a range. + /// + /// The value. + /// Whether the value is within the range. + public bool Contains(T value) + { + return ValueHelper.Compare(Minimum, value) <= 0 && ValueHelper.Compare(value, Maximum) <= 0; + } + + /////// + /////// Returns a new range that contains the value. + /////// + /////// The value to extend the range to. + /////// The range which contains the value. + ////public Range ExtendTo(T value) + ////{ + //// if (!HasData) + //// { + //// return new Range(value, value); + //// } + + //// if (ValueHelper.Compare(Minimum, value) > 0) + //// { + //// return new Range(value, Maximum); + //// } + //// else if (ValueHelper.Compare(Maximum, value) < 0) + //// { + //// return new Range(Minimum, value); + //// } + + //// return this; + ////} + + /// + /// Returns a value indicating whether two ranges intersect. + /// + /// The range to compare against this range. + /// A value indicating whether the ranges intersect. + public bool IntersectsWith(Range range) + { + if (!this.HasData || !range.HasData) + { + return false; + } + + Func, Range, bool> rightCollidesWithLeft = + (leftRange, rightRange) => + (ValueHelper.Compare(rightRange.Minimum, leftRange.Maximum) <= 0 && ValueHelper.Compare(rightRange.Minimum, leftRange.Minimum) >= 0) + || (ValueHelper.Compare(leftRange.Minimum, rightRange.Maximum) <= 0 && ValueHelper.Compare(leftRange.Minimum, rightRange.Minimum) >= 0); + + return rightCollidesWithLeft(this, range) || rightCollidesWithLeft(range, this); + } + + /// + /// Computes a hash code value. + /// + /// A hash code value. + public override int GetHashCode() + { + if (!HasData) + { + return 0; + } + + int num = 0x5374e861; + num = (-1521134295 * num) + EqualityComparer.Default.GetHashCode(Minimum); + return ((-1521134295 * num) + EqualityComparer.Default.GetHashCode(Maximum)); + } + + /// + /// Returns the string representation of the range. + /// + /// The string representation of the range. + public override string ToString() + { + if (!this.HasData) + { + return Properties.Resources.Range_ToString_NoData; + } + else + { + return string.Format(CultureInfo.CurrentCulture, Properties.Resources.Range_ToString_Data, this.Minimum, this.Maximum); + } + } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/RangeEnumerableFunctions.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/RangeEnumerableFunctions.cs new file mode 100644 index 00000000..9a5e82d6 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/RangeEnumerableFunctions.cs @@ -0,0 +1,68 @@ +// (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; +using System.Collections.Generic; +using System.Linq; + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// Collection of functions that manipulate streams of ranges. + /// + internal static class RangeEnumerableExtensions + { + /// + /// Returns the minimum and maximum values in a stream. + /// + /// The type of the stream. + /// The stream. + /// The range of values in the stream. + public static Range GetRange(this IEnumerable that) + where T : IComparable + { + IEnumerator enumerator = that.GetEnumerator(); + if (!enumerator.MoveNext()) + { + return new Range(); + } + T minimum = enumerator.Current; + T maximum = minimum; + while (enumerator.MoveNext()) + { + T current = enumerator.Current; + if (ValueHelper.Compare(minimum, current) == 1) + { + minimum = current; + } + if (ValueHelper.Compare(maximum, current) == -1) + { + maximum = current; + } + } + return new Range(minimum, maximum); + } + + /// + /// Returns a range encompassing all ranges in a stream. + /// + /// The type of the minimum and maximum values. + /// + /// The stream. + /// A range encompassing all ranges in a stream. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nesting necessary to provide method for use with all types of Range.")] + public static Range Sum(this IEnumerable> that) + where T : IComparable + { + Range sum = new Range(); + IEnumerator> enumerator = that.GetEnumerator(); + while (enumerator.MoveNext()) + { + sum = sum.Add(enumerator.Current); + } + return sum; + } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/ReadOnlyObservableCollection.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/ReadOnlyObservableCollection.cs new file mode 100644 index 00000000..f69bad37 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/ReadOnlyObservableCollection.cs @@ -0,0 +1,107 @@ +// (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; +using System.Collections.ObjectModel; + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// An observable collection that can only be written to by internal + /// classes. + /// + /// The type of object in the observable collection. + /// + internal class ReadOnlyObservableCollection : NoResetObservableCollection + { + /// + /// Gets or sets a value indicating whether the owner is writing to the + /// collection. + /// + private bool IsMutating { get; set; } + + /// + /// A method that mutates the collection. + /// + /// The action to mutate the collection. + public void Mutate(Action> action) + { + IsMutating = true; + try + { + action(this); + } + finally + { + IsMutating = false; + } + } + + /// + /// Removes an item from the collection at an index. + /// + /// The index to remove. + protected override void RemoveItem(int index) + { + if (!IsMutating) + { + throw new NotSupportedException(Properties.Resources.ReadOnlyObservableCollection_CollectionIsReadOnly); + } + else + { + base.RemoveItem(index); + } + } + + /// + /// Sets an item at a particular location in the collection. + /// + /// The location to set an item. + /// The item to set. + protected override void SetItem(int index, T item) + { + if (!IsMutating) + { + throw new NotSupportedException(Properties.Resources.ReadOnlyObservableCollection_CollectionIsReadOnly); + } + else + { + base.SetItem(index, item); + } + } + + /// + /// Inserts an item in the collection. + /// + /// The index at which to insert the item. + /// The item to insert. + protected override void InsertItem(int index, T item) + { + if (!IsMutating) + { + throw new NotSupportedException(Properties.Resources.ReadOnlyObservableCollection_CollectionIsReadOnly); + } + else + { + base.InsertItem(index, item); + } + } + + /// + /// Clears the items from the collection. + /// + protected override void ClearItems() + { + if (!IsMutating) + { + throw new NotSupportedException(Properties.Resources.ReadOnlyObservableCollection_CollectionIsReadOnly); + } + else + { + base.ClearItems(); + } + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/ResourceDictionaryCollection.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/ResourceDictionaryCollection.cs new file mode 100644 index 00000000..6179c639 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/ResourceDictionaryCollection.cs @@ -0,0 +1,23 @@ +// (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.ObjectModel; + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// Represents a collection of ResourceDictionary objects. + /// + /// Preview + public partial class ResourceDictionaryCollection : Collection + { + /// + /// Initializes a new instance of the ResourceDictionaryCollection class. + /// + public ResourceDictionaryCollection() + { + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/ResourceDictionaryExtensions.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/ResourceDictionaryExtensions.cs new file mode 100644 index 00000000..51241307 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/ResourceDictionaryExtensions.cs @@ -0,0 +1,30 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization +{ +#if SILVERLIGHT + /// + /// Extension methods for the ResourceDictionary class. + /// + public static class ResourceDictionaryExtensions + { + /// + /// Makes a shallow copy of the specified ResourceDictionary. + /// + /// ResourceDictionary to copy. + /// Copied ResourceDictionary. + public static ResourceDictionary ShallowCopy(this ResourceDictionary dictionary) + { + ResourceDictionary clone = new ResourceDictionary(); + foreach (object key in dictionary.Keys) + { + clone.Add(key, dictionary[key]); + } + return clone; + } + } +#endif +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/StoryboardQueue.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/StoryboardQueue.cs new file mode 100644 index 00000000..476cbcd3 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/StoryboardQueue.cs @@ -0,0 +1,62 @@ +// (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; +using System.Collections.Generic; +using System.Windows.Media.Animation; + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// Represents a storyboard queue that plays storyboards in sequence. + /// + internal class StoryboardQueue + { + /// + /// A queue of the storyboards. + /// + private Queue _storyBoards = new Queue(); + + /// + /// Accepts a new storyboard to play in sequence. + /// + /// The storyboard to play. + /// An action to execute when the + /// storyboard completes. + public void Enqueue(Storyboard storyBoard, EventHandler completedAction) + { + storyBoard.Completed += + (sender, args) => + { + if (completedAction != null) + { + completedAction(sender, args); + } + + _storyBoards.Dequeue(); + Dequeue(); + }; + + _storyBoards.Enqueue(storyBoard); + + if (_storyBoards.Count == 1) + { + Dequeue(); + } + } + + /// + /// Removes the next storyboard in the queue and plays it. + /// + private void Dequeue() + { + if (_storyBoards.Count > 0) + { + Storyboard storyboard = _storyBoards.Peek(); + storyboard.Begin(); + } + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/StringFormatConverter.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/StringFormatConverter.cs new file mode 100644 index 00000000..ac22c359 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/StringFormatConverter.cs @@ -0,0 +1,49 @@ +// (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; +using System.Globalization; +using System.Windows.Data; + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// Converts a value to a string using a format string. + /// + public class StringFormatConverter : IValueConverter + { + /// + /// Converts a value to a string by formatting it. + /// + /// The value to convert. + /// The target type of the conversion. + /// The format string. + /// The culture to use for conversion. + /// The formatted string. + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null) + { + return string.Empty; + } + + return string.Format(CultureInfo.CurrentCulture, (parameter as string) ?? "{0}", value); + } + + /// + /// Converts a value from a string to a target type. + /// + /// The value to convert to a string. + /// The target type of the conversion. + /// A parameter used during the conversion + /// process. + /// The culture to use for the conversion. + /// The converted object. + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotSupportedException(); + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Themes/Generic.xaml b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Themes/Generic.xaml new file mode 100644 index 00000000..0749ae0d --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Themes/Generic.xaml @@ -0,0 +1,1174 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Title/Title.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Title/Title.cs new file mode 100644 index 00000000..8183bb06 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Title/Title.cs @@ -0,0 +1,37 @@ +// (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.Windows; +using System.Windows.Controls; + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// Represents the title of a data visualization control. + /// + /// Preview + public partial class Title : ContentControl + { +#if !SILVERLIGHT + /// + /// Initializes the static members of the Title class. + /// + static Title() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(Title), new FrameworkPropertyMetadata(typeof(Title))); + } + +#endif + /// + /// Initializes a new instance of the Title class. + /// + public Title() + { +#if SILVERLIGHT + DefaultStyleKey = typeof(Title); +#endif + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/BindingExtractor.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/BindingExtractor.cs new file mode 100644 index 00000000..3887e9bd --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/BindingExtractor.cs @@ -0,0 +1,79 @@ +// (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.Windows.Data; + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// Helper class which can extract the value from a source object using a binding path. It + /// creates a Binding object based on the path, and calls SetBinding to a temporary + /// FrameworkElement (base class) to extract the value. + /// + internal class BindingExtractor : FrameworkElement + { + #region public object Value + /// + /// Gets or sets a generic Value which will be the target of the binding. + /// + private object Value + { + get { return (object)GetValue(ValueProperty); } + set { SetValue(ValueProperty, value); } + } + + /// + /// Identifies the Value dependency property. + /// + private static readonly DependencyProperty ValueProperty = + DependencyProperty.Register( + "Value", + typeof(object), + typeof(BindingExtractor), + null); + #endregion + + /// + /// Returns the value of the given Binding when applied on the given object instance. + /// It does that by making a copy of the binding, setting its source to be the object + /// instance and the target to be the member Value property. + /// + /// Object instance containing the property. + /// Binding to the property to be retrieved. + /// The value of the binding. + public object RetrieveProperty(object instance, Binding valueBinding) + { + // We need to make a new instance each time because you can't change + // the Source of a Binding once it has been set. + Binding binding = new Binding() + { + BindsDirectlyToSource = valueBinding.BindsDirectlyToSource, + Converter = valueBinding.Converter, + ConverterCulture = valueBinding.ConverterCulture, + ConverterParameter = valueBinding.ConverterParameter, + Mode = valueBinding.Mode, + NotifyOnValidationError = valueBinding.NotifyOnValidationError, + Path = valueBinding.Path, + Source = instance, + UpdateSourceTrigger = valueBinding.UpdateSourceTrigger, + ValidatesOnExceptions = valueBinding.ValidatesOnExceptions, +#if !NO_COMPLETE_BINDING_PROPERTY_LIST + FallbackValue = valueBinding.FallbackValue, + StringFormat = valueBinding.StringFormat, + TargetNullValue = valueBinding.TargetNullValue, + ValidatesOnDataErrors = valueBinding.ValidatesOnDataErrors, +#endif +#if !NO_VALIDATESONNOTIFYDATAERRORS + ValidatesOnNotifyDataErrors = valueBinding.ValidatesOnNotifyDataErrors, +#endif + }; + + SetBinding(BindingExtractor.ValueProperty, binding); + + // Now this dependency property has been updated. + return Value; + } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/Interpolators/DoubleInterpolator.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/Interpolators/DoubleInterpolator.cs new file mode 100644 index 00000000..17e521ea --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/Interpolators/DoubleInterpolator.cs @@ -0,0 +1,35 @@ +// (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.Globalization; + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// Interpolator which converts a numeric value from its [RangeMinimum, RangeMaximum] + /// range to another value in the range [From, To]. + /// + /// Preview + public class DoubleInterpolator : RangeInterpolator + { + /// + /// Interpolates the given value between its [RangeMinimum, RangeMaximum] range + /// and returns an interpolated value in the range [From, To]. + /// + /// Value to interpolate. + /// An interpolated value in the range [From, To]. + public override object Interpolate(double value) + { + double result = From; + if (ActualDataMaximum - ActualDataMinimum != 0) + { + double ratio = (value - ActualDataMinimum) / (ActualDataMaximum - ActualDataMinimum); + result = From + (ratio * (To - From)); + } + + return result; + } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/Interpolators/InterpolationMode.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/Interpolators/InterpolationMode.cs new file mode 100644 index 00000000..0b678203 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/Interpolators/InterpolationMode.cs @@ -0,0 +1,24 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// Specifies the supported interpolation modes. + /// + /// Preview + public enum InterpolationMode + { + /// + /// Interpolation shall be applied to leaf nodes only in the tree. + /// + LeafNodesOnly = 0, + + /// + /// Interpolation shall be applied to all nodes in the tree. + /// + AllNodes = 1, + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/Interpolators/Interpolator.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/Interpolators/Interpolator.cs new file mode 100644 index 00000000..5e998782 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/Interpolators/Interpolator.cs @@ -0,0 +1,225 @@ +// (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.Globalization; +using System.Windows.Data; + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// Abstract base class for Interpolator converters. + /// + /// + /// + /// An Interpolator is used to project a value from a source range + /// [ActualDataMinimum, ActualDataMaximum] to a target range [From, To]. + /// The source range can be specified directly by setting the DataMinimum + /// and/or DataMaximum properties, or indirectly by setting DataRangeBinding. + /// When the DataRangeBinding property is set,the TreeMap will evaluate the + /// binding for the entire tree, calculating the minimum and maximum values + /// automatically. The custom target range and the actual interpolation + /// logic is defined by sub-classes of this abstract class. + /// + /// + /// Preview + public abstract class Interpolator : FrameworkElement + { + /// + /// Holds a helper object used to extract values using a property path. + /// + private BindingExtractor _helper; + + /// + /// Gets or sets a value telling to which tree nodes the interpolation + /// should be applied to. LeafNodesOnly by default. + /// + public InterpolationMode InterpolationMode { get; set; } + + /// + /// Gets or sets a value representing the x:Name of the element to which + /// the interpolated value will be applied. + /// + public string TargetName { get; set; } + + /// + /// Gets or sets a value representing the path to a property which will + /// receive the interpolated value. + /// + public string TargetProperty { get; set; } + + #region public double DataMinimum + /// + /// Gets or sets a value representing the fixed minimum value across the + /// entire set. If the value is not set directly or is NaN, the + /// ActualDataMaximum will be calculated automatically from the data set + /// by using the DataRangeBinding property. + /// + public double DataMinimum + { + get { return (double)GetValue(DataMinimumProperty); } + set { SetValue(DataMinimumProperty, value); } + } + + /// + /// Identifies the DataMinimum dependency property. + /// + public static readonly DependencyProperty DataMinimumProperty = + DependencyProperty.Register( + "DataMinimum", + typeof(double), + typeof(Interpolator), + new PropertyMetadata(double.NaN)); + #endregion public double DataMinimum + + #region public double DataMaximum + /// + /// Gets or sets a value representing the fixed maximum value across the + /// entire set. If the value is not set directly or is NaN, the + /// ActualDataMinimum will be calculated automatically from the data set + /// by using the DataRangeBinding property. + /// + public double DataMaximum + { + get { return (double)GetValue(DataMaximumProperty); } + set { SetValue(DataMaximumProperty, value); } + } + + /// + /// Identifies the DataMaximum dependency property. + /// + public static readonly DependencyProperty DataMaximumProperty = + DependencyProperty.Register( + "DataMaximum", + typeof(double), + typeof(Interpolator), + new PropertyMetadata(double.NaN)); + #endregion public double DataMaximum + + /// + /// This fields contains the automatically calculated maximal value in + /// the dataset. + /// + private double _actualDataMaximum; + + /// + /// Gets the value representing the maximal value in the data set. It is + /// automatically from the data set by using the DataRangeBinding + /// property if DataMaximum is not set. If it is set, DataMaximum is + /// returned. + /// + public double ActualDataMaximum + { + get + { + if (Double.IsNaN(DataMaximum)) + { + return _actualDataMaximum; + } + else + { + return DataMaximum; + } + } + + internal set + { + _actualDataMaximum = value; + } + } + + /// + /// This fields contains the automatically calculated minimal value in + /// the dataset. + /// + private double _actualDataMinimum; + + /// + /// Gets the value representing the minimal value in the data set. It is + /// automatically from the data set by using the DataRangeBinding + /// property if DataMinimum is not set. If it is set, DataMinimum is + /// returned. + /// + public double ActualDataMinimum + { + get + { + if (Double.IsNaN(DataMinimum)) + { + return _actualDataMinimum; + } + else + { + return DataMinimum; + } + } + + internal set + { + _actualDataMinimum = value; + } + } + + /// + /// Gets or sets a binding to a property which will be examined to retrieve the minimum and maximum range + /// values across the entire data set. If this value is null then the DataMinimum and DataMaximum values + /// need be set manually. + /// + public Binding DataRangeBinding { get; set; } + + /// + /// Initializes a new instance of the Interpolator class. + /// + protected Interpolator() + { + InterpolationMode = InterpolationMode.LeafNodesOnly; + ActualDataMinimum = double.PositiveInfinity; + ActualDataMaximum = double.NegativeInfinity; + _helper = new BindingExtractor(); + } + + /// + /// If the DataRangeBinding property is set then this method updates the minimum/maximum range + /// of this object by including the value passed in. + /// + /// Object to extract the value from (the Source of the DataRangeBinding). + internal virtual void IncludeInRange(object data) + { + if (DataRangeBinding != null) + { + if (!Double.IsNaN(DataMinimum) && !Double.IsNaN(DataMaximum)) + { + return; + } + + IConvertible input = _helper.RetrieveProperty(data, DataRangeBinding) as IConvertible; + if (input == null) + { + throw new ArgumentException( + Properties.Resources.Interpolator_IncludeInRange_DataRangeBindingNotIConvertible); + } + + double value = input.ToDouble(CultureInfo.InvariantCulture); + if (Double.IsNaN(DataMinimum) && value < ActualDataMinimum) + { + ActualDataMinimum = value; + } + + if (Double.IsNaN(DataMaximum) && value > ActualDataMaximum) + { + ActualDataMaximum = value; + } + } + } + + /// + /// Called to interpolate the value of the given object between the DataMinimum and DataMaximum + /// extremes, and to project it in a specific [From, To] range defined. The target range (and + /// therefore the implementation of this method) is defined in a specific sub-class. + /// + /// Value to interpolate. + /// An interpolated value. + public abstract object Interpolate(double value); + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/Interpolators/RangeInterpolator.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/Interpolators/RangeInterpolator.cs new file mode 100644 index 00000000..689801e2 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/Interpolators/RangeInterpolator.cs @@ -0,0 +1,26 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// Abstract class representing an interpolator which projects values to + /// a continuous range defined by the From and To properties. + /// + /// The data type of the values in the target range. + /// Preview + public abstract class RangeInterpolator : Interpolator + { + /// + /// Gets or sets a value representing the start value of the target range. + /// + public T From { get; set; } + + /// + /// Gets or sets a value representing the end value of the target range. + /// + public T To { get; set; } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/Interpolators/SolidColorBrushInterpolator.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/Interpolators/SolidColorBrushInterpolator.cs new file mode 100644 index 00000000..50bf3ef3 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/Interpolators/SolidColorBrushInterpolator.cs @@ -0,0 +1,40 @@ +// (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.Windows.Media; + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// Interpolator which converts a numeric value from its [RangeMinimum, RangeMaximum] + /// range to a color in the range [From, To]. + /// + /// Preview + public class SolidColorBrushInterpolator : RangeInterpolator + { + /// + /// Interpolates the given value between its [RangeMinimum, RangeMaximum] range + /// and returns a color in the range [From, To]. + /// + /// Value to interpolate. + /// An interpolated color in the range [From, To]. + public override object Interpolate(double value) + { + Color color = From; + if (ActualDataMaximum - ActualDataMinimum != 0) + { + double ratio = (value - ActualDataMinimum) / (ActualDataMaximum + - ActualDataMinimum); + + color = Color.FromArgb( + (byte)(From.A + (ratio * (To.A - From.A))), + (byte)(From.R + (ratio * (To.R - From.R))), + (byte)(From.G + (ratio * (To.G - From.G))), + (byte)(From.B + (ratio * (To.B - From.B)))); + } + + return new SolidColorBrush(color); + } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/Layout/SquaringAlgorithm.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/Layout/SquaringAlgorithm.cs new file mode 100644 index 00000000..d35cd603 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/Layout/SquaringAlgorithm.cs @@ -0,0 +1,202 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; +using System.Linq; + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// Class encapsulating the logic of sub-dividing a parent rectangular area into child rectangles. + /// It implements the squaring tree map algorithm where all child nodes are allocated + /// areas proportional to their values, but the aspect ratio of each rectangle is kept + /// as close as possible to a square. + /// + internal class SquaringAlgorithm + { + /// + /// Holds the list of nodes being considered by the algorithm. + /// + private IList _areas; + + /// + /// The current rectangle being divided. + /// + private Rect _currentRectangle; + + /// + /// Internal index in the list of nodes being divided. + /// + private int _currentStart; + + /// + /// Temporary variable used during the algorithm. Represents the ratio between + /// the real area of the rectangle and the virtual value associated with the node. + /// + private double _factor; + + /// + /// Subdivides the parent rectangle using squaring tree map algorithm into + /// rectangles with areas specified by the children. The areas must add up + /// to at most the area of the rectangle. + /// + /// Total area being split. + /// The node associated with the total area. The + /// children of this node will be allocated small chunks of the parent rectangle. + /// How much of a gap should be left between the parent rectangle and the children. + /// A list of RectangularArea objects describing areas associated with each of the children of parentNode. + public IEnumerable> Split(Rect parentRectangle, TreeMapNode parentNode, Thickness margin) + { + IEnumerable> retVal; + + double area = parentNode.Area; + if (parentNode.Children == null || parentNode.Children.Count() == 0 || area == 0) + { + retVal = Enumerable.Empty>(); + } + else + { + if (parentRectangle.Width - margin.Left - margin.Right <= 0 || + parentRectangle.Height - margin.Top - margin.Bottom <= 0) + { + // Margins too big, no more room for children. Returning + // zero sized rectangles for all children. + retVal = from child in parentNode.Children + select new Tuple(new Rect(0, 0, 0, 0), child); + } + else + { + // Leave as much room as specified by the margin + _currentRectangle = new Rect( + parentRectangle.X + margin.Left, + parentRectangle.Y + margin.Top, + parentRectangle.Width - margin.Left - margin.Right, + parentRectangle.Height - margin.Top - margin.Bottom); + + _areas = (from child in parentNode.Children + where child.Area != 0 + orderby child.Area descending + select child).ToArray(); + + // Factor is only computed once and used during the algorithm + _factor = _currentRectangle.Width * _currentRectangle.Height / area; + + retVal = BuildTreeMap().ToArray(); + } + } + + return retVal; + } + + /// + /// This function returns an IEnumerable over the rectangles associated with the children, + /// as divided using the tree map algorithm. + /// + /// A list of RectangularArea objects describing areas associated with each of the children. + private IEnumerable> BuildTreeMap() + { + _currentStart = 0; + while (_currentStart < _areas.Count) + { + foreach (Tuple rectangle in BuildTreeMapStep()) + { + yield return rectangle; + } + } + } + + /// + /// Performs one step of the body of the squaring tree map algorithm. + /// + /// List of rectangles calculated by this step. + private IEnumerable> BuildTreeMapStep() + { + int last = _currentStart; + double total = 0; + double prevAspect = double.PositiveInfinity; + double wh = 0; + bool horizontal = _currentRectangle.Width > _currentRectangle.Height; + for (; last < _areas.Count; last++) + { + total += GetArea(last); + wh = total / (horizontal ? _currentRectangle.Height : _currentRectangle.Width); + double curAspect = Math.Max(GetAspect(_currentStart, wh), GetAspect(last, wh)); + if (curAspect > prevAspect) + { + total -= GetArea(last); + wh = total / (horizontal ? _currentRectangle.Height : _currentRectangle.Width); + last--; + break; + } + + prevAspect = curAspect; + } + + if (last == _areas.Count) + { + last--; + } + + double x = _currentRectangle.Left; + double y = _currentRectangle.Top; + + for (int i = _currentStart; i <= last; i++) + { + if (horizontal) + { + double h = GetArea(i) / wh; + Rect rect = new Rect(x, y, wh, h); + yield return new Tuple(rect, _areas[i]); + y += h; + } + else + { + double w = GetArea(i) / wh; + Rect rect = new Rect(x, y, w, wh); + yield return new Tuple(rect, _areas[i]); + x += w; + } + } + + _currentStart = last + 1; + + if (horizontal) + { + _currentRectangle = new Rect(_currentRectangle.Left + wh, _currentRectangle.Top, Math.Max(0, _currentRectangle.Width - wh), _currentRectangle.Height); + } + else + { + _currentRectangle = new Rect(_currentRectangle.Left, _currentRectangle.Top + wh, _currentRectangle.Width, Math.Max(0, _currentRectangle.Height - wh)); + } + } + + /// + /// Returns the calculated area of the node at the given index. + /// + /// Index of the node to consider. + /// Area of the node, calculated based on the node's value multiplied by the current factor. + private double GetArea(int i) + { + return _areas[i].Area * _factor; + } + + /// + /// Returns the aspect ratio of the area associated with the node at the given index. + /// + /// Index of the node to consider. + /// Width of the area. + /// Positive supra-unitary ratio of the given area. + private double GetAspect(int i, double wh) + { + double aspect = GetArea(i) / (wh * wh); + if (aspect < 1) + { + aspect = 1.0 / aspect; + } + + return aspect; + } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/Layout/TreeMapNode.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/Layout/TreeMapNode.cs new file mode 100644 index 00000000..7fc6d88b --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/Layout/TreeMapNode.cs @@ -0,0 +1,71 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics.CodeAnalysis; + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// Holds the information needed by the tree map layout algorithm, such as the area + /// associated with this node and the list of children. The class also contains + /// an DataContext object which is the real user context, and a reference to the UI + /// container associated with this node. + /// + internal class TreeMapNode + { + /// + /// Gets or sets a value representing the area associated with this node. + /// This value is relative to all the other values in the hierarchy; the layout + /// algorithm will allocated a real area proportional to this value. + /// + public double Area { get; set; } + + /// + /// Gets or sets the list of children under this node. + /// + public IEnumerable Children { get; set; } + + /// + /// Gets or sets a value representing the WeakEventListener associated with the + /// ItemsSource that created the children of this node. + /// + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Used by implementation; warning is only for the test project.")] + internal WeakEventListener WeakEventListener { get; set; } + + /// + /// Gets or sets a value representing a reference to the user's custom data object. + /// + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Used by implementation; warning is only for the test project.")] + public object DataContext { get; set; } + + /// + /// Gets or sets a value representing the associated Silverlight UI element. + /// + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Used by implementation; warning is only for the test project.")] + public FrameworkElement Element { get; set; } + + /// + /// Gets or sets a value representing the TreeMapItemDefinition used to describe + /// properties of this item. + /// + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Used by implementation; warning is only for the test project.")] + public TreeMapItemDefinition ItemDefinition { get; set; } + + /// + /// Gets or sets a value representing the padding between this node and its children. + /// + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Used by implementation; warning is only for the test project.")] + public Thickness ChildItemPadding { get; set; } + + /// + /// Gets or sets a value representing the level of this node in the tree (the + /// root node is at level 0). + /// + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Used by implementation; warning is only for the test project.")] + public int Level { get; set; } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/TreeMap.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/TreeMap.cs new file mode 100644 index 00000000..c747794f --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/TreeMap.cs @@ -0,0 +1,754 @@ +// (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.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using System.Windows.Data; +using System.Windows.Markup; + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// Represents a control which can display hierarchical data as a set of nested rectangles. + /// Each item in the hierarchy is laid out in a rectangular area of a size proportional to + /// the value associated with the item. + /// + /// + /// You populate a TreeMap by setting its ItemsSource property to the root of the hierarchy + /// you would like to display. The ItemDefinition property must be set to an instance of a + /// TreeMapItemDefinition with appropriate bindings for Value (identifying the value to be used + /// when calculating relative item sizes) and ItemsSource (identifying the collection of + /// children for each item). + /// + /// Preview + [TemplatePart(Name = ContainerName, Type = typeof(Canvas))] + [ContentProperty("ItemsSource")] + public class TreeMap : Control + { + /// + /// The name of the Container template part. + /// + private const string ContainerName = "Container"; + + #region private object InterpolatorValue + /// + /// Identifies the InterpolatorValue dependency property. + /// + private static readonly DependencyProperty InterpolatorValueProperty = + DependencyProperty.Register( + "InterpolatorValue", + typeof(object), + typeof(TreeMap), + null); + + /// + /// Gets or sets a generic value used as a temporary storage used as a source for TargetName/TargetProperty binding. + /// + private object InterpolatorValue + { + get { return (object)GetValue(InterpolatorValueProperty); } + set { SetValue(InterpolatorValueProperty, value); } + } + #endregion + + /// + /// Holds a helper object used to extract values using a property path. + /// + private BindingExtractor _helper; + + /// + /// The roots of the pre-calculated parallel tree of TreeMapNodes. + /// + private IEnumerable _nodeRoots; + + /// + /// Cached sequence of all TreeMapNodes used by GetTreeMapNodes. + /// + private IEnumerable _getTreeMapNodesCache; + + #region public TreeMapItemDefinitionSelector TreeMapItemDefinitionSelector + /// + /// Gets or sets the selector used to choose the item template dynamically. + /// + public TreeMapItemDefinitionSelector ItemDefinitionSelector + { + get { return (TreeMapItemDefinitionSelector)GetValue(ItemDefinitionSelectorProperty); } + set { SetValue(ItemDefinitionSelectorProperty, value); } + } + + /// + /// Identifies the ItemDefinitionSelector dependency property. + /// + public static readonly DependencyProperty ItemDefinitionSelectorProperty = + DependencyProperty.Register( + "ItemDefinitionSelector", + typeof(TreeMapItemDefinitionSelector), + typeof(TreeMap), + new PropertyMetadata(OnItemDefinitionSelectorPropertyChanged)); + + /// + /// Called when the value of the TreeMapItemDefinitionSelectorProperty property changes. + /// + /// Reference to the TreeMap object. + /// Event handler arguments. + private static void OnItemDefinitionSelectorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + TreeMap treeMap = d as TreeMap; + if (treeMap != null) + { + TreeMapItemDefinitionSelector oldValue = e.OldValue as TreeMapItemDefinitionSelector; + TreeMapItemDefinitionSelector newValue = e.NewValue as TreeMapItemDefinitionSelector; + treeMap.OnItemDefinitionSelectorPropertyChanged(oldValue, newValue); + } + } + + /// + /// Called when the value of the ItemDefinitionSelectorProperty property changes. + /// Triggers a recalculation of the layout. + /// + /// The old selector. + /// The new selector. + protected virtual void OnItemDefinitionSelectorPropertyChanged(TreeMapItemDefinitionSelector oldValue, TreeMapItemDefinitionSelector newValue) + { + RebuildTree(); + } + + #endregion + + #region public TreeMapItemDefinition ItemDefinition + /// + /// Gets or sets a value representing the template used to display each item. + /// + public TreeMapItemDefinition ItemDefinition + { + get { return (TreeMapItemDefinition)GetValue(ItemDefinitionProperty); } + set { SetValue(ItemDefinitionProperty, value); } + } + + /// + /// Identifies the ItemDefinition dependency property. + /// + public static readonly DependencyProperty ItemDefinitionProperty = + DependencyProperty.Register( + "ItemDefinition", + typeof(TreeMapItemDefinition), + typeof(TreeMap), + new PropertyMetadata(OnItemDefinitionPropertyChanged)); + + /// + /// Called when the value of the ItemDefinitionProperty property changes. + /// + /// Reference to the TreeMap object. + /// Event handler arguments. + private static void OnItemDefinitionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + TreeMap treeMap = d as TreeMap; + if (treeMap != null) + { + TreeMapItemDefinition oldValue = e.OldValue as TreeMapItemDefinition; + TreeMapItemDefinition newValue = e.NewValue as TreeMapItemDefinition; + + // Unregister old TreeMapItemDefinition + if (oldValue != null) + { + oldValue.PropertyChanged -= treeMap.OnItemDefinitionPropertyChanged; + } + + // Register new TreeMapItemDefinition + if (newValue != null) + { + newValue.PropertyChanged += treeMap.OnItemDefinitionPropertyChanged; + } + + treeMap.OnItemDefinitionPropertyChanged(oldValue, newValue); + } + } + + /// + /// This callback ensures that any change in TreeMapItemDefinition. + /// + /// Source TreeMapItemDefinition object. + /// Event handler arguments (parameter name). + private void OnItemDefinitionPropertyChanged(object sender, PropertyChangedEventArgs e) + { + RebuildTree(); + } + + /// + /// Called when the value of the ItemDefinitionProperty property changes. + /// Triggers a recalculation of the layout. + /// + /// The old item definition. + /// The new item definition. + protected virtual void OnItemDefinitionPropertyChanged(TreeMapItemDefinition oldValue, TreeMapItemDefinition newValue) + { + RebuildTree(); + } + #endregion + + #region public IEnumerable ItemsSource + /// + /// Gets or sets a value representing the list of hierarchies used to generate + /// content for the TreeMap. + /// + 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(TreeMap), + new PropertyMetadata(OnItemsSourcePropertyChanged)); + + /// + /// Called when the value of the ItemsSourceProperty property changes. + /// + /// Reference to the TreeMap object. + /// Event handler arguments. + private static void OnItemsSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + TreeMap treeMap = d as TreeMap; + if (treeMap != null) + { + IEnumerable oldValue = e.OldValue as IEnumerable; + IEnumerable newValue = e.NewValue as IEnumerable; + treeMap.OnItemsSourcePropertyChanged(oldValue, newValue); + } + } + + /// + /// Called when the value of the ItemsSourceProperty property changes. + /// + /// The old ItemsSource collection. + /// The new ItemsSource collection. + protected virtual void OnItemsSourcePropertyChanged(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; + } + + // Handle property change + RebuildTree(); + } + + /// + /// WeakEventListener used to handle INotifyCollectionChanged events. + /// + private WeakEventListener _weakEventListener; + + /// + /// 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) + { + RebuildTree(); + } + #endregion + + #region private Collection Interpolators + /// + /// Gets or sets a value representing a collection of interpolators to use in TreeMap. + /// + [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 Interpolators + { + get { return (Collection)GetValue(InterpolatorsProperty); } + set { throw new NotSupportedException(Properties.Resources.TreeMap_Interpolators_SetterNotSupported); } + } + + /// + /// Identifies the Interpolators dependency property. + /// + public static readonly DependencyProperty InterpolatorsProperty = + DependencyProperty.Register( + "Interpolators", + typeof(Collection), + typeof(TreeMap), + new PropertyMetadata(OnInterpolatorsPropertyChanged)); + + /// + /// Called when the value of the InterpolatorsProperty property changes. + /// + /// Reference to the TreeMap object. + /// Event handler arguments. + private static void OnInterpolatorsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + TreeMap treeMap = d as TreeMap; + if (treeMap != null) + { + Collection oldValue = e.OldValue as Collection; + Collection newValue = e.NewValue as Collection; + treeMap.OnInterpolatorsPropertyChanged(oldValue, newValue); + } + } + + /// + /// Called when the value of the InterpolatorsProperty property changes. + /// Triggers a recalculation of the layout. + /// + /// The old Interpolators collection. + /// The new Interpolators collection. + protected virtual void OnInterpolatorsPropertyChanged(Collection oldValue, Collection newValue) + { + RebuildTree(); + } + #endregion + + #region Template Parts + /// + /// The Container template part is used to hold all the items inside + /// a TreeMap. + /// + private Canvas _containerElement; + + /// + /// Gets the Container template part that is used to hold all the items inside + /// a TreeMap. + /// + internal Canvas ContainerElement + { + get { return _containerElement; } + private set + { + // Detach from the old Container element + if (_containerElement != null) + { + _containerElement.Children.Clear(); + } + + // Attach to the new Container element + _containerElement = value; + } + } + #endregion + + /// + /// Initializes a new instance of the TreeMap class. + /// + public TreeMap() + { + _helper = new BindingExtractor(); + + DefaultStyleKey = typeof(TreeMap); + + SetValue(InterpolatorsProperty, new ObservableCollection()); + (Interpolators as ObservableCollection).CollectionChanged += + new NotifyCollectionChangedEventHandler(OnInterpolatorsCollectionChanged); + } + + /// + /// Invoked whenever application code or internal processes call ApplyTemplate. Gets references + /// to the template parts required by this control. + /// + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + ContainerElement = GetTemplateChild(ContainerName) as Canvas; + + RebuildTree(); + } + + /// + /// Constructs a new instance of an element used to display an item in the tree. + /// + /// + /// By default TreeMap will use the template set in its ItemDefinition property, or the value + /// returned from GetTemplateForItemOverride if overridden. Override this method to build a + /// custom element. + /// + /// One of the items in the ItemsSource hierarchy. + /// The level of the item in the hierarchy. + /// A new FrameworkElement which will be added to the TreeMap control. If this + /// method returns null the TreeMap will create the item using the ItemDefinition property, + /// or the value returned by TreeMapItemDefinitionSelector if specified. + protected virtual FrameworkElement GetContainerForItemOverride(object data, int level) + { + return null; + } + + /// + /// Performs the Arrange pass of the layout. + /// + /// + /// We round rectangles to snap to nearest pixels. We do that to avoid + /// anti-aliasing which results in better appearance. Moreover to get + /// correct layout we would need to use UseLayoutRounding=false which + /// is Silverlight specific. A side effect is that areas for rectangles + /// in the visual tree no longer can be used to compare them as dimensions + /// are not rounded and therefore not precise. + /// + /// The final area within the parent that this element should use to arrange itself and its children. + /// The actual size used. + protected override Size ArrangeOverride(Size finalSize) + { + // Sets ActualHeight & ActualWidth for the container + finalSize = base.ArrangeOverride(finalSize); + + if (_nodeRoots != null && ContainerElement != null) + { + // Create a temporary pseudo-root node containing all the top-level nodes + TreeMapNode root = new TreeMapNode() + { + Area = _nodeRoots.Sum(x => x.Area), + Children = _nodeRoots, + ChildItemPadding = new Thickness(0) + }; + + // Calculate associated rectangles. We use ContainerElement, + // not finalSize so all elements that are above it like border + // (with padding and border) are taken into account + IEnumerable> measuredRectangles = ComputeRectangles( + root, + new Rect(0, 0, ContainerElement.ActualWidth, ContainerElement.ActualHeight)); + + // Position everything + foreach (Tuple rectangle in measuredRectangles) + { + FrameworkElement element = rectangle.Item2.Element; + if (element != null) + { + double roundedTop = Math.Round(rectangle.Item1.Top); + double roundedLeft = Math.Round(rectangle.Item1.Left); + double height = Math.Round(rectangle.Item1.Height + rectangle.Item1.Top) - roundedTop; + double width = Math.Round(rectangle.Item1.Width + rectangle.Item1.Left) - roundedLeft; + + // Fully specify element location/size (setting size is required on WPF) + Canvas.SetLeft(element, roundedLeft); + Canvas.SetTop(element, roundedTop); + element.Width = width; + element.Height = height; + + element.Arrange(new Rect(roundedLeft, roundedTop, width, height)); + } + } + } + + return finalSize; + } + + /// + /// Triggers a recalculation of the layout when items are added/removed from the Interpolators collection. + /// + /// Reference to the Interpolators collection. + /// Event handler arguments. + private void OnInterpolatorsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + RebuildTree(); + } + + /// + /// Returns a sequence of TreeMapNodes in breadth-first order. + /// + /// Sequence of TreeMapNodes. + private IEnumerable GetTreeMapNodes() + { + if (_getTreeMapNodesCache == null) + { + // Create a new list + List allNodes = new List(); + + // Seed the queue with the roots + Queue nodes = new Queue(); + foreach (TreeMapNode node in _nodeRoots ?? Enumerable.Empty()) + { + nodes.Enqueue(node); + } + // Process the queue in breadth-first order + while (0 < nodes.Count) + { + TreeMapNode node = nodes.Dequeue(); + allNodes.Add(node); + foreach (TreeMapNode child in node.Children) + { + nodes.Enqueue(child); + } + } + + // Cache the list + _getTreeMapNodesCache = allNodes; + } + + // Return the cached sequence + return _getTreeMapNodesCache; + } + + /// + /// Recursively computes TreeMap rectangles given the root node and the bounding rectangle as start. + /// + /// Root of the TreeMapNode tree. + /// Bounding rectangle which will be sub-divided. + /// A list of RectangularAreas containing a rectangle for each node in the tree. + private IEnumerable> ComputeRectangles(TreeMapNode root, Rect boundingRectangle) + { + Queue> treeQueue = new Queue>(); + treeQueue.Enqueue(new Tuple(boundingRectangle, root)); + + // Perform a breadth-first traversal of the tree + SquaringAlgorithm algorithm = new SquaringAlgorithm(); + while (treeQueue.Count > 0) + { + Tuple currentParent = treeQueue.Dequeue(); + + yield return currentParent; + + foreach (Tuple rectangle in + algorithm.Split(currentParent.Item1, currentParent.Item2, currentParent.Item2.ChildItemPadding)) + { + treeQueue.Enqueue(rectangle); + } + } + } + + /// + /// Builds the parallel trees of TreeMapNodes with references to the original user's trees. + /// + /// The list of roots of the user hierarchies (whatever was passed through ItemsSource). + /// Level being processed at this recursive call (the root node is at level 0). + /// The list of roots of the internal trees of TreeMapNodes. + private IEnumerable BuildTreeMapTree(IEnumerable nodes, int level) + { + List retList = new List(); + + if (nodes == null) + { + return retList; + } + + foreach (object root in nodes) + { + // Give the template selector a chance to override the template for this item. + TreeMapItemDefinition template = null; + if (ItemDefinitionSelector != null) + { + template = ItemDefinitionSelector.SelectItemDefinition(this, root, level); + } + + // Use the default otherwise + if (template == null) + { + template = ItemDefinition; + } + + if (template == null) + { + throw new ArgumentException( + Properties.Resources.TreeMap_BuildTreeMapTree_TemplateNotSet); + } + + // Silently create 0 elements if ValueBinding is set to null + // in the template + if (template.ValueBinding != null) + { + IEnumerable objectChildren = (template.ItemsSource != null) ? + _helper.RetrieveProperty(root, template.ItemsSource) as IEnumerable : + null; + IEnumerable children = (objectChildren != null) ? + BuildTreeMapTree(objectChildren, level + 1) : + children = Enumerable.Empty(); + + // Subscribe to CollectionChanged for the collection + WeakEventListener weakEventListener = null; + INotifyCollectionChanged objectChildrenINotifyCollectionChanged = objectChildren as INotifyCollectionChanged; + if (objectChildrenINotifyCollectionChanged != null) + { + // 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 = (wel) => objectChildrenINotifyCollectionChanged.CollectionChanged -= wel.OnEvent; + objectChildrenINotifyCollectionChanged.CollectionChanged += weakEventListener.OnEvent; + } + + // Auto-aggregate children area values + double area; + if (children.Any()) + { + area = children.Sum(x => x.Area); + } + else + { + IConvertible value = _helper.RetrieveProperty(root, template.ValueBinding) as IConvertible; + if (value == null) + { + // Provide a default value so there's something to display + value = 1.0; + } + + area = value.ToDouble(CultureInfo.InvariantCulture); + } + + // Do not include elements with negative or 0 size in the + // VisualTransition tree. We skip interpolation for such + // elements as well + if (area > 0) + { + // Calculate ranges for all interpolators, only consider leaf + // nodes in the LeafNodesOnly mode, or all nodes in the AllNodes + // mode. + foreach (Interpolator interpolator in Interpolators) + { + if (interpolator.InterpolationMode == InterpolationMode.AllNodes || !children.Any()) + { + interpolator.IncludeInRange(root); + } + } + + retList.Add(new TreeMapNode() + { + DataContext = root, + Level = level, + Area = area, + ItemDefinition = template, + ChildItemPadding = template.ChildItemPadding, + Children = children, + WeakEventListener = weakEventListener, + }); + } + } + } + + return retList; + } + + /// + /// Extracts all children from the user's trees (ItemsSource) into a flat list, and + /// creates UI elements for them. + /// + private void CreateChildren() + { + // Breadth-first traversal so elements closer to the root will be added first, + // so that leaf elements will show on top of them. + foreach (TreeMapNode current in GetTreeMapNodes()) + { + // Create the UI element and keep a reference to it in our tree + FrameworkElement element = GetContainerForItemOverride(current.DataContext, current.Level); + if (element == null && current.ItemDefinition.ItemTemplate != null) + { + element = current.ItemDefinition.ItemTemplate.LoadContent() as FrameworkElement; + } + + // If an element was created + if (element != null) + { + current.Element = element; + + // Apply interpolators to element + foreach (Interpolator interpolator in Interpolators) + { + // Apply interpolators only for leaf nodes in the + // LeafNodesOnly mode, or for all nodes in the AllNodes + // mode. + if (interpolator.InterpolationMode == InterpolationMode.AllNodes || !current.Children.Any()) + { + DependencyObject target = element.FindName(interpolator.TargetName) as DependencyObject; + if (target != null) + { + SetBinding( + InterpolatorValueProperty, + new Binding(interpolator.TargetProperty) { Source = target, Mode = BindingMode.TwoWay }); + + if (interpolator.DataRangeBinding == null) + { + throw new ArgumentException( + Properties.Resources.TreeMap_CreateChildren_InterpolatorBindingNotSet); + } + + // Extract the current value to interpolate + IConvertible value = + _helper.RetrieveProperty(current.DataContext, interpolator.DataRangeBinding) as + IConvertible; + if (value == null) + { + throw new ArgumentException( + Properties.Resources.Interpolator_IncludeInRange_DataRangeBindingNotIConvertible); + } + + // This will update the TargetProperty of the TargetName object + InterpolatorValue = interpolator.Interpolate(value.ToDouble(CultureInfo.InvariantCulture)); + } + } + } + + // Add new child to the panel + element.DataContext = current.DataContext; + ContainerElement.Children.Add(element); + } + } + } + + /// + /// Called internally whenever a property of TreeMap is changed and the internal + /// structures need to be rebuilt in order to recalculate the layout. + /// + private void RebuildTree() + { + if (ContainerElement != null) + { + // Unhook from CollectionChanged + foreach (TreeMapNode treeMapNode in GetTreeMapNodes().Where(n => n.WeakEventListener != null)) + { + treeMapNode.WeakEventListener.Detach(); + } + + // Reset all interpolators + foreach (Interpolator interpolator in Interpolators) + { + interpolator.ActualDataMinimum = double.PositiveInfinity; + interpolator.ActualDataMaximum = double.NegativeInfinity; + interpolator.DataContext = this.DataContext; + } + + // Build the parallel tree of TreeMapNodes needed by the algorithm + _nodeRoots = BuildTreeMapTree(ItemsSource, 0); + + // Clear cache + _getTreeMapNodesCache = null; + + // Populate the TreeMap panel with a flat list of all children + // in the hierarchy passed in. + ContainerElement.Children.Clear(); + CreateChildren(); + + // Refresh UI + InvalidateArrange(); + } + } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/TreeMapItemDefinition.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/TreeMapItemDefinition.cs new file mode 100644 index 00000000..ef672b01 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/TreeMapItemDefinition.cs @@ -0,0 +1,165 @@ +// (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.ComponentModel; +using System.Windows.Data; +using System.Windows.Markup; + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// Represents a class that defines various aspects of TreeMap items. + /// + /// Preview + [ContentProperty("ItemTemplate")] + public class TreeMapItemDefinition : INotifyPropertyChanged + { + /// + /// A value representing the DataTemplate to instantiate in + /// order to create a representation of each TreeMap item. + /// + private DataTemplate _itemTemplate; + + /// + /// Gets or sets a value representing the DataTemplate to instantiate in + /// order to create a representation of each TreeMap item. + /// + public DataTemplate ItemTemplate + { + get { return _itemTemplate; } + set + { + if (value != _itemTemplate) + { + _itemTemplate = value; + NotifyPropertyChanged("ItemTemplate"); + } + } + } + + /// + /// A value representing a binding which can be used + /// to retrieve the value associated with each item, needed to calculate + /// relative areas of TreeMap items. + /// + private Binding _valueBinding; + + /// + /// Gets or sets a value representing a binding which can be used + /// to retrieve the value associated with each item, needed to calculate + /// relative areas of TreeMap items. + /// + public Binding ValueBinding + { + get { return _valueBinding; } + set + { + if (value != _valueBinding) + { + _valueBinding = value; + NotifyPropertyChanged("ValueBinding"); + } + } + } + + /// + /// Gets or sets the Value Path used to set ValueBinding for retrieving + /// the value associated with each item, needed to calculate relative + /// areas of TreeMap items. + /// + public string ValuePath + { + get { return (null != ValueBinding) ? ValueBinding.Path.Path : null; } + set + { + if (value != ValuePath) + { + if (null == value) + { + ValueBinding = null; + } + else + { + ValueBinding = new Binding(value); + } + + // PropertyChanged(); thru ValueBinding + } + } + } + + /// + /// The binding that indicates where to find the collection + /// that represents the next level in the data hierarchy. + /// + private Binding _itemsSource; + + /// + /// Gets or sets the binding that indicates where to find the collection + /// that represents the next level in the data hierarchy. + /// + public Binding ItemsSource + { + get { return _itemsSource; } + set + { + if (value != _itemsSource) + { + _itemsSource = value; + NotifyPropertyChanged("ItemsSource"); + } + } + } + + /// + /// A property representing the amount of space to leave + /// between a parent item and its children. + /// + private Thickness _childItemPadding; + + /// + /// Gets or sets a property representing the amount of space to leave + /// between a parent item and its children. + /// + public Thickness ChildItemPadding + { + get { return _childItemPadding; } + set + { + if (value != _childItemPadding) + { + _childItemPadding = value; + NotifyPropertyChanged("ChildItemPadding"); + } + } + } + + /// + /// Initializes a new instance of the TreeMapItemDefinition class. + /// + public TreeMapItemDefinition() + { + ChildItemPadding = new Thickness(0); + } + + /// + /// PropertyChanged event required by INotifyPropertyChanged. + /// + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Updates the TreeMap if one of properties changes. + /// + /// The parameter name. + protected void NotifyPropertyChanged(string parameterName) + { + PropertyChangedEventHandler handler = PropertyChanged; + if (handler != null) + { + handler(this, new PropertyChangedEventArgs(parameterName)); + } + } + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/TreeMapItemDefinitionSelector.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/TreeMapItemDefinitionSelector.cs new file mode 100644 index 00000000..d7f24cb7 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/TreeMap/TreeMapItemDefinitionSelector.cs @@ -0,0 +1,36 @@ +// (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.ObjectModel; +using System.Windows.Markup; + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// Provides a way to choose a TreeMapItemDefinition based on the data item and + /// the level of the item in the tree. + /// + /// Preview + public abstract class TreeMapItemDefinitionSelector + { + /// + /// Initializes a new instance of the TreeMapItemDefinitionSelector class. + /// + protected TreeMapItemDefinitionSelector() + { + } + + /// + /// Returns an instance of a TreeMapItemDefinition class used to specify properties for the + /// current item. + /// + /// Reference to the TreeMap class. + /// One of the nodes in the ItemsSource hierarchy. + /// The level of the node in the hierarchy. + /// The TreeMapItemDefinition to be used for this node. If this method returns null + /// the TreeMap will use the value of its ItemDefinition property. + public abstract TreeMapItemDefinition SelectItemDefinition(TreeMap treeMap, object item, int level); + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Tuple.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Tuple.cs new file mode 100644 index 00000000..8c4b1aa0 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Tuple.cs @@ -0,0 +1,36 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// Represents a 2-tuple, or pair. + /// + /// The type of the tuple's first component. + /// The type of the tuple's second component. + internal class Tuple + { + /// + /// Gets the value of the current Tuple(T1, T2) object's first component. + /// + public T1 Item1 { get; private set; } + + /// + /// Gets the value of the current Tuple(T1, T2) object's second component. + /// + public T2 Item2 { get; private set; } + + /// + /// Initializes a new instance of the Tuple(T1, T2) class. + /// + /// The value of the tuple's first component. + /// The value of the tuple's second component. + public Tuple(T1 item1, T2 item2) + { + Item1 = item1; + Item2 = item2; + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/UniqueObservableCollection.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/UniqueObservableCollection.cs new file mode 100644 index 00000000..72d216b9 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/UniqueObservableCollection.cs @@ -0,0 +1,64 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// An observable collection that does not allow duplicates. + /// + /// The type of items in the collection. + internal class UniqueObservableCollection : ObservableCollection + { + /// + /// Inserts an item at an index. Throws if the item already exists in the collection. + /// + /// The index at which to insert the item. + /// The item to insert. + protected override void InsertItem(int index, T item) + { + if (!this.Contains(item)) + { + base.InsertItem(index, item); + } + else + { + throw new InvalidOperationException(Properties.Resources.UniqueObservableCollection_InvalidAttemptToInsertADuplicateItem); + } + } + + /// + /// Sets an item at a given index. Throws if the item already exists at another index. + /// + /// The index at which to insert the item. + /// The item to be inserted. + protected override void SetItem(int index, T item) + { + int newItemIndex = this.IndexOf(item); + if (newItemIndex != -1 && newItemIndex != index) + { + throw new InvalidOperationException(Properties.Resources.UniqueObservableCollection_InvalidAttemptToInsertADuplicateItem); + } + else + { + base.SetItem(index, item); + } + } + + /// + /// Clears all items in the collection by removing them individually. + /// + protected override void ClearItems() + { + IList items = new List(this); + foreach (T item in items) + { + Remove(item); + } + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Unit.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Unit.cs new file mode 100644 index 00000000..75fa20af --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/Unit.cs @@ -0,0 +1,23 @@ +// (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. + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// Units of measure. + /// + public enum Unit + { + /// + /// The corresponding value is in pixels. + /// + Pixels, + + /// + /// The corresponding value is in degrees. + /// + Degrees, + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/UnitValue.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/UnitValue.cs new file mode 100644 index 00000000..7ac4cde8 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/UnitValue.cs @@ -0,0 +1,144 @@ +// (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; + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// A value in units. + /// + public struct UnitValue : IComparable + { + /// + /// Returns a UnitValue representing an invalid value. + /// + /// UnitValue instance. + public static UnitValue NaN() + { + return new UnitValue { Value = double.NaN }; + } + + /// + /// Instantiates a new instance of the UnitValue struct. + /// + /// The value associated with the units. + /// The units associated with the value. + public UnitValue(double value, Unit unit) : this() + { + Value = value; + Unit = unit; + } + + /// + /// Gets the value associated with the units. + /// + public double Value { get; private set; } + + /// + /// Gets the units associated with the value. + /// + public Unit Unit { get; private set; } + + /// + /// Compares two unit values to determine if they are equal or not. + /// + /// The object being compared. + /// A number smaller than zero if the obj is larger than this + /// object. A number equal to 0 if they are equal. A number greater + /// than zero if this unit value is greater than obj. + public int CompareTo(object obj) + { + UnitValue unitValue = (UnitValue) obj; + + if (unitValue.Unit != this.Unit) + { + throw new InvalidOperationException("Cannot compare two unit values with different units."); + } + + return this.Value.CompareTo(unitValue.Value); + } + + /// + /// Determines if two values are equal. + /// + /// The other value. + /// A value indicating whether values are equal. + public override bool Equals(object obj) + { + if (!(obj is UnitValue)) + { + return false; + } + UnitValue unitValue = (UnitValue)obj; + + if ((Object.ReferenceEquals(unitValue.Value, this.Value) || Object.Equals(unitValue.Value, this.Value)) && unitValue.Unit == this.Unit) + { + return true; + } + return false; + } + + /// + /// Determines whether two unit value objects are equal. + /// + /// The left unit value. + /// The right unit value. + /// A value indicating whether two unit value objects are + /// equal. + public static bool operator ==(UnitValue left, UnitValue right) + { + return left.Equals(right); + } + + /// + /// Determines whether two unit value objects are not equal. + /// + /// The left unit value. + /// The right unit value. + /// A value indicating whether two unit value objects are not + /// equal. + public static bool operator !=(UnitValue left, UnitValue right) + { + return !left.Equals(right); + } + + /// + /// Determines whether the left value is smaller than the right. + /// + /// The left unit value. + /// The right unit value. + /// A value indicating whether the left value is smaller than + /// the right. + public static bool operator <(UnitValue left, UnitValue right) + { + return left.CompareTo(right) < 0; + } + + /// + /// Determines whether the left value is larger than the right. + /// + /// The left unit value. + /// The right unit value. + /// A value indicating whether the left value is larger than + /// the right. + public static bool operator >(UnitValue left, UnitValue right) + { + return left.CompareTo(right) > 0; + } + + /// + /// Returns the hash code of the unit value object. + /// + /// The hash code. + public override int GetHashCode() + { + unchecked + { + return this.Value.GetHashCode() + (int)this.Unit; + } + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/ValueHelper.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/ValueHelper.cs new file mode 100644 index 00000000..a2acf7ff --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/ValueHelper.cs @@ -0,0 +1,426 @@ +// (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; +using System.Globalization; +using System.Collections.Generic; +using System.Linq; +using System.Diagnostics; +using System.Windows; + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// A set of functions for data conversion operations. + /// + internal static class ValueHelper + { + /// + /// The value of a single radian. + /// + public const double Radian = Math.PI / 180.0; + + /// + /// Returns a value indicating whether this value can be graphed on a + /// linear axis. + /// + /// The value to evaluate. + /// A value indicating whether this value can be graphed on a + /// linear axis. + public static bool CanGraph(double value) + { + return !double.IsNaN(value) && !double.IsNegativeInfinity(value) && !double.IsPositiveInfinity(value) && !double.IsInfinity(value); + } + + /// + /// Attempts to convert an object into a double. + /// + /// The value to convert. + /// The double value. + /// A value indicating whether the value can be converted to a + /// double. + public static bool TryConvert(object value, out double doubleValue) + { + doubleValue = default(double); + try + { + if (value != null && + (value is double + || value is int + || value is byte + || value is short + || value is decimal + || value is float + || value is long + || value is uint + || value is sbyte + || value is ushort + || value is ulong)) + { + doubleValue = ValueHelper.ToDouble(value); + return true; + } + } + catch (FormatException) + { + } + catch (InvalidCastException) + { + } + return false; + } + + /// + /// Attempts to convert an object into a date time. + /// + /// The value to convert. + /// The double value. + /// A value indicating whether the value can be converted to a + /// date time. + public static bool TryConvert(object value, out DateTime dateTimeValue) + { + dateTimeValue = default(DateTime); + if (value != null && value is DateTime) + { + dateTimeValue = (DateTime)value; + return true; + } + + return false; + } + + /////// + /////// Converts a value in an IComparable. + /////// + /////// The value to convert. + /////// The converted value. + ////public static IComparable ToComparable(object value) + ////{ + //// double doubleValue; + //// DateTime dateTimeValue; + //// if (TryConvert(value, out doubleValue)) + //// { + //// return doubleValue; + //// } + //// else if (TryConvert(value, out dateTimeValue)) + //// { + //// return dateTimeValue; + //// } + //// IComparable comparable = value as IComparable; + //// return (comparable != null); + ////} + + /// + /// Converts an object into a double. + /// + /// The value to convert to a double. + /// The converted double value. + public static double ToDouble(object value) + { + return Convert.ToDouble(value, CultureInfo.InvariantCulture); + } + + /// + /// Converts a value to a date. + /// + /// The value to convert to a date. + /// The converted date value. + public static DateTime ToDateTime(object value) + { + return Convert.ToDateTime(value, CultureInfo.InvariantCulture); + } + + /// + /// Returns a sequence of date time values from a start and end date + /// time inclusive. + /// + /// The start date time. + /// The end date time. + /// The number of values to return. + /// A sequence of date time values. + public static IEnumerable GetDateTimesBetweenInclusive(DateTime start, DateTime end, long count) + { + Debug.Assert(count >= 2L, "Count must be at least 2."); + + return GetIntervalsInclusive(start.Ticks, end.Ticks, count).Select(value => new DateTime(value)); + } + + /// + /// Returns a sequence of time span values within a time span inclusive. + /// + /// The time span to split. + /// The number of time spans to return. + /// A sequence of time spans. + public static IEnumerable GetTimeSpanIntervalsInclusive(TimeSpan timeSpan, long count) + { + Debug.Assert(count >= 2L, "Count must be at least 2."); + + long distance = timeSpan.Ticks; + + return GetIntervalsInclusive(0, distance, count).Select(value => new TimeSpan(value)); + } + + /// + /// Returns that intervals between a start and end value, including those + /// start and end values. + /// + /// The start value. + /// The end value. + /// The total number of intervals. + /// A sequence of intervals. + public static IEnumerable GetIntervalsInclusive(long start, long end, long count) + { + Debug.Assert(count >= 2L, "Count must be at least 2."); + + long interval = end - start; + for (long index = 0; index < count; index++) + { + double ratio = (double)index / (double)(count - 1); + long value = (long)((ratio * interval) + start); + yield return value; + } + } + + /// + /// Removes the noise from double math. + /// + /// The value. + /// A double without a noise. + internal static double RemoveNoiseFromDoubleMath(double value) + { + if (value == 0.0 || Math.Abs((Math.Log10(Math.Abs(value)))) < 27) + { + return (double)((decimal)value); + } + return Double.Parse(value.ToString(CultureInfo.InvariantCulture), CultureInfo.InvariantCulture); + } + + /// + /// Converts a range into a double range. + /// + /// The range to convert. + /// A range with its members converted to doubles. + public static Range ToDoubleRange(this Range range) + { + if (!range.HasData) + { + return new Range(); + } + else + { + return new Range((double)range.Minimum, (double)range.Maximum); + } + } + + /// + /// Converts a range into a date time range. + /// + /// The range to convert. + /// A range with its members converted to date times. + /// + public static Range ToDateTimeRange(this Range range) + { + if (!range.HasData) + { + return new Range(); + } + else + { + return new Range((DateTime)range.Minimum, (DateTime)range.Maximum); + } + } + + /////// + /////// Returns the point given an angle and a distanceFromOrigin. + /////// + /////// The angle of orientation. + /////// The radius. + /////// The point calculated from the angle and radius. + ////public static Point GetPoint(double angle, double distanceFromOrigin) + ////{ + //// return new Point(Math.Cos(angle * Radian) * distanceFromOrigin, Math.Sin(angle * Radian) * distanceFromOrigin); + ////} + + /// + /// Compares two IComparables returning -1 if the left is null and 1 if + /// the right is null. + /// + /// The left comparable. + /// The right comparable. + /// A value indicating which is larger. + public static int Compare(IComparable left, IComparable right) + { + if (left == null && right == null) + { + return 0; + } + else if (left == null && right != null) + { + return -1; + } + else if (left != null && right == null) + { + return 1; + } + else + { + return left.CompareTo(right); + } + } + + /// + /// Applies the translate transform to a point. + /// + /// The origin point. + /// The offset point. + /// The translated point. + public static Point Translate(this Point origin, Point offset) + { + return new Point(origin.X + offset.X, origin.Y + offset.Y); + } + + /// + /// Converts any range to a range of IComparable. + /// + /// The range to be converted. + /// The new range type. + public static Range ToComparableRange(this Range range) + { + if (range.HasData) + { + return new Range(range.Minimum, range.Maximum); + } + else + { + return new Range(); + } + } + + /// + /// Returns the left value of the rectangle. + /// + /// The rectangle. + /// The default value. + /// The left value of the rectangle. + public static double LeftOrDefault(this Rect rectangle, double value) + { + return rectangle.IsEmpty ? value : rectangle.Left; + } + + /// + /// Returns the right value of the rectangle. + /// + /// The rectangle. + /// The default value. + /// The right value of the rectangle. + public static double RightOrDefault(this Rect rectangle, double value) + { + return rectangle.IsEmpty ? value : rectangle.Right; + } + + /// + /// Returns the width value of the rectangle. + /// + /// The rectangle. + /// The default value. + /// The width value of the rectangle. + public static double WidthOrDefault(this Rect rectangle, double value) + { + return rectangle.IsEmpty ? value : rectangle.Width; + } + + /// + /// Returns the height value of the rectangle. + /// + /// The rectangle. + /// The default value. + /// The height value of the rectangle. + public static double HeightOrDefault(this Rect rectangle, double value) + { + return rectangle.IsEmpty ? value : rectangle.Height; + } + + /// + /// Returns the bottom value of the rectangle. + /// + /// The rectangle. + /// The default value. + /// The bottom value of the rectangle. + public static double BottomOrDefault(this Rect rectangle, double value) + { + return rectangle.IsEmpty ? value : rectangle.Bottom; + } + + /// + /// Returns the top value of the rectangle. + /// + /// The rectangle. + /// The default value. + /// The top value of the rectangle. + public static double TopOrDefault(this Rect rectangle, double value) + { + return rectangle.IsEmpty ? value : rectangle.Top; + } + + /// + /// Converts any range to a range of IComparable. + /// + /// The range to be converted. + /// The new range type. + public static Range ToComparableRange(this Range range) + { + if (range.HasData) + { + return new Range(range.Minimum, range.Maximum); + } + else + { + return new Range(); + } + } + + /// + /// Returns the time span of a date range. + /// + /// The range of values. + /// The length of the range. + public static TimeSpan? GetLength(this Range range) + { + return range.HasData ? range.Maximum - range.Minimum : new TimeSpan?(); + } + + /// + /// Returns the time span of a date range. + /// + /// The range of values. + /// The length of the range. + public static double? GetLength(this Range range) + { + return range.HasData ? range.Maximum - range.Minimum : new double?(); + } + + /// + /// Returns a value indicating whether a rectangle is empty or has + /// no width or height. + /// + /// The rectangle. + /// A value indicating whether a rectangle is empty or has + /// no width or height. + public static bool IsEmptyOrHasNoSize(this Rect rect) + { + return rect.IsEmpty || (rect.Width == 0 && rect.Height == 0); + } + + /// + /// Sets the style property of an element. + /// + /// The element. + /// The style. + public static void SetStyle(this FrameworkElement element, Style style) + { + element.Style = style; + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/WPFToolkit.Extended.DataVisualization.csproj b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/WPFToolkit.Extended.DataVisualization.csproj new file mode 100644 index 00000000..c3bf2828 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/WPFToolkit.Extended.DataVisualization.csproj @@ -0,0 +1,224 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {C921A4BC-1781-4A14-B1D0-C41E49C7606A} + library + Properties + System.Windows.Controls.DataVisualization + WPFToolkit.Extended.DataVisualization + v4.0 + Client + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + SAK + SAK + SAK + SAK + + + true + full + false + bin\Debug\ + TRACE;DEBUG;NO_VALIDATESONNOTIFYDATAERRORS + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/WPFToolkit.Extended.DataVisualization.csproj.vspscc b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/WPFToolkit.Extended.DataVisualization.csproj.vspscc new file mode 100644 index 00000000..feffdeca --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/WPFToolkit.Extended.DataVisualization.csproj.vspscc @@ -0,0 +1,10 @@ +"" +{ +"FILE_VERSION" = "9237" +"ENLISTMENT_CHOICE" = "NEVER" +"PROJECT_FILE_RELATIVE_PATH" = "" +"NUMBER_OF_EXCLUDED_FILES" = "0" +"ORIGINAL_PROJECT_FILE_PATH" = "" +"NUMBER_OF_NESTED_PROJECTS" = "0" +"SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER" +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/WeakEventListener.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/WeakEventListener.cs new file mode 100644 index 00000000..548b4ec4 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/WeakEventListener.cs @@ -0,0 +1,83 @@ +// (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.Diagnostics.CodeAnalysis; + +namespace System.Windows.Controls +{ + /// + /// Implements a weak event listener that allows the owner to be garbage + /// collected if its only remaining link is an event handler. + /// + /// Type of instance listening for the event. + /// Type of source for the event. + /// Type of event arguments for the event. + [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Used as link target in several projects.")] + internal class WeakEventListener where TInstance : class + { + /// + /// WeakReference to the instance listening for the event. + /// + private WeakReference _weakInstance; + + /// + /// Gets or sets the method to call when the event fires. + /// + public Action OnEventAction { get; set; } + + /// + /// Gets or sets the method to call when detaching from the event. + /// + public Action> OnDetachAction { get; set; } + + /// + /// Initializes a new instances of the WeakEventListener class. + /// + /// Instance subscribing to the event. + public WeakEventListener(TInstance instance) + { + if (null == instance) + { + throw new ArgumentNullException("instance"); + } + _weakInstance = new WeakReference(instance); + } + + /// + /// Handler for the subscribed event calls OnEventAction to handle it. + /// + /// Event source. + /// Event arguments. + public void OnEvent(TSource source, TEventArgs eventArgs) + { + TInstance target = (TInstance)_weakInstance.Target; + if (null != target) + { + // Call registered action + if (null != OnEventAction) + { + OnEventAction(target, source, eventArgs); + } + } + else + { + // Detach from event + Detach(); + } + } + + /// + /// Detaches from the subscribed event. + /// + public void Detach() + { + if (null != OnDetachAction) + { + OnDetachAction(this); + OnDetachAction = null; + } + } + } +} \ No newline at end of file diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/WeakReferenceBag.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/WeakReferenceBag.cs new file mode 100644 index 00000000..e9462ea1 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended.DataVisualization/WeakReferenceBag.cs @@ -0,0 +1,94 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System.Collections.Generic; +using System.Diagnostics; + +namespace System.Windows.Controls.DataVisualization +{ + /// + /// A bag of weak references to items. + /// + /// The type of the item. + internal class WeakReferenceBag : IEnumerable + where T : class + { + /// + /// Gets or sets the list of event listeners. + /// + private IList Items { get; set; } + + /// + /// Initializes a new instance of the WeakEvent class. + /// + public WeakReferenceBag() + { + this.Items = new List(); + } + + /// + /// Adds an item to the bag. + /// + /// The item to add to the bag. + public void Add(T item) + { + Debug.Assert(item != null, "listener must not be null."); + this.Items.Add(new WeakReference(item)); + } + + /// + /// Removes an item from the bag. + /// + /// The item to remove. + public void Remove(T item) + { + Debug.Assert(item != null, "listener must not be null."); + int count = 0; + while (count < this.Items.Count) + { + object target = this.Items[count].Target; + if (!this.Items[count].IsAlive || object.ReferenceEquals(target, item)) + { + this.Items.RemoveAt(count); + } + else + { + count++; + } + } + } + + /// + /// Returns a sequence of the elements in the bag. + /// + /// A sequence of the elements in the bag. + public IEnumerator GetEnumerator() + { + int count = 0; + while (count < this.Items.Count) + { + object target = this.Items[count].Target; + if (!this.Items[count].IsAlive) + { + this.Items.RemoveAt(count); + } + else + { + yield return (T)target; + count++; + } + } + } + + /// + /// Returns a sequence of the elements in the bag. + /// + /// A sequence of the elements in the bag. + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return ((IEnumerable) this).GetEnumerator(); + } + } +} \ No newline at end of file