Click here to Skip to main content
15,892,674 members
Articles / Desktop Programming / WPF

OpenWPFChart: Assembling Charts from Components. Part II - Controls

Rate me:
Please Sign up or sign in to vote.
4.90/5 (14 votes)
26 Mar 2009CPOL10 min read 70K   2.2K   52  
Chart controls composed from Chart Parts
// <copyright file="CurveChart.cs" company="Oleg V. Polikarpotchkin">
// Copyright © 2008 Oleg V. Polikarpotchkin. All Right Reserved
// </copyright>
// <author>Oleg V. Polikarpotchkin</author>
// <email>ov-p@yandex.ru</email>
// <date>2008-12-23</date>
// <summary>OpenWPFChart library. CurveChart control.
// Default CurveChart style contains two coordinate grids and two axes.
// </summary>
// <revision>$Id: CurveChart.cs 18093 2009-03-16 04:15:06Z unknown $</revision>

using System;
using System.Collections.Specialized; // For NotifyCollectionChangedEventArgs
using System.ComponentModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using OpenWPFChart.Parts;

namespace OpenWPFChart.Controls
{
	/// <summary>
	/// CurveChart control.
	/// </summary>
	/// <remarks>
	/// Default CurveChart style contains two coordinate grids and two axes.
	/// </remarks>
	[TemplatePart(Name = "PART_ItemsHost", Type = typeof(FrameworkElement))]
	[TemplatePart(Name = "PART_HorizontalAxisHost", Type = typeof(DependencyObject))]
	[TemplatePart(Name = "PART_VerticalAxisHost", Type = typeof(DependencyObject))]
	public class CurveChart : ListBox
	{
		/// <summary>
		/// Initializes the <see cref="CurveChart"/> class.
		/// </summary>
		static CurveChart()
		{
			DefaultStyleKeyProperty.OverrideMetadata(typeof(CurveChart)
				, new FrameworkPropertyMetadata(typeof(CurveChart)));
		}

		#region Named Parts fields
		/// <summary>
		/// Items host.
		/// </summary>
		FrameworkElement itemsHost;
		/// <summary>
		/// Horizontal and Vertical Axes hosts.
		/// </summary>
		DependencyObject hAxisHost, vAxisHost;
		#endregion Named Parts fields

		/// <summary>
		/// When overridden in a derived class, is invoked whenever application code or 
		/// internal processes call <see cref="M:System.Windows.FrameworkElement.ApplyTemplate"/>.
		/// </summary>
		public override void OnApplyTemplate()
		{
			base.OnApplyTemplate();

			// Find Items Host.
			itemsHost = Template.FindName("PART_ItemsHost", this) as FrameworkElement;
			// Find Axes Hosts.
			hAxisHost = Template.FindName("PART_HorizontalAxisHost", this) as DependencyObject;
			vAxisHost = Template.FindName("PART_VerticalAxisHost", this) as DependencyObject;
		}

		#region Dependency Properties
		#region HorizontalScale
		/// <summary>
		/// Identifies the <see cref="HorizontalScale"/> dependency property.
		/// </summary>
		public static readonly DependencyProperty HorizontalScaleProperty
			= DependencyProperty.Register("HorizontalScale", typeof(ChartScale), typeof(CurveChart)
				, new FrameworkPropertyMetadata(null
					, FrameworkPropertyMetadataOptions.AffectsMeasure
						| FrameworkPropertyMetadataOptions.AffectsRender
					, HorizontalScalePropertyChanged
					)
				, HorizontalScaleValidate);
		/// <summary>
		/// Gets or sets the <see cref="HorizontalScale"/> property.
		/// </summary>
		/// <value><see cref="OpenWPFChart.Parts.ChartScale"/> value</value>
		public ChartScale HorizontalScale
		{
			get { return (ChartScale)GetValue(HorizontalScaleProperty); }
			set { SetValue(HorizontalScaleProperty, value); }
		}
		/// <summary>
		/// Validates the suggested value.
		/// </summary>
		/// <remarks>
		/// HorizontalScale may be either null or a consistent <see cref="T:OpenWPFChart.ChartScale"/> object.
		/// </remarks>
		/// <param name="value">The value.</param>
		/// <returns></returns>
		static bool HorizontalScaleValidate(object value)
		{
			if (value == null)
				return true;
			ChartScale scale = value as ChartScale;
			Debug.Assert(scale != null, "scale != null");
			return scale.IsConsistent;
		}
		/// <summary>
		/// HorizontalScale property changed event handler.
		/// </summary>
		/// <remarks>
		/// Applies new HorizontalScale to all Chart Items.
		/// </remarks>
		/// <param name="d">The sender.</param>
		/// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> 
		/// instance containing the event data.</param>
		static void HorizontalScalePropertyChanged(DependencyObject d,	DependencyPropertyChangedEventArgs e)
		{
			CurveChart ths = d as CurveChart;
			if (ths == null)
				return;

			// Apply new HorizontalScale to all Items.
			foreach (var obj in ths.Items)
			{
				ItemDataView itemDataView = castToItemDataView(obj);
				if (itemDataView != null)
				{
					try
					{
						itemDataView.HorizontalScale = ths.HorizontalScale;
					}
					catch (ArgumentException)
					{
						itemDataView.HorizontalScale = null;
					}
				}
			}
		}
		#endregion HorizontalScale

		#region VerticalScale
		/// <summary>
		/// Identifies the VerticalScale dependency property.
		/// </summary>
		public static readonly DependencyProperty VerticalScaleProperty
			= DependencyProperty.Register("VerticalScale", typeof(ChartScale), typeof(CurveChart)
				, new FrameworkPropertyMetadata(null
					, FrameworkPropertyMetadataOptions.AffectsMeasure
						| FrameworkPropertyMetadataOptions.AffectsRender
					, VerticalScalePropertyChanged
					)
				, VerticalScaleValidate);
		/// <summary>
		/// Gets or sets the VerticalScale property.
		/// </summary>
		/// <value>Finite positive value</value>
		public ChartScale VerticalScale
		{
			get { return (ChartScale)GetValue(VerticalScaleProperty); }
			set { SetValue(VerticalScaleProperty, value); }
		}
		/// <summary>
		/// Validates the suggested value.
		/// </summary>
		/// <remarks>
		/// VerticalScale may be either null or a consistent <see cref="T:OpenWPFChart.ChartScale"/> object.
		/// </remarks>
		/// <param name="value">The value.</param>
		/// <returns></returns>
		static bool VerticalScaleValidate(object value)
		{
			if (value == null)
				return true;
			ChartScale scale = value as ChartScale;
			Debug.Assert(scale != null, "scale != null");
			return scale.IsConsistent;
		}
		/// <summary>
		/// VerticalScale property changed event handler.
		/// </summary>
		/// <remarks>
		/// Applies new VerticalScale to all Chart Items.
		/// </remarks>
		/// <param name="d">The sender.</param>
		/// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> 
		/// instance containing the event data.</param>
		static void VerticalScalePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			CurveChart ths = d as CurveChart;
			if (ths == null)
				return;

			// Apply new VerticalScale to all Items.
			foreach (var obj in ths.Items)
			{
				ItemDataView itemDataView = castToItemDataView(obj);
				if (itemDataView != null)
				{
					try
					{
						itemDataView.VerticalScale = ths.VerticalScale;
					}
					catch (ArgumentException)
					{
						itemDataView.VerticalScale = null;
					}
				}
			}
		}
		#endregion VerticalScale

		#region Grids
		#region HorizontalGridVisibility
		/// <summary>
		/// Identifies the HorizontalGridVisibility dependency property.
		/// </summary>
		public static readonly DependencyProperty HorizontalGridVisibilityProperty
			= DependencyProperty.Register("HorizontalGridVisibility", typeof(GridVisibility), typeof(CurveChart)
				, new FrameworkPropertyMetadata(GridVisibility.AllTicks
					, FrameworkPropertyMetadataOptions.AffectsRender)
				, GridVisibility_Validate);
		/// <summary>
		/// Gets or sets the HorizontalGridVisibility property.
		/// </summary>
		/// <value><see cref="GridVisibility"/> value</value>
		public GridVisibility HorizontalGridVisibility
		{
			get { return (GridVisibility)GetValue(HorizontalGridVisibilityProperty); }
			set { SetValue(HorizontalGridVisibilityProperty, value); }
		}
		#endregion HorizontalGridVisibility

		#region VerticalGridVisibility
		/// <summary>
		/// Identifies the VerticalGridVisibility dependency property.
		/// </summary>
		public static readonly DependencyProperty VerticalGridVisibilityProperty
			= DependencyProperty.Register("VerticalGridVisibility", typeof(GridVisibility), typeof(CurveChart)
				, new FrameworkPropertyMetadata(GridVisibility.AllTicks
					, FrameworkPropertyMetadataOptions.AffectsRender)
				, GridVisibility_Validate);
		/// <summary>
		/// Gets or sets the VerticalGridVisibility property.
		/// </summary>
		/// <value><see cref="GridVisibility"/> value</value>
		public GridVisibility VerticalGridVisibility
		{
			get { return (GridVisibility)GetValue(VerticalGridVisibilityProperty); }
			set { SetValue(VerticalGridVisibilityProperty, value); }
		}
		#endregion VerticalGridVisibility

		/// <summary>
		/// Validates suggested GridVisibility property value.
		/// </summary>
		/// <param name="value"></param>
		/// <returns></returns>
		private static bool GridVisibility_Validate(object value)
		{
			GridVisibility x = (GridVisibility)value;
			return (x == GridVisibility.AllTicks || x == GridVisibility.LongTicks || x == GridVisibility.Hidden);
		}
		#endregion Grids

		#region Axes
		#region HorizontalAxisTitle
		/// <summary>
		/// Identifies the HorizontalAxisTitle dependency property.
		/// </summary>
		public static readonly DependencyProperty HorizontalAxisTitleProperty
			= DependencyProperty.Register("HorizontalAxisTitle", typeof(string), typeof(CurveChart)
				, new FrameworkPropertyMetadata(null
						, FrameworkPropertyMetadataOptions.AffectsMeasure
							| FrameworkPropertyMetadataOptions.AffectsRender
					)
				);
		/// <summary>
		/// Gets or sets the HorizontalAxisTitle property.
		/// </summary>
		/// <value>Horizontal Axis Title</value>
		public string HorizontalAxisTitle
		{
			get { return (string)GetValue(HorizontalAxisTitleProperty); }
			set { SetValue(HorizontalAxisTitleProperty, value); }
		}
		#endregion HorizontalAxisTitle

		#region HorizontalAxisLabelFormat
		/// <summary>
		/// Identifies the HorizontalAxisLabelFormat dependency property.
		/// </summary>
		public static readonly DependencyProperty HorizontalAxisLabelFormatProperty
			= DependencyProperty.Register("HorizontalAxisLabelFormat", typeof(string), typeof(CurveChart)
				, new FrameworkPropertyMetadata("G"
						, FrameworkPropertyMetadataOptions.AffectsMeasure
							| FrameworkPropertyMetadataOptions.AffectsRender
					, HorizontalAxisLabelFormatPropertyChanged)
				);
		/// <summary>
		/// Gets or sets the HorizontalAxisLabelFormat property.
		/// </summary>
		/// <value>HorizontalAxisLabelFormatProperty</value>
		public string HorizontalAxisLabelFormat
		{
			get { return (string)GetValue(HorizontalAxisLabelFormatProperty); }
			set { SetValue(HorizontalAxisLabelFormatProperty, value); }
		}
		/// <summary>
		/// HorizontalAxisLabelFormat property changed event handler.
		/// </summary>
		/// <remarks>
		/// Applies new LabelFormat to the HorizontalAxis.
		/// </remarks>
		/// <param name="d">The sender.</param>
		/// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> 
		/// instance containing the event data.</param>
		static void HorizontalAxisLabelFormatPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			CurveChart ths = d as CurveChart;
			if (ths == null)
				return;

			// Apply new LabelFormat to the HorizontalAxis.
			if (ths.hAxisHost == null)
			{
				Axis axis = findAxis(ths.hAxisHost);
				if (axis != null)
					axis.LabelFormat = e.NewValue as string;
			}
		}
		#endregion HorizontalAxisLabelFormat

		#region VerticalAxisTitle
		/// <summary>
		/// Identifies the VerticalAxisTitle dependency property.
		/// </summary>
		public static readonly DependencyProperty VerticalAxisTitleProperty
			= DependencyProperty.Register("VerticalAxisTitle", typeof(string), typeof(CurveChart)
				, new FrameworkPropertyMetadata(null
						, FrameworkPropertyMetadataOptions.AffectsMeasure
							| FrameworkPropertyMetadataOptions.AffectsRender
							, null, CoerceVerticalAxisTitle
					)
				);
		/// <summary>
		/// Gets or sets the VerticalAxisTitle property.
		/// </summary>
		/// <value>Vertical Axis Title</value>
		public string VerticalAxisTitle
		{
			get { return (string)GetValue(VerticalAxisTitleProperty); }
			set { SetValue(VerticalAxisTitleProperty, value); }
		}
		/// <summary>
		/// Coerces the VerticalAxisTitle property value.
		/// Returns either the current selected item name or local VerticalAxisTitle property value.
		/// </summary>
		/// <param name="d">The sender.</param>
		/// <param name="baseValue">The base value.</param>
		/// <returns></returns>
		private static object CoerceVerticalAxisTitle(DependencyObject d, object baseValue)
		{
			CurveChart ths = d as CurveChart;
			if (ths == null)
				return DependencyProperty.UnsetValue;

			ItemDataView itemDataView = ths.SelectedItem as ItemDataView;
			if (itemDataView == null)
			{
				Item item = ths.SelectedItem as Item;
				if (item != null)
					itemDataView = item.ItemDataView;
			}

			if (itemDataView != null)
				return itemDataView.ItemData.ItemName;
			else
			{
				object localValue = ths.ReadLocalValue(VerticalAxisTitleProperty);
				if (localValue != DependencyProperty.UnsetValue)
					return localValue;
				else
					return baseValue;
			}
		}
		#endregion VerticalAxisTitle

		#region VerticalAxisLabelFormat
		/// <summary>
		/// Identifies the VerticalAxisLabelFormat dependency property.
		/// </summary>
		public static readonly DependencyProperty VerticalAxisLabelFormatProperty
			= DependencyProperty.Register("VerticalAxisLabelFormat", typeof(string), typeof(CurveChart)
				, new FrameworkPropertyMetadata("G"
						, FrameworkPropertyMetadataOptions.AffectsMeasure
							| FrameworkPropertyMetadataOptions.AffectsRender
					, VerticalAxisLabelFormatPropertyChanged)
				);
		/// <summary>
		/// Gets or sets the VerticalAxisLabelFormat property.
		/// </summary>
		/// <value>VerticalAxisLabelFormatProperty</value>
		public string VerticalAxisLabelFormat
		{
			get { return (string)GetValue(VerticalAxisLabelFormatProperty); }
			set { SetValue(VerticalAxisLabelFormatProperty, value); }
		}
		/// <summary>
		/// VerticalAxisLabelFormat property changed event handler.
		/// </summary>
		/// <remarks>
		/// Applies new LabelFormat to the VerticalAxis.
		/// </remarks>
		/// <param name="d">The sender.</param>
		/// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> 
		/// instance containing the event data.</param>
		static void VerticalAxisLabelFormatPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			CurveChart ths = d as CurveChart;
			if (ths == null)
				return;

			// Apply new LabelFormat to the VerticalAxis.
			if (ths.vAxisHost == null)
			{
				Axis axis = findAxis(ths.vAxisHost);
				if (axis != null)
					axis.LabelFormat = e.NewValue as string;
			}
		}
		#endregion VerticalAxisLabelFormat
		#endregion Axes
		#endregion Dependency Properties

		#region CLR Properties
		#region Axes
		/// <summary>
		/// Gets the Horizontal axis.
		/// </summary>
		public Axis HorizontalAxis
		{
			get { return hAxisHost == null ? null : findAxis(hAxisHost); }
		}

		/// <summary>
		/// Gets the Vertical axis.
		/// </summary>
		public Axis VerticalAxis
		{
			get { return vAxisHost == null ? null : findAxis(vAxisHost); }
		}
		#endregion Axes

		/// <summary>
		/// Gets the actual render size of the Chart Area.
		/// </summary>
		/// <remarks>
		/// This is the RenderSize of the control PART_ItemsHost part.
		/// </remarks>
		public Size ChartAreaRenderSize
		{
			get { return itemsHost == null ? Size.Empty : itemsHost.RenderSize; }
		}
		#endregion CLR Properties

		/// <summary>
		/// Invoked when the <see cref="P:System.Windows.Controls.ItemsControl.Items"/> property changes.
		/// </summary>
		/// <param name="e">The event data.</param>
		/// <remarks>
		/// Adds/removes <see cref="T:OpenWPFChart.Parts.ItemDataView"/> PropertyChanged event handlers.
		/// </remarks>
		protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
		{
			base.OnItemsChanged(e);

			switch (e.Action)
			{
				case NotifyCollectionChangedAction.Add:
					foreach (object obj in e.NewItems)
					{
						ItemDataView itemDataView = castToItemDataView(obj);
						if (itemDataView != null)
						{
							setCommonChartScales(itemDataView);

							itemDataView.PropertyChanged += item_PropertyChanged;
						}
					}
					break;
				case NotifyCollectionChangedAction.Remove:
					foreach (object obj in e.OldItems)
					{
						ItemDataView itemDataView = castToItemDataView(obj);
						if (itemDataView != null)
							itemDataView.PropertyChanged -= item_PropertyChanged;
					}
					break;
				case NotifyCollectionChangedAction.Replace:
					foreach (object obj in e.OldItems)
					{
						ItemDataView itemDataView = castToItemDataView(obj);
						if (itemDataView != null)
							itemDataView.PropertyChanged -= item_PropertyChanged;
					}
					foreach (object obj in e.NewItems)
					{
						ItemDataView itemDataView = castToItemDataView(obj);
						if (itemDataView != null)
						{
							setCommonChartScales(itemDataView);

							itemDataView.PropertyChanged += item_PropertyChanged;
						}
					}
					break;
				case NotifyCollectionChangedAction.Reset:
					foreach (object obj in Items)
					{
						ItemDataView itemDataView = castToItemDataView(obj);
						if (itemDataView != null)
						{
							setCommonChartScales(itemDataView);

							itemDataView.PropertyChanged += item_PropertyChanged;
						}
					}
					break;
			}

			if (Items.Count == 0)
			{
				HorizontalScale = null;
				VerticalScale = null;
			}
		}

		/// <summary>
		/// Sets Common ChartScales to the Chart control and its Items.
		/// </summary>
		/// <remarks>
		/// When the control Items count is or becomes equal to zero the control scales are set 
		/// to <c>null</c> by the <see cref="M:OpenWPFChart.Chart.OnItemsChanged"/> method override.
		/// <para>If the control scale has null value it's to the corresponding 
		/// <paramref name="itemDataView"/> scale value.</para>
		/// <para>If the control scale value isn't null then the corresponding 
		/// <paramref name="itemDataView"/> scale is set to that value.</para>
		/// </remarks>
		/// <param name="itemDataView">The item data view.</param>
		void setCommonChartScales(ItemDataView itemDataView)
		{
			Debug.Assert(itemDataView != null, "itemDataView != null");
			
			if (HorizontalScale == null)
				HorizontalScale = itemDataView.HorizontalScale;
			else
				itemDataView.HorizontalScale = HorizontalScale;

			if (VerticalScale == null)
				VerticalScale = itemDataView.VerticalScale;
			else
				itemDataView.VerticalScale = VerticalScale;
		}

		/// <summary>
		/// Casts the <paramref name="obj"/> to <see cref="T:OpenWPFChart.ItemDataView"/>.
		/// </summary>
		/// <param name="obj">The object to cast.</param>
		/// <returns>ItemDataView object or null.</returns>
		static ItemDataView castToItemDataView(object obj)
		{
			ItemDataView itemDataView = obj as ItemDataView;
			if (itemDataView == null)
			{
				Item item = obj as Item;
				if (item != null)
					itemDataView = item.ItemDataView;
			}
			return itemDataView;
		}

		/// <summary>
		/// Finds an axis in its container Visual Tree.
		/// </summary>
		/// <param name="host">Axis host.</param>
		/// <returns>The axis object or null</returns>
		static Axis findAxis(DependencyObject host)
		{
			Axis axis = host as Axis;
			if (axis != null)
				return axis;
			for (int i = 0; i < VisualTreeHelper.GetChildrenCount(host); ++i)
			{
				DependencyObject item = VisualTreeHelper.GetChild(host, i);
				axis = findAxis(item);
				if (axis != null)
					return axis;
			}
			return null;
		}

		/// <summary>
		/// Handles the PropertyChanged event of the item control.
		/// </summary>
		/// <remarks>
		/// This handler is responsible for coordination this control
		/// <see cref="P:OpenWPFChart.Chart.HorizontalScale"/> and <see cref="P:OpenWPFChart.Chart.VerticalScale"/>
		/// properties with hosted items <see cref="P:OpenWPFChart.ItemDataView.HorizontalScale"/> and 
		/// <see cref="P:OpenWPFChart.ItemDataView.VerticalScale"/> properties.
		/// <para>For this control ChartScale's of the control and ChartScale's of the hosted items must
		/// be the same. So if the ChartScale of the hosted item is changed by third-partie code
		/// this change should be discarded.</para>
		/// <para>Also this handler resets the ItemTemplateSelector when 
		/// <see cref="P:OpenWPFChart.ItemDataView.VisualCue"/> property changes.</para>
		/// </remarks>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> instance containing the event data.</param>
		private void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			ItemDataView itemDataView = castToItemDataView(sender);
			if (itemDataView == null)
				return;

			if (e.PropertyName.StartsWith("HorizontalScale"))
			{
				Debug.Assert(HorizontalScale != null, "HorizontalScale != null");
				if (!object.ReferenceEquals(itemDataView.HorizontalScale, HorizontalScale))
					itemDataView.HorizontalScale = HorizontalScale;
			}
			else if (e.PropertyName.StartsWith("VerticalScale"))
			{
				Debug.Assert(VerticalScale != null, "VerticalScale != null");
				if (!object.ReferenceEquals(itemDataView.VerticalScale, VerticalScale))
					itemDataView.VerticalScale = VerticalScale;
			}

			if (e.PropertyName == "VisualCue")
			{ // Reset the ItemTemplateSelector.
				DataTemplateSelector old = ItemTemplateSelector;
				ItemTemplateSelector = null;
				ItemTemplateSelector = old;
			}
		}

		/// <summary>
		/// Responds to a list box selection change by raising a 
		/// <see cref="E:System.Windows.Controls.Primitives.Selector.SelectionChanged"/> event.
		/// </summary>
		/// <remarks>
		/// Coerces VerticalAxisTitleProperty to force it show the current item Name.
		/// </remarks>
		/// <param name="e">Provides data for <see cref="T:System.Windows.Controls.SelectionChangedEventArgs"/>.</param>
		protected override void OnSelectionChanged(SelectionChangedEventArgs e)
		{
			base.OnSelectionChanged(e);
			CoerceValue(VerticalAxisTitleProperty);
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Team Leader
Russian Federation Russian Federation
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions