Click here to Skip to main content
15,881,559 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 69.6K   2.2K   52  
Chart controls composed from Chart Parts
// Copyright © Oleg V. Polikarpotchkin 2008
// <copyright file="MainWindow.xaml.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.
// Temperature Chart Sample. Chart is composed in MainWindow.
// It has three axis - one X-axis for dates and two Y-axes for temperature in Celsius and Fahrenheit.
// </summary>
// <revision>$Id: MainWindow.xaml.cs 19488 2009-03-23 08:40:46Z unknown $</revision>

using System;
using System.Collections; // for ArrayList
using System.Collections.Generic;
using System.Collections.ObjectModel; // For ObservableCollection
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using Microsoft.Win32; // For OpenFileDialog and SaveFileDialog
using OpenWPFChart.Helpers;
using OpenWPFChart.Parts;

namespace TemperatureSample
{
	/// <summary>
	/// OpenWPFChart library Temperature Chart Sample main Window.
	/// </summary>
	/// <remarks>
	/// Chart is composed in MainWindow. It has three axis - one X-axis for dates and 
	/// two Y-axes for temperature in Celsius and Fahrenheit.
	/// </remarks>
	public partial class MainWindow : Window
	{
		/// <summary>
		/// Initializes a new instance of the <see cref="MainWindow"/> class.
		/// </summary>
		public MainWindow()
		{
			InitializeComponent();
		}

		/// <summary>
		/// Raises the <see cref="E:System.Windows.FrameworkElement.Initialized"/> event. This method is invoked whenever <see cref="P:System.Windows.FrameworkElement.IsInitialized"/> is set to true internally.
		/// </summary>
		/// <param name="e">The <see cref="T:System.Windows.RoutedEventArgs"/> that contains the event data.</param>
		protected override void OnInitialized(EventArgs e)
		{
			base.OnInitialized(e);

			// Load MRU files list
			ArrayList mru = Properties.Settings.Default.MRU;
			if (mru != null)
			{
				foreach (string fileName in mru)
				{
					MenuItem item = new MenuItem() { Header = fileName };
					item.Click += mru_Click;
					mnuRecentFiles.Items.Add(item);
				}
			}
		}

		/// <summary>
		/// Saves the settings.
		/// Raises the <see cref="E:System.Windows.Window.Closed"/> event.
		/// </summary>
		/// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.</param>
		protected override void OnClosed(EventArgs e)
		{
			base.OnClosed(e);

			// MRU files list.
			ArrayList mru = null;
			if (mnuRecentFiles.Items.Count > 0)
			{
				mru = new ArrayList();
				foreach (MenuItem item in mnuRecentFiles.Items)
				{
					mru.Add(item.Header as string);
				}
			}
			Properties.Settings.Default.MRU = mru;

			Properties.Settings.Default.Save();
		}

		#region Dependency Properties
		#region HorizontalScale
		/// <summary>
		/// Identifies the HorizontalScale dependency property.
		/// </summary>
		public static readonly DependencyProperty HorizontalScaleProperty
			= DependencyProperty.Register("HorizontalScale", typeof(ChartScale), typeof(MainWindow)
				, new FrameworkPropertyMetadata(null
					, FrameworkPropertyMetadataOptions.AffectsMeasure
						| FrameworkPropertyMetadataOptions.AffectsRender
					)
				);
		/// <summary>
		/// Gets or sets the HorizontalScale property.
		/// </summary>
		/// <value/>
		public ChartScale HorizontalScale
		{
			get { return (ChartScale)GetValue(HorizontalScaleProperty); }
			set { SetValue(HorizontalScaleProperty, value); }
		}
		#endregion HorizontalScale

		#region VerticalScale
		/// <summary>
		/// Identifies the VerticalScale dependency property.
		/// </summary>
		public static readonly DependencyProperty VerticalScaleProperty
			= DependencyProperty.Register("VerticalScale", typeof(ChartScale), typeof(MainWindow)
				, new FrameworkPropertyMetadata(null
					, FrameworkPropertyMetadataOptions.AffectsMeasure
						| FrameworkPropertyMetadataOptions.AffectsRender
					)
				);
		/// <summary>
		/// Gets or sets the VerticalScale property.
		/// </summary>
		/// <value/>
		public ChartScale VerticalScale
		{
			get { return (ChartScale)GetValue(VerticalScaleProperty); }
			set { SetValue(VerticalScaleProperty, value); }
		}
		#endregion VerticalScale
		#endregion Dependency Properties

		#region Chart Events Handlers
		/// <summary>
		/// Handles the SelectionChanged event of the chart control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.Windows.Controls.SelectionChangedEventArgs"/> instance containing the event data.</param>
		private void chart_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
		{
			Trace.WriteLine(string.Format("***** MainWindow.chart_SelectionChanged Source={0} OriginalSource={0}", e.Source, e.OriginalSource));
		}

		/// <summary>
		/// Sets the VisualCue property on all ItemDataView items of SampledCurveDataView type.
		/// </summary>
		/// <param name="cue">The cue.</param>
		private void setVisualCue(object cue)
		{
			ObservableCollection<ItemDataView> dataViewCollection = DataContext as ObservableCollection<ItemDataView>;
			if (dataViewCollection == null)
				return;
			foreach (ItemDataView item in dataViewCollection)
			{
				SampledCurveDataView sampledCurveDataView = item as SampledCurveDataView;
				if (sampledCurveDataView != null)
					sampledCurveDataView.VisualCue = cue;
			}
		}

		/// <summary>
		/// Handles the MouseEnterItem event of the OpenWPFChart.Item element.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="OpenWPFChart.MouseItemEventArgs"/> instance containing the event data.</param>
		private void item_Enter(object sender, MouseItemEventArgs e)
		{
			Cursor = Cursors.Hand;
		}

		/// <summary>
		/// Handles the MouseLeaveItem event of the OpenWPFChart.Item element.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="OpenWPFChart.MouseItemEventArgs"/> instance containing the event data.</param>
		private void item_Leave(object sender, MouseItemEventArgs e)
		{
			Cursor = null;
		}

		/// <summary>
		/// Handles the VisualCueChanged event of the OpenWPFChart.Item element.
		/// <para>Resets the Chart ItemTemplateSelector.</para>
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
		private void item_VisualCueChanged(object sender, RoutedEventArgs e)
		{
			DataTemplateSelector old = chart.ItemTemplateSelector;
			chart.ItemTemplateSelector = null;
			chart.ItemTemplateSelector = old;
		}
		#endregion Chart Events Handlers

		/// <summary>
		/// Loads CurveData from the file.
		/// </summary>
		/// <param name="fileName">Name of the file.</param>
		/// <exception cref="Exception">Misc IO and file parser exceptions.</exception>
		private bool loadCurveData(string fileName)
		{
			ObservableCollection<ItemData> dataCollection = null;
			FileInfo fi = new FileInfo(fileName);
			try
			{
				if (fi.Extension.ToLower() == ".txt")
					dataCollection = SpaceSeparatedDataFileParser.Parse(fileName);
				else if (fi.Extension.ToLower() == ".xml")
					dataCollection = XMLDataFileParser.Parse(fileName);

				if (dataCollection == null || dataCollection.Count == 0)
				{
					MessageBox.Show("Data wasn't loaded");
					return false;
				}
			}
			catch (Exception ex)
			{
				MessageBox.Show(ex.Message);
				return false;
			}

			// Set Chart Scales
			ChartScale horizontalScale = null, verticalScale = null;
			if (dataCollection[0] is SampledCurveData<double, double>)
			{ // abscissa and ordinate are Numeric.
				SampledCurveData<double, double> xData = dataCollection[0] as SampledCurveData<double, double>;

				// Abscissa Min, Max
				double xMin = xData.Points.First<DataPoint<double, double>>().X;
				double xMax = xData.Points.Last<DataPoint<double, double>>().X;
				// Ordinate Min, Max
				double yMax = double.MinValue, yMin = double.MaxValue;
				for (int i = 0; i < dataCollection.Count; ++i)
				{
					IEnumerable<double> ordinates = from pt in (dataCollection[i] as SampledCurveData<double, double>).Points select pt.Y;
					double min = ordinates.Min();
					if (min < yMin)
						yMin = min;
					double max = ordinates.Max();
					if (max > yMax)
						yMax = max;
				}

				// Attach ChartScales
				// X-scale.
				horizontalScale = new ChartLinearScale(xMin, xMax
					, chart.ActualWidth > 0 ? chart.ActualWidth : ActualWidth);
				// Y-scale.
				verticalScale = new ChartLinearScale(yMax, yMin
					, chart.ActualHeight > 0 ? chart.ActualHeight : ActualHeight);
			}
			else if (dataCollection[0] is SampledCurveData<DateTime, double>)
			{ // abscissa is DateTime and ordinate is Numeric.
				SampledCurveData<DateTime, double> xData = dataCollection[0] as SampledCurveData<DateTime, double>;

				// Abscissa Min, Max
				DateTime xMin = xData.Points.First<DataPoint<DateTime, double>>().X;
				DateTime xMax = xData.Points.Last<DataPoint<DateTime, double>>().X;
				// Ordinate Min, Max
				double yMax = double.MinValue, yMin = double.MaxValue;
				for (int i = 0; i < dataCollection.Count; ++i)
				{
					IEnumerable<double> ordinates = from pt in (dataCollection[i] as SampledCurveData<DateTime, double>).Points select pt.Y;
					double min = ordinates.Min();
					if (min < yMin)
						yMin = min;
					double max = ordinates.Max();
					if (max > yMax)
						yMax = max;
				}

				// Attach ChartScales
				// X-scale.
				horizontalScale = new ChartDateTimeScale(xMin, xMax
					, chart.ActualWidth > 0 ? chart.ActualWidth : ActualWidth);
				// Y-scale.
				verticalScale = new ChartLinearScale(yMax, yMin
					, chart.ActualHeight > 0 ? chart.ActualHeight : ActualHeight);
			}
			else
			{
				MessageBox.Show(string.Format("There is no appropriate data in the {0} file", fileName)
					, "Open Data File");
				return false;
			}

			if (horizontalScale == null || !horizontalScale.IsConsistent)
				throw new ArgumentException(string.Format("Invalid X scale in {0} file", fileName));
			HorizontalScale = horizontalScale;
			if (verticalScale == null || !verticalScale.IsConsistent)
				throw new ArgumentException(string.Format("Invalid Y scale in {0} file", fileName));
			VerticalScale = verticalScale;

			// PointMarker.
			GeometryDrawing pointMarker = new GeometryDrawing(Brushes.Brown, new Pen(Brushes.Red, 1)
				, new EllipseGeometry(new Point(0, 0), 4, 4));
			pointMarker.Freeze();

			// Create DataViewCollection.
			ObservableCollection<ItemDataView> dataViewCollection = new ObservableCollection<ItemDataView>();
			for (int i = 0; i < dataCollection.Count; ++i)
			{
				SampledCurveDataView dataView = new SampledCurveDataView()
				{
					ItemData = dataCollection[i],
					HorizontalScale = this.HorizontalScale,
					VerticalScale = this.VerticalScale,
					PointMarker = pointMarker
				};

				dataViewCollection.Add(dataView);
			}

			DataContext = dataViewCollection;

			return true;
		}

		#region Menu actions handlers
		/// <summary>
		/// Handles the Click event of the mnuViewInterpolator items.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
		private void mnuViewInterpolator_Click(object sender, RoutedEventArgs e)
		{
			MenuItem mnuItem = sender as MenuItem;
			if (mnuItem == null)
				return;
			switch (mnuItem.Header.ToString())
			{
				case "_Polyline":
					setVisualCue(typeof(PolylineSampledCurve));
					break;
				case "_Bezier":
					setVisualCue(typeof(BezierSampledCurve));
					break;
				case "_Spline":
					setVisualCue(typeof(SplineSampledCurve));
					break;
			}
		}

		/// <summary>
		/// Handles the Click event of the MRU menu subitems.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
		private void mru_Click(Object sender, RoutedEventArgs e)
		{
			MenuItem item = sender as MenuItem;
			if (item == null)
				return;
			string fileName = item.Header as string;
			if (!loadCurveData(fileName))
				// Remove from MRU list
				mnuRecentFiles.Items.Remove(item);
		}

		/// <summary>
		/// Handles the Click event of the Exit control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
		private void Exit_Click(object sender, RoutedEventArgs e)
		{
			Close();
		}
		#endregion Menu actions handlers

		#region Command Handlers
		/// <summary>
		/// Execute <see cref="ApplicationCommands.Open"/> command.
		/// </summary>
		/// <param name="sender">The sender.</param>
		/// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
		private void OpenExecutedCommandHandler(object sender, ExecutedRoutedEventArgs e)
		{
			OpenFileDialog dlg = new OpenFileDialog();
			dlg.Filter = "Text Files (*.txt)|*.txt|XML Files (*.xml)|*.xml|All Files (*.*)|*.*";
			if (dlg.ShowDialog() == true)
			{
				if (loadCurveData(dlg.FileName))
				{
					// Add to MRU list
					foreach (MenuItem item in mnuRecentFiles.Items)
					{
						if (dlg.FileName == item.Header as string)
							return;
					}

					MenuItem newItem = new MenuItem() { Header = dlg.FileName };
					newItem.Click += mru_Click;
					mnuRecentFiles.Items.Add(newItem);
				}
			}
		}

		/// <summary>
		/// Executes <see cref="ApplicationCommands.Properties"/> command.
		/// </summary>
		/// <param name="sender">The sender.</param>
		/// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
		void PropertiesExecutedCommandHandler(object sender, ExecutedRoutedEventArgs e)
		{
			ContextMenu mnu = (ContextMenu)FindResource("chartItemCtxMnu");
			Debug.Assert(mnu != null, "mnu != null");
			ListBoxItem listBoxItem = e.OriginalSource as ListBoxItem;
			Debug.Assert(listBoxItem != null, "listBoxItem != null");
			ItemDataView itemDataView = listBoxItem.DataContext as ItemDataView;
			Debug.Assert(itemDataView != null, "itemDataView != null");

			// ContextMenu is assigned in OpenWPFChart.Item object.
			if (mnu.Tag is ItemVisual)
			{ // Show CurveProperties dialog.
				// Get GenericDataTemplateSelectorItems.
				Collection<GenericDataTemplateSelectorItem> templateSelectorItems = null;
				GenericDataTemplateSelector templateSelector = chart.ItemTemplateSelector as GenericDataTemplateSelector;
				if (templateSelector != null)
					templateSelectorItems = templateSelector.SelectorItems;

				// Show the dialog.
				ItemPropertiesDialog dlg = new ItemPropertiesDialog(itemDataView, templateSelectorItems) 
				{ 
					Owner = this,
					AllowSwapVetricalLinLogScale = false,
					AllowSwapHorizontalLinLogScale = false
				};
				if (dlg.ShowDialog() == true)
				{
					// Curve Name
					itemDataView.ItemData.ItemName = dlg.ItemName;

					// Curve Color
					CurveDataView curveDataView = itemDataView as CurveDataView;
					if (curveDataView != null)
					{
						Color color = Colors.Black;
						SolidColorBrush solidColorBrush = curveDataView.Pen.Brush as SolidColorBrush;
						if (solidColorBrush != null)
							color = solidColorBrush.Color;
						if (solidColorBrush == null || dlg.CurveColor != color)
							curveDataView.Pen = new Pen(new SolidColorBrush(dlg.CurveColor), 1);
					}

					// PointMarkerVisibility
					IPointMarker iPointMarker = curveDataView as IPointMarker;
					if (iPointMarker != null)
						iPointMarker.PointMarkerVisible = dlg.PointMarkerVisible;

					// VisualCue
					if (dlg.VisualCue != null)
						itemDataView.VisualCue = dlg.VisualCue;
				}
				e.Handled = true;
			}
			else if (mnu.Tag is ChartPointVisual)
			{ // Show ChartPointProperties dialog.
				IPointMarker iPointMarker = itemDataView as IPointMarker;
				if (iPointMarker != null)
				{
					ChartPointPropertiesDialog dlg = new ChartPointPropertiesDialog(iPointMarker.PointMarker) { Owner = this };
					if (dlg.ShowDialog() == true)
					{
						if (dlg.PointMarker != iPointMarker.PointMarker)
						{
							iPointMarker.PointMarker = dlg.PointMarker;
						}
					}
				}
				e.Handled = true;
			}
		}

		/// <summary>
		/// Execute <see cref="NavigationCommands.Zoom"/> command.
		/// </summary>
		/// <param name="sender">The sender.</param>
		/// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
		private void ZoomExecutedCommandHandler(object sender, ExecutedRoutedEventArgs e)
		{
			double zoom = 1.2;
			if (e.Parameter != null)
			{
				try
				{
					zoom = Convert.ToDouble(e.Parameter);
				}
				catch (Exception ex)
				{
					Trace.WriteLine("ZoomCommandHandler " + ex.Message);
					return;
				}
			}

			if (zoom == 0.0)
				return;
			if (zoom < 0.0)
				zoom = -1.0 / zoom;

			HorizontalScale.Scale *= zoom;
			VerticalScale.Scale *= zoom;
		}
		#endregion Command Handlers
	}
}

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