Click here to Skip to main content
15,889,096 members
Articles / Desktop Programming / WPF

How to create stock charts using the Silverlight Toolkit

Rate me:
Please Sign up or sign in to vote.
4.70/5 (15 votes)
16 Feb 2009CPOL2 min read 142.1K   2.7K   65  
An article on how to create a Candlestick stock chart using the Silverlight Toolkit.
<!--
// (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.
-->
    
<UserControl 
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:controls="clr-namespace:Microsoft.Windows.Controls;assembly=Microsoft.Windows.Controls"
  xmlns:charting="clr-namespace:Microsoft.Windows.Controls.DataVisualization.Charting;assembly=Microsoft.Windows.Controls.DataVisualization"
  xmlns:samples="clr-namespace:Microsoft.Windows.Controls.Samples"
  xmlns:sys="clr-namespace:System;assembly=mscorlib"
  x:Class="Microsoft.Windows.Controls.Samples.CustomSeriesSample">
    <StackPanel>

        <!-- Basic Examples -->
        <ContentControl Content="Basic Examples" Style="{StaticResource Header}"/>
        <controls:WrapPanel>
            <Grid Style="{StaticResource WrapperStyle}">
                <charting:Chart Title="Constant">
                    <charting:Chart.Series>
                        <charting:ScatterSeries
                          Title="Particulate Levels"
                          ItemsSource="{Binding LevelsInRainfall, Source={StaticResource ParticulateLevel}}"
                          IndependentValueBinding="{Binding Rainfall}"
                          DependentValueBinding="{Binding Particulate}" />
                        <samples:FunctionSeries
                          Function="110"
                          Title="f(x) = 110"
                          LineBrush="Blue"
                          LineThickness="2" />
                    </charting:Chart.Series>
                </charting:Chart>
            </Grid>
            
            <Grid Style="{StaticResource WrapperStyle}">
                <charting:Chart Title="Linear">
                    <charting:Chart.Series>
                        <charting:ScatterSeries
                          Title="Particulate Levels"
                          ItemsSource="{Binding LevelsInRainfall, Source={StaticResource ParticulateLevel}}"
                          IndependentValueBinding="{Binding Rainfall}"
                          DependentValueBinding="{Binding Particulate}" />
                        <samples:FunctionSeries
                          Function="110 + x"
                          Title="f(x) = 110 + x"
                          LineBrush="Green"
                          LineThickness="2" />
                    </charting:Chart.Series>
                </charting:Chart>
            </Grid>
            
            <Grid Style="{StaticResource WrapperStyle}">
                <charting:Chart Title="Quadratic">
                    <charting:Chart.Series>
                        <charting:ScatterSeries
                          Title="Particulate Levels"
                          ItemsSource="{Binding LevelsInRainfall, Source={StaticResource ParticulateLevel}}"
                          IndependentValueBinding="{Binding Rainfall}"
                          DependentValueBinding="{Binding Particulate}" />
                        <samples:FunctionSeries
                          Function="110 + (x - 4)^2"
                          LineBrush="Red"
                          LineThickness="1">
                            <samples:FunctionSeries.Title>
                                <StackPanel Orientation="Horizontal">
                                    <TextBlock Grid.Row="1" Text="f(x) = 110 + (x - 4)" Margin="0 4 0 0" />
                                    <TextBlock Grid.Column="1" Text="2" FontSize="8" Margin="0"  />
                                </StackPanel>
                            </samples:FunctionSeries.Title>
                        </samples:FunctionSeries>
                    </charting:Chart.Series>
                </charting:Chart>
            </Grid>
            
            <Grid Style="{StaticResource WrapperStyle}">
                <charting:Chart Title="Cubic">
                    <charting:Chart.Series>
                        <charting:ScatterSeries
                          Title="Particulate Levels"
                          ItemsSource="{Binding LevelsInRainfall, Source={StaticResource ParticulateLevel}}"
                          IndependentValueBinding="{Binding Rainfall}"
                          DependentValueBinding="{Binding Particulate}" />
                        <samples:FunctionSeries
                          Function="110 + (x - 4)^3"
                          LineBrush="Black"
                          LineThickness="3">
                            <samples:FunctionSeries.Title>
                                <StackPanel Orientation="Horizontal">
                                    <TextBlock Grid.Row="1" Text="f(x) = 110 + (x - 4)" Margin="0 4 0 0" />
                                    <TextBlock Grid.Column="1" Text="3" FontSize="8" Margin="0"  />
                                </StackPanel>
                            </samples:FunctionSeries.Title>
                        </samples:FunctionSeries>
                    </charting:Chart.Series>
                </charting:Chart>
            </Grid>
            
            <Grid Style="{StaticResource WrapperStyle}">
                <charting:Chart Title="Inverse">
                    <charting:Chart.Series>
                        <charting:ScatterSeries
                          Title="Particulate Levels"
                          ItemsSource="{Binding LevelsInRainfall, Source={StaticResource ParticulateLevel}}"
                          IndependentValueBinding="{Binding Rainfall}"
                          DependentValueBinding="{Binding Particulate}" />
                        <samples:FunctionSeries
                          Function="110 + 1 / (x - 4)"
                          LineThickness="1">
                            <samples:FunctionSeries.Title>
                                <StackPanel Orientation="Horizontal">
                                    <TextBlock Grid.Row="1" Text="f(x) = 110 + " VerticalAlignment="Center" />
                                    <StackPanel VerticalAlignment="Center">
                                        <TextBlock Text="1" HorizontalAlignment="Center" />
                                        <Line Stroke="Black" StrokeThickness="1" X1="0" X2="30" Y1="0" Y2="0" />
                                        <TextBlock Text="x - 4" HorizontalAlignment="Center" />
                                    </StackPanel>
                                </StackPanel>
                            </samples:FunctionSeries.Title>
                        </samples:FunctionSeries>
                    </charting:Chart.Series>
                </charting:Chart>
            </Grid>
            
            <Grid Style="{StaticResource WrapperStyle}">
                <charting:Chart x:Name="CustomFunctionChart" Title="Custom Function">
                    <charting:Chart.Series>
                        <charting:ScatterSeries
                          Title="Particulate Levels"
                          ItemsSource="{Binding LevelsInRainfall, Source={StaticResource ParticulateLevel}}"
                          IndependentValueBinding="{Binding Rainfall}"
                          DependentValueBinding="{Binding Particulate}" />
                        <samples:FunctionSeries
                          Title="f(x) = 110 + 3 * Math.Sin(x)"
                          LineThickness="1" />
                    </charting:Chart.Series>
                </charting:Chart>
            </Grid>
        </controls:WrapPanel>
        
        <!-- Regression Scenario -->
        <ContentControl Content="Regression Scenario" Style="{StaticResource Header}"/>
        <Grid Height="500">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <TextBlock HorizontalAlignment="Right" Text="Regression: " />
            <ComboBox
              Grid.Column="1"
              SelectionChanged="OnRegressionTypeChanged"
              HorizontalAlignment="Left">
                <ComboBox.ItemsSource>
                    <controls:ObjectCollection>
                        <sys:String>Linear</sys:String>
                        <sys:String>Quadratic</sys:String>
                        <sys:String>Cubic</sys:String>
                        <sys:String>Quartic</sys:String>
                    </controls:ObjectCollection>
                </ComboBox.ItemsSource>
            </ComboBox>
            <charting:Chart x:Name="ParticulateAnalysis" Grid.Row="1" Grid.ColumnSpan="2" Title="Particulate Level Analysis">
                <charting:Chart.Series>
                    <charting:ScatterSeries
                      Title="Particulate Levels"
                      ItemsSource="{Binding LevelsInRainfall, Source={StaticResource ParticulateLevel}}"
                      IndependentValueBinding="{Binding Rainfall}"
                      DependentValueBinding="{Binding Particulate}" />
                    <samples:FunctionSeries
                      Title="Regression"
                      LineBrush="Blue"
                      LineThickness="2" />
                </charting:Chart.Series>
            </charting:Chart>
        </Grid>
        
        <src:SourceViewer xmlns:src="clr-namespace:Microsoft.Windows.Controls.Samples;assembly=Microsoft.Windows.Controls.Samples.Common" xmlns:sys="clr-namespace:System;assembly=mscorlib">
  <src:SourceFile Path="CustomSeriesSample.xaml">
    <src:SourceFile.Source>
      <sys:String>&lt;!--
// (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.
--&gt;
    
&lt;UserControl 
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:controls="clr-namespace:Microsoft.Windows.Controls;assembly=Microsoft.Windows.Controls"
  xmlns:charting="clr-namespace:Microsoft.Windows.Controls.DataVisualization.Charting;assembly=Microsoft.Windows.Controls.DataVisualization"
  xmlns:samples="clr-namespace:Microsoft.Windows.Controls.Samples"
  xmlns:sys="clr-namespace:System;assembly=mscorlib"
  x:Class="Microsoft.Windows.Controls.Samples.CustomSeriesSample"&gt;
    &lt;StackPanel&gt;

        &lt;!-- Basic Examples --&gt;
        &lt;ContentControl Content="Basic Examples" Style="{StaticResource Header}"/&gt;
        &lt;controls:WrapPanel&gt;
            &lt;Grid Style="{StaticResource WrapperStyle}"&gt;
                &lt;charting:Chart Title="Constant"&gt;
                    &lt;charting:Chart.Series&gt;
                        &lt;charting:ScatterSeries
                          Title="Particulate Levels"
                          ItemsSource="{Binding LevelsInRainfall, Source={StaticResource ParticulateLevel}}"
                          IndependentValueBinding="{Binding Rainfall}"
                          DependentValueBinding="{Binding Particulate}" /&gt;
                        &lt;samples:FunctionSeries
                          Function="110"
                          Title="f(x) = 110"
                          LineBrush="Blue"
                          LineThickness="2" /&gt;
                    &lt;/charting:Chart.Series&gt;
                &lt;/charting:Chart&gt;
            &lt;/Grid&gt;
            
            &lt;Grid Style="{StaticResource WrapperStyle}"&gt;
                &lt;charting:Chart Title="Linear"&gt;
                    &lt;charting:Chart.Series&gt;
                        &lt;charting:ScatterSeries
                          Title="Particulate Levels"
                          ItemsSource="{Binding LevelsInRainfall, Source={StaticResource ParticulateLevel}}"
                          IndependentValueBinding="{Binding Rainfall}"
                          DependentValueBinding="{Binding Particulate}" /&gt;
                        &lt;samples:FunctionSeries
                          Function="110 + x"
                          Title="f(x) = 110 + x"
                          LineBrush="Green"
                          LineThickness="2" /&gt;
                    &lt;/charting:Chart.Series&gt;
                &lt;/charting:Chart&gt;
            &lt;/Grid&gt;
            
            &lt;Grid Style="{StaticResource WrapperStyle}"&gt;
                &lt;charting:Chart Title="Quadratic"&gt;
                    &lt;charting:Chart.Series&gt;
                        &lt;charting:ScatterSeries
                          Title="Particulate Levels"
                          ItemsSource="{Binding LevelsInRainfall, Source={StaticResource ParticulateLevel}}"
                          IndependentValueBinding="{Binding Rainfall}"
                          DependentValueBinding="{Binding Particulate}" /&gt;
                        &lt;samples:FunctionSeries
                          Function="110 + (x - 4)^2"
                          LineBrush="Red"
                          LineThickness="1"&gt;
                            &lt;samples:FunctionSeries.Title&gt;
                                &lt;StackPanel Orientation="Horizontal"&gt;
                                    &lt;TextBlock Grid.Row="1" Text="f(x) = 110 + (x - 4)" Margin="0 4 0 0" /&gt;
                                    &lt;TextBlock Grid.Column="1" Text="2" FontSize="8" Margin="0"  /&gt;
                                &lt;/StackPanel&gt;
                            &lt;/samples:FunctionSeries.Title&gt;
                        &lt;/samples:FunctionSeries&gt;
                    &lt;/charting:Chart.Series&gt;
                &lt;/charting:Chart&gt;
            &lt;/Grid&gt;
            
            &lt;Grid Style="{StaticResource WrapperStyle}"&gt;
                &lt;charting:Chart Title="Cubic"&gt;
                    &lt;charting:Chart.Series&gt;
                        &lt;charting:ScatterSeries
                          Title="Particulate Levels"
                          ItemsSource="{Binding LevelsInRainfall, Source={StaticResource ParticulateLevel}}"
                          IndependentValueBinding="{Binding Rainfall}"
                          DependentValueBinding="{Binding Particulate}" /&gt;
                        &lt;samples:FunctionSeries
                          Function="110 + (x - 4)^3"
                          LineBrush="Black"
                          LineThickness="3"&gt;
                            &lt;samples:FunctionSeries.Title&gt;
                                &lt;StackPanel Orientation="Horizontal"&gt;
                                    &lt;TextBlock Grid.Row="1" Text="f(x) = 110 + (x - 4)" Margin="0 4 0 0" /&gt;
                                    &lt;TextBlock Grid.Column="1" Text="3" FontSize="8" Margin="0"  /&gt;
                                &lt;/StackPanel&gt;
                            &lt;/samples:FunctionSeries.Title&gt;
                        &lt;/samples:FunctionSeries&gt;
                    &lt;/charting:Chart.Series&gt;
                &lt;/charting:Chart&gt;
            &lt;/Grid&gt;
            
            &lt;Grid Style="{StaticResource WrapperStyle}"&gt;
                &lt;charting:Chart Title="Inverse"&gt;
                    &lt;charting:Chart.Series&gt;
                        &lt;charting:ScatterSeries
                          Title="Particulate Levels"
                          ItemsSource="{Binding LevelsInRainfall, Source={StaticResource ParticulateLevel}}"
                          IndependentValueBinding="{Binding Rainfall}"
                          DependentValueBinding="{Binding Particulate}" /&gt;
                        &lt;samples:FunctionSeries
                          Function="110 + 1 / (x - 4)"
                          LineThickness="1"&gt;
                            &lt;samples:FunctionSeries.Title&gt;
                                &lt;StackPanel Orientation="Horizontal"&gt;
                                    &lt;TextBlock Grid.Row="1" Text="f(x) = 110 + " VerticalAlignment="Center" /&gt;
                                    &lt;StackPanel VerticalAlignment="Center"&gt;
                                        &lt;TextBlock Text="1" HorizontalAlignment="Center" /&gt;
                                        &lt;Line Stroke="Black" StrokeThickness="1" X1="0" X2="30" Y1="0" Y2="0" /&gt;
                                        &lt;TextBlock Text="x - 4" HorizontalAlignment="Center" /&gt;
                                    &lt;/StackPanel&gt;
                                &lt;/StackPanel&gt;
                            &lt;/samples:FunctionSeries.Title&gt;
                        &lt;/samples:FunctionSeries&gt;
                    &lt;/charting:Chart.Series&gt;
                &lt;/charting:Chart&gt;
            &lt;/Grid&gt;
            
            &lt;Grid Style="{StaticResource WrapperStyle}"&gt;
                &lt;charting:Chart x:Name="CustomFunctionChart" Title="Custom Function"&gt;
                    &lt;charting:Chart.Series&gt;
                        &lt;charting:ScatterSeries
                          Title="Particulate Levels"
                          ItemsSource="{Binding LevelsInRainfall, Source={StaticResource ParticulateLevel}}"
                          IndependentValueBinding="{Binding Rainfall}"
                          DependentValueBinding="{Binding Particulate}" /&gt;
                        &lt;samples:FunctionSeries
                          Title="f(x) = 110 + 3 * Math.Sin(x)"
                          LineThickness="1" /&gt;
                    &lt;/charting:Chart.Series&gt;
                &lt;/charting:Chart&gt;
            &lt;/Grid&gt;
        &lt;/controls:WrapPanel&gt;
        
        &lt;!-- Regression Scenario --&gt;
        &lt;ContentControl Content="Regression Scenario" Style="{StaticResource Header}"/&gt;
        &lt;Grid Height="500"&gt;
            &lt;Grid.RowDefinitions&gt;
                &lt;RowDefinition Height="Auto" /&gt;
                &lt;RowDefinition /&gt;
            &lt;/Grid.RowDefinitions&gt;
            &lt;Grid.ColumnDefinitions&gt;
                &lt;ColumnDefinition Width="Auto" /&gt;
                &lt;ColumnDefinition /&gt;
            &lt;/Grid.ColumnDefinitions&gt;
            &lt;TextBlock HorizontalAlignment="Right" Text="Regression: " /&gt;
            &lt;ComboBox
              Grid.Column="1"
              SelectionChanged="OnRegressionTypeChanged"
              HorizontalAlignment="Left"&gt;
                &lt;ComboBox.ItemsSource&gt;
                    &lt;controls:ObjectCollection&gt;
                        &lt;sys:String&gt;Linear&lt;/sys:String&gt;
                        &lt;sys:String&gt;Quadratic&lt;/sys:String&gt;
                        &lt;sys:String&gt;Cubic&lt;/sys:String&gt;
                        &lt;sys:String&gt;Quartic&lt;/sys:String&gt;
                    &lt;/controls:ObjectCollection&gt;
                &lt;/ComboBox.ItemsSource&gt;
            &lt;/ComboBox&gt;
            &lt;charting:Chart x:Name="ParticulateAnalysis" Grid.Row="1" Grid.ColumnSpan="2" Title="Particulate Level Analysis"&gt;
                &lt;charting:Chart.Series&gt;
                    &lt;charting:ScatterSeries
                      Title="Particulate Levels"
                      ItemsSource="{Binding LevelsInRainfall, Source={StaticResource ParticulateLevel}}"
                      IndependentValueBinding="{Binding Rainfall}"
                      DependentValueBinding="{Binding Particulate}" /&gt;
                    &lt;samples:FunctionSeries
                      Title="Regression"
                      LineBrush="Blue"
                      LineThickness="2" /&gt;
                &lt;/charting:Chart.Series&gt;
            &lt;/charting:Chart&gt;
        &lt;/Grid&gt;
    &lt;/StackPanel&gt;
&lt;/UserControl&gt;</sys:String>
    </src:SourceFile.Source>
  </src:SourceFile>
  <src:SourceFile Path="CustomSeriesSample.xaml.cs">
    <src:SourceFile.Source>
      <sys:String>// (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.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using Microsoft.Windows.Controls.DataVisualization.Charting;
using System.ComponentModel;

namespace Microsoft.Windows.Controls.Samples
{
    /// &lt;summary&gt;
    /// Charting sample demonstrating how to create a custom series.
    /// &lt;/summary&gt;
    [Sample("(2)Custom Series", DifficultyLevel.Advanced)]
    [Category("DataVisualization")]
    public partial class CustomSeriesSample : UserControl
    {
        /// &lt;summary&gt;
        /// Initializes a new instance of the CustomSeriesSample class.
        /// &lt;/summary&gt;
        public CustomSeriesSample()
        {
            InitializeComponent();

            // Use a custom function for a series
            FunctionSeries series = CustomFunctionChart.Series[1] as FunctionSeries;
            series.Function = x =&gt; 110 + 3 * Math.Sin(x);
        }

        /// &lt;summary&gt;
        /// Perform a regression against the particulate levels data.
        /// &lt;/summary&gt;
        /// &lt;param name="sender"&gt;The regression ComboBox.&lt;/param&gt;
        /// &lt;param name="e"&gt;Event arguments.&lt;/param&gt;
        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by an event handler in XAML.")]
        [SuppressMessage("Microsoft.Performance", "CA1814:PreferJaggedArraysOverMultidimensional", MessageId = "Body", Justification = "Simplifies the sample.")]
        [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Simplifies the sample.")]
        private void OnRegressionTypeChanged(object sender, SelectionChangedEventArgs e)
        {
            // Get the options and the series
            ComboBox combo = sender as ComboBox;
            if (combo == null || ParticulateAnalysis == null)
            {
                return;
            }
            ScatterSeries dataSeries = ParticulateAnalysis.Series[0] as ScatterSeries;
            FunctionSeries regressionSeries = ParticulateAnalysis.Series[1] as FunctionSeries;
            if (dataSeries == null || regressionSeries == null)
            {
                return;
            }

            // Get the active DataPoints (this assumes the default template for
            // ScatterSeries)
            Canvas plotArea = VisualTreeHelper.GetChild(dataSeries, 0) as Canvas;
            if (plotArea == null)
            {
                return;
            }
            List&lt;DataPoint&gt; activePoints =
                plotArea
                .Children
                .OfType&lt;DataPoint&gt;()
                .ToList();

            // The dimensions were added linearly to the ComboBox
            int dimension = combo.SelectedIndex + 1;

            // Initialize a simple least squares analysis
            int i = 0;
            int j = 0;
            int k = 0;
            double[] y = new double[activePoints.Count];
            double[,] x = new double[activePoints.Count, dimension + 1];
            for (i = 0; i &lt; activePoints.Count; i++)
            {
                DataPoint point = activePoints[i];
                double independentValue = Convert.ToDouble(point.IndependentValue, CultureInfo.InvariantCulture);

                for (j = 0; j &lt;= dimension; j++)
                {
                    x[i, j] = Math.Pow(independentValue, j);
                }

                y[i] = Convert.ToDouble(point.DependentValue, CultureInfo.InvariantCulture);
            }

            // Create the equations
            double[][] matrix = new double[dimension + 1][];
            for (i = 0; i &lt;= dimension; i++)
            {
                // Create the row
                matrix[i] = new double[dimension + 2];

                // indeterminate coefficients
                for (j = 0; j &lt;= dimension; j++)
                {
                    matrix[i][j] = 0.0;
                    for (k = 0; k &lt; activePoints.Count; k++)
                    {
                        matrix[i][j] += x[k, i] * x[k, j];
                    }
                }

                // determinate values
                for (k = 0; k &lt; activePoints.Count; k++)
                {
                    matrix[i][dimension + 1] += x[k, i] * y[k];
                }
            }

            // Convert to row-echelon form
            i = 0;
            j = 0;
            while (i &lt;= dimension &amp;&amp; j &lt;= dimension)
            {
                // Get the pivot in column j starting at row i
                int pivotRow = i;
                for (k = i; k &lt;= dimension; k++)
                {
                    if (Math.Abs(matrix[k][j]) &gt; Math.Abs(matrix[pivotRow][j]))
                    {
                        pivotRow = k;
                    }
                }
                double pivot = matrix[pivotRow][j];

                // If we have a pivot element
                if (pivot != 0)
                {
                    // Swap the current row with the pivot row
                    double[] temp = matrix[i];
                    matrix[i] = matrix[pivotRow];
                    matrix[pivotRow] = temp;
                    pivotRow = i;

                    // Normalize the pivot row to the pivot
                    double c = matrix[i][j];
                    for (k = 0; k &lt;= dimension + 1; k++)
                    {
                        matrix[i][k] /= c;
                    }

                    // Clear out the pivot position from the remaining rows
                    for (k = i + 1; k &lt;= dimension; k++)
                    {
                        c = matrix[k][j];
                        for (int m = i; m &lt;= dimension + 1; m++)
                        {
                            matrix[k][m] -= c * matrix[i][m];
                        }
                    }

                    i++;
                }

                j++;
            }

            // Solve using substitution
            for (i = dimension - 1; i &gt;= 0; i--)
            {
                for (j = dimension; j &gt; i; j--)
                {
                    matrix[i][dimension + 1] -= matrix[i][j] * matrix[j][dimension + 1];
                    matrix[i][j] = 0;
                }
            }

            // Capture the coefficients
            double a0 = matrix[0][dimension + 1];
            double a1 = matrix[1][dimension + 1];
            double a2 = (dimension &gt;= 2) ? matrix[2][dimension + 1] : double.NaN;
            double a3 = (dimension &gt;= 3) ? matrix[3][dimension + 1] : double.NaN;
            double a4 = (dimension == 4) ? matrix[4][dimension + 1] : double.NaN;

            // Create the function
            Func&lt;double, double&gt; function = null;
            switch (dimension)
            {
                case 1:
                    function = z =&gt; a1 * z + a0;
                    break;
                case 2:
                    function = z =&gt; a2 * z * z + a1 * z + a0;
                    break;
                case 3:
                    function = z =&gt; a3 * z * z * z + a2 * z * z + a1 * z + a0;
                    break;
                case 4:
                    function = z =&gt; a4 * z * z * z * z + a3 * z * z * z + a2 * z * z + a1 * z + a0;
                    break;
            }

            // Create the title
            StackPanel title = new StackPanel { Orientation = Orientation.Horizontal };
            title.Children.Add(
                new TextBlock
                {
                    Text = "f(x) = ",
                    Margin = new Thickness(0, 4, 0, 0)
                });

            title.Children.Add(
                new TextBlock
                {
                    Text = a0.ToString("N3", CultureInfo.InvariantCulture),
                    Margin = new Thickness(0, 4, 0, 0)
                });
            AddTitleTerm(title, a1, 1);
            if (dimension &gt;= 2)
            {
                AddTitleTerm(title, a2, 2);
            }
            if (dimension &gt;= 3)
            {
                AddTitleTerm(title, a3, 3);
            }
            if (dimension == 4)
            {
                AddTitleTerm(title, a4, 4);
            }

            // Set the function and the title
            regressionSeries.Function = function;
            regressionSeries.Title = title;
        }

        /// &lt;summary&gt;
        /// Add a term to the title.
        /// &lt;/summary&gt;
        /// &lt;param name="title"&gt;The title container.&lt;/param&gt;
        /// &lt;param name="value"&gt;The value of the term.&lt;/param&gt;
        /// &lt;param name="exponent"&gt;The exponent of the term.&lt;/param&gt;
        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by a method called by an event handler in XAML.")]
        private static void AddTitleTerm(StackPanel title, double value, int exponent)
        {
            if (value == 0)
            {
                return;
            }

            title.Children.Add(
                new TextBlock
                {
                    Text = value &gt;= 0 ? " + " : " - ",
                    Margin = new Thickness(0, 4, 0, 0)
                });
            title.Children.Add(
                new TextBlock
                {
                    Text = string.Format(CultureInfo.InvariantCulture, "{0:N3}x", Math.Abs(value)),
                    Margin = new Thickness(0, 4, 0, 0)
                });

            if (exponent &gt; 1)
            {
                title.Children.Add(
                    new TextBlock
                    {
                        Text = exponent.ToString(CultureInfo.InvariantCulture),
                        FontSize = 8
                    });
            }
        }
    }
}</sys:String>
    </src:SourceFile.Source>
  </src:SourceFile>
  <src:SourceFile Path="FunctionSeries.cs">
    <src:SourceFile.Source>
      <sys:String>// (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.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
using System.Windows.Media;
using Microsoft.Windows.Controls.DataVisualization;
using Microsoft.Windows.Controls.DataVisualization.Charting;

namespace Microsoft.Windows.Controls.Samples
{
    /// &lt;summary&gt;
    /// FunctionSeries is used to single variable functions on a chart.
    /// &lt;/summary&gt;
    [TemplatePart(Name = FunctionSeries.PlotAreaName, Type = typeof(Canvas))]
    public sealed partial class FunctionSeries : Series, IRangeAxisInformationProvider
    {
        /// &lt;summary&gt;
        /// The default control template would normally reside in generic.xaml,
        /// but the sample project is an application and doesn't have that file.
        /// We're just putting it here, but a real control project wouldn't.
        /// &lt;/summary&gt;
        private const string DefaultTemplate =
@"&lt;ControlTemplate
  xmlns='http://schemas.microsoft.com/client/2007'
  xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
  xmlns:samples='clr-namespace:Microsoft.Windows.Controls.Samples;assembly=Microsoft.Windows.Controls.Samples'
  TargetType='samples:FunctionSeries'&gt;
    &lt;Canvas x:Name='PlotArea'&gt;
        &lt;Path
          Stroke='{TemplateBinding LineBrush}'
          StrokeThickness='{TemplateBinding LineThickness}'
          Data='{TemplateBinding Geometry}' /&gt;
    &lt;/Canvas&gt;
&lt;/ControlTemplate&gt;";

        #region Template Parts
        /// &lt;summary&gt;
        /// Name of the plot area canvas.
        /// &lt;/summary&gt;
        private const string PlotAreaName = "PlotArea";

        /// &lt;summary&gt;
        /// Gets or sets the plot area canvas.
        /// &lt;/summary&gt;
        private Canvas PlotArea { get; set; }
        #endregion Template Parts

        #region public Func&lt;double, double&gt; Function
        /// &lt;summary&gt;
        /// Gets or sets the function to plot.
        /// &lt;/summary&gt;
        [TypeConverter(typeof(SimpleFunctionTypeConverter))]
        public Func&lt;double, double&gt; Function
        {
            get { return GetValue(FunctionProperty) as Func&lt;double, double&gt;; }
            set { SetValue(FunctionProperty, value); }
        }

        /// &lt;summary&gt;
        /// Identifies the Function dependency property.
        /// &lt;/summary&gt;
        public static readonly DependencyProperty FunctionProperty =
            DependencyProperty.Register(
                "Function",
                typeof(Func&lt;double, double&gt;),
                typeof(FunctionSeries),
                new PropertyMetadata(null, OnFunctionPropertyChanged));

        /// &lt;summary&gt;
        /// FunctionProperty property changed handler.
        /// &lt;/summary&gt;
        /// &lt;param name="d"&gt;FunctionSeries that changed its Function.&lt;/param&gt;
        /// &lt;param name="e"&gt;Event arguments.&lt;/param&gt;
        private static void OnFunctionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FunctionSeries source = d as FunctionSeries;
            source.Refresh();
        }
        #endregion public Func&lt;double, double&gt; Function

        #region public Geometry Geometry
        /// &lt;summary&gt;
        /// Gets or sets the geometry of the line object rendering the function.
        /// &lt;/summary&gt;
        public Geometry Geometry
        {
            get { return GetValue(GeometryProperty) as Geometry; }
            set { SetValue(GeometryProperty, value); }
        }

        /// &lt;summary&gt;
        /// Identifies the Geometry dependency property.
        /// &lt;/summary&gt;
        public static readonly DependencyProperty GeometryProperty =
            DependencyProperty.Register(
                "Geometry",
                typeof(Geometry),
                typeof(FunctionSeries),
                new PropertyMetadata(null));
        #endregion public Geometry Geometry

        #region public Brush LineBrush
        /// &lt;summary&gt;
        /// Gets or sets the brush used to plot the function.
        /// &lt;/summary&gt;
        public Brush LineBrush
        {
            get { return GetValue(LineBrushProperty) as Brush; }
            set { SetValue(LineBrushProperty, value); }
        }

        /// &lt;summary&gt;
        /// Identifies the LineBrush dependency property.
        /// &lt;/summary&gt;
        public static readonly DependencyProperty LineBrushProperty =
            DependencyProperty.Register(
                "LineBrush",
                typeof(Brush),
                typeof(FunctionSeries),
                new PropertyMetadata(null, OnLineBrushPropertyChanged));

        /// &lt;summary&gt;
        /// LineBrushProperty property changed handler.
        /// &lt;/summary&gt;
        /// &lt;param name="d"&gt;FunctionSeries that changed its LineBrush.&lt;/param&gt;
        /// &lt;param name="e"&gt;Event arguments.&lt;/param&gt;
        private static void OnLineBrushPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FunctionSeries source = d as FunctionSeries;
            Brush value = e.NewValue as Brush;
            source.LegendItem.DataContext = new ContentControl { Background = value };
        }
        #endregion public Brush LineBrush

        #region public double LineThickness
        /// &lt;summary&gt;
        /// Gets or sets the thickness of the line used to plot the function.
        /// &lt;/summary&gt;
        public double LineThickness
        {
            get { return (double) GetValue(LineThicknessProperty); }
            set { SetValue(LineThicknessProperty, value); }
        }

        /// &lt;summary&gt;
        /// Identifies the LineThickness dependency property.
        /// &lt;/summary&gt;
        public static readonly DependencyProperty LineThicknessProperty =
            DependencyProperty.Register(
                "LineThickness",
                typeof(double),
                typeof(FunctionSeries),
                new PropertyMetadata(1.0));
        #endregion public double LineThickness

        #region private IRangeAxis IndependentAxis
        /// &lt;summary&gt;
        /// Gets or sets the value of the independent axis.
        /// &lt;/summary&gt;
        private IRangeAxis IndependentAxis
        {
            get { return GetValue(IndependentAxisProperty) as IRangeAxis; }
            set { SetValue(IndependentAxisProperty, value); }
        }

        /// &lt;summary&gt;
        /// Identifies the IndependentAxis dependency property.
        /// &lt;/summary&gt;
        private static readonly DependencyProperty IndependentAxisProperty =
            DependencyProperty.Register(
                "IndependentAxis",
                typeof(IRangeAxis),
                typeof(FunctionSeries),
                null);
        #endregion protected IRangeAxis IndependentAxis

        #region private IRangeAxis DependentAxis
        /// &lt;summary&gt;
        /// Gets or sets the value of the dependent axis.
        /// &lt;/summary&gt;
        private IRangeAxis DependentAxis
        {
            get { return GetValue(DependentAxisProperty) as IRangeAxis; }
            set { SetValue(DependentAxisProperty, value); }
        }

        /// &lt;summary&gt;
        /// Identifies the DependentAxis dependency property.
        /// &lt;/summary&gt;
        private static readonly DependencyProperty DependentAxisProperty =
            DependencyProperty.Register(
                "DependentAxis",
                typeof(IRangeAxis),
                typeof(FunctionSeries),
                null);
        #endregion protected IRangeAxis DependentAxis

        /// &lt;summary&gt;
        /// Gets or sets the single chart legend item associated with the
        /// series.
        /// &lt;/summary&gt;
        private LegendItem LegendItem { get; set; }

        /// &lt;summary&gt;
        /// Gets or sets the Geometry used to clip the line to the PlotArea
        /// bounds.
        /// &lt;/summary&gt;
        private RectangleGeometry ClipGeometry { get; set; }

        /// &lt;summary&gt;
        /// Initializes a new instance of the FunctionSeries class.
        /// &lt;/summary&gt;
        public FunctionSeries()
        {
            LegendItem = new LegendItem();
            LegendItems.Add(LegendItem);
            Clip = ClipGeometry = new RectangleGeometry();
            SizeChanged += OnSizeChanged;

            // Explicitly load the default template since the samples project
            // is an application and does not have a generic.xaml file.
            Template = XamlReader.Load(DefaultTemplate) as ControlTemplate;
            LineBrush = new SolidColorBrush(Colors.Purple);
        }

        /// &lt;summary&gt;
        /// Refreshes data from data source and renders the series.
        /// &lt;/summary&gt;
        public override void Refresh()
        {
            if (SeriesHost == null || ActualWidth == 0)
            {
                return;
            }

            // Ensure we have a function to plot
            Func&lt;double, double&gt; function = Function;
            if (function == null)
            {
                return;
            }

            // Ensure we have axes
            IRangeAxis independent = GetAxis(AxisOrientation.Horizontal, IndependentAxis);
            IndependentAxis = independent;
            IRangeAxis dependent = GetAxis(AxisOrientation.Vertical, DependentAxis);
            DependentAxis = dependent;
            if (!independent.Range.HasData)
            {
                return;
            }

            // Create a geometry that matches the function to plot
            PathGeometry path = new PathGeometry();
            PathFigure figure = new PathFigure();

            // Get the range over which we will 
            double start = (double) independent.Range.Minimum;
            double end = (double) independent.Range.Maximum;

            // Adjust the line at each pixel
            double delta = (end - start) / ActualWidth;
            
            // We'll only add a new line segment when the slope is changing
            // between points
            Point last = GetPoint(start, function, independent, dependent);
            figure.StartPoint = last;
            double slope = double.NaN;
            for (double x = start + delta; x &lt;= end; x += delta)
            {
                Point next = GetPoint(x, function, independent, dependent);
                double newSlope = (next.Y - last.Y) / (next.X - last.X);

                if (slope != newSlope)
                {
                    figure.Segments.Add(new LineSegment { Point = last });
                }

                slope = newSlope;
                last = next;
            }
            figure.Segments.Add(new LineSegment { Point = last });

            path.Figures.Add(figure);
            Geometry = path;
        }

        /// &lt;summary&gt;
        /// Get a point in screen coordinates.
        /// &lt;/summary&gt;
        /// &lt;param name="x"&gt;Independent value.&lt;/param&gt;
        /// &lt;param name="function"&gt;The function.&lt;/param&gt;
        /// &lt;param name="independent"&gt;The independent axis.&lt;/param&gt;
        /// &lt;param name="dependent"&gt;The dependent axis.&lt;/param&gt;
        /// &lt;returns&gt;The point in screen coordinates.&lt;/returns&gt;
        private Point GetPoint(double x, Func&lt;double, double&gt; function, IRangeAxis independent, IRangeAxis dependent)
        {
            // Get the dependent value
            double y = double.NaN;
            try
            {
                y = function(x);
            }
            catch (DivideByZeroException)
            {
            }

            // Map the actual values into coordinate values
            return new Point(
                independent.GetPlotAreaCoordinate(x),
                Math.Min(
                    Math.Max(
                        ActualHeight - dependent.GetPlotAreaCoordinate(y),
                        -1),
                    ActualHeight + 1));
        }

        /// &lt;summary&gt;
        /// Get the plot area after loading it from XAML.
        /// &lt;/summary&gt;
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            PlotArea = GetTemplateChild("PlotArea") as Canvas;
        }

        /// &lt;summary&gt;
        /// Updates the visual appearance of all the data points when the size
        /// changes. 
        /// &lt;/summary&gt;
        /// &lt;param name="sender"&gt;The series.&lt;/param&gt;
        /// &lt;param name="e"&gt;Event arguments.&lt;/param&gt;
        private void OnSizeChanged(object sender, SizeChangedEventArgs e)
        {
            // Update the clip geometry
            ClipGeometry.Rect = new Rect(0.0, 0.0, e.NewSize.Width, e.NewSize.Height);

            // Update the PlotArea size and refresh.
            if (PlotArea != null)
            {
                PlotArea.Width = e.NewSize.Width;
                PlotArea.Height = e.NewSize.Height;
                Refresh();
            }
        }

        /// &lt;summary&gt;
        /// Sets all the text the legend items to the title.
        /// &lt;/summary&gt;
        /// &lt;param name="oldValue"&gt;The old title.&lt;/param&gt;
        /// &lt;param name="newValue"&gt;The new title.&lt;/param&gt;
        protected override void OnTitleChanged(object oldValue, object newValue)
        {
            base.OnTitleChanged(oldValue, newValue);
            LegendItem.Content = Title;
        }

        /// &lt;summary&gt;
        /// Get or create a linear numeric axis in the correct dimension.
        /// &lt;/summary&gt;
        /// &lt;param name="orientation"&gt;Dimension of the axis to create.&lt;/param&gt;
        /// &lt;param name="oldAxis"&gt;
        /// Old value of the axis in this dimension.
        /// &lt;/param&gt;
        /// &lt;returns&gt;New value of the axis in this dimension.&lt;/returns&gt;
        private IRangeAxis GetAxis(AxisOrientation orientation, IRangeAxis oldAxis)
        {
            // Check the existing axes for a potential axis
            IRangeAxis axis =
                (from IRangeAxis a in SeriesHost.Axes.OfType&lt;IRangeAxis&gt;()
                 where a.Orientation == orientation
                 select a)
                .FirstOrDefault();

            if (axis == null)
            {
                // Create a new axis if not found
                axis = new LinearAxis
                {
                    Orientation = orientation,
                };
            }

            if (oldAxis != axis)
            {
                // Unregister any existing axis
                if (oldAxis != null)
                {
                    SeriesHost.UnregisterWithAxis(this, oldAxis);
                    oldAxis.Invalidated -= OnAxisInvalidated;
                }
                
                // Register the new axis
                SeriesHost.RegisterWithAxis(this, axis);
                axis.Invalidated += OnAxisInvalidated;
            }

            return axis;
        }

        /// &lt;summary&gt;
        /// Updates the series when the axis is invalidated.
        /// &lt;/summary&gt;
        /// &lt;param name="sender"&gt;The axis that was invalidated.&lt;/param&gt;
        /// &lt;param name="e"&gt;Event arguments.&lt;/param&gt;
        private void OnAxisInvalidated(object sender, EventArgs e)
        {
            if (DependentAxis != null &amp;&amp; IndependentAxis != null)
            {
                Refresh();
            }
        }

        /// &lt;summary&gt;
        /// Ensures that chart and series are kept in a consistent state when a
        /// series is added or removed from a chart. 
        /// &lt;/summary&gt;
        /// &lt;param name="oldValue"&gt;Old chart.&lt;/param&gt;
        /// &lt;param name="newValue"&gt;New chart.&lt;/param&gt;
        protected override void OnSeriesHostPropertyChanged(ISeriesHost oldValue, ISeriesHost newValue)
        {
            IRangeAxis axis = null;

            // Unregister the axes from the old chart
            if (oldValue != null)
            {
                axis = IndependentAxis;
                if (axis != null)
                {
                    oldValue.UnregisterWithAxis(this, axis);
                    IndependentAxis = null;
                }

                axis = DependentAxis;
                if (axis != null)
                {
                    oldValue.UnregisterWithAxis(this, axis);
                    DependentAxis = null;
                }
            }

            // Register the axes with new chart
            if (newValue != null)
            {
                axis = IndependentAxis;
                if (axis != null)
                {
                    newValue.RegisterWithAxis(this, axis);
                }

                axis = DependentAxis;
                if (axis != null)
                {
                    newValue.RegisterWithAxis(this, axis);
                }
            }

            base.OnSeriesHostPropertyChanged(oldValue, newValue);
        }

        /// &lt;summary&gt;
        /// If data is found returns the minimum and maximum dependent numeric
        /// values. 
        /// &lt;/summary&gt;
        /// &lt;param name="rangeAxis"&gt;IRangeAxis that needs the data.&lt;/param&gt;
        /// &lt;returns&gt;
        /// The range of values or empty if no data is present.
        /// &lt;/returns&gt;
        public Range&lt;IComparable&gt; GetActualRange(IRangeAxis rangeAxis)
        {
            // Use an empty range so we only plot over the area used by other
            // axes.
            return new Range&lt;IComparable&gt;();
        }

        /// &lt;summary&gt;
        /// If data is found returns the minimum and maximum dependent numeric
        /// values. 
        /// &lt;/summary&gt;
        /// &lt;param name="rangeAxis"&gt;IRangeAxis that needs the data.&lt;/param&gt;
        /// &lt;returns&gt;
        /// The range of values or empty if no data is present.
        /// &lt;/returns&gt;
        public Range&lt;IComparable&gt; GetDesiredRange(IRangeAxis rangeAxis)
        {
            // Use an empty range so we only plot over the area used by other
            // axes.
            return new Range&lt;IComparable&gt;();
        }
    }
}</sys:String>
    </src:SourceFile.Source>
  </src:SourceFile>
  <src:SourceFile Path="SimpleFunctionTypeConverter.cs">
    <src:SourceFile.Source>
      <sys:String>// (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;
using System.Globalization;
using System.Linq.Expressions;
using System.Text;

namespace Microsoft.Windows.Controls.Samples
{
    /// &lt;summary&gt;
    /// TypeConverter used for creating single variable functions from simple
    /// arithmetic expressions.
    /// &lt;/summary&gt;
    public class SimpleFunctionTypeConverter : TypeConverter
    {
        /// &lt;summary&gt;
        /// Initializes a new instance of the SimpleFunctionTypeConverter class.
        /// &lt;/summary&gt;
        public SimpleFunctionTypeConverter()
        {
        }

        /// &lt;summary&gt;
        /// Determine whether the sourceType can be converted to a single
        /// variable function.
        /// &lt;/summary&gt;
        /// &lt;param name="context"&gt;Conversion context.&lt;/param&gt;
        /// &lt;param name="sourceType"&gt;The type to convert from.&lt;/param&gt;
        /// &lt;returns&gt;
        /// A value indicating whether the type can be converted.
        /// &lt;/returns&gt;
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            return sourceType == typeof(string);
        }

        /// &lt;summary&gt;
        /// Convert the value into a single variable function.
        /// &lt;/summary&gt;
        /// &lt;param name="context"&gt;Conversion context.&lt;/param&gt;
        /// &lt;param name="culture"&gt;Conversion culture.&lt;/param&gt;
        /// &lt;param name="value"&gt;The value to convert.&lt;/param&gt;
        /// &lt;returns&gt;A single variable function.&lt;/returns&gt;
        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
        {
            Func&lt;double, double&gt; function = null;

            string text = value as string;
            if (!string.IsNullOrEmpty(text))
            {
                function = new Parser(text).Function;
            }

            return function;
        }

        /// &lt;summary&gt;
        /// Defines the type of a token.
        /// &lt;/summary&gt;
        private enum TokenType
        {
            /// &lt;summary&gt;
            /// A numeric value..
            /// &lt;/summary&gt;
            Number,

            /// &lt;summary&gt;
            /// An identifier.
            /// &lt;/summary&gt;
            Identifier,

            /// &lt;summary&gt;
            /// The addition operator.
            /// &lt;/summary&gt;
            Addition,

            /// &lt;summary&gt;
            /// The substraction operator.
            /// &lt;/summary&gt;
            Subtraction,

            /// &lt;summary&gt;
            /// The exponentiation operator.
            /// &lt;/summary&gt;
            Multiplication,

            /// &lt;summary&gt;
            /// The division operator.
            /// &lt;/summary&gt;
            Division,

            /// &lt;summary&gt;
            /// The exponentiation operator.
            /// &lt;/summary&gt;
            Exponentiation,

            /// &lt;summary&gt;
            /// A left parenthesis.
            /// &lt;/summary&gt;
            LeftParenthesis,

            /// &lt;summary&gt;
            /// A right parenthesis.
            /// &lt;/summary&gt;
            RightParenthesis
        }

        /// &lt;summary&gt;
        /// Represents a lexical token.
        /// &lt;/summary&gt;
        private class Token
        {
            /// &lt;summary&gt;
            /// Gets or sets the type of the token.
            /// &lt;/summary&gt;
            public TokenType TokenType { get; set; }

            /// &lt;summary&gt;
            /// Gets or sets the value of a token (for numbers and identifiers).
            /// &lt;/summary&gt;
            public object Value { get; set; }

            /// &lt;summary&gt;
            /// Initializes a new instance of the Token class.
            /// &lt;/summary&gt;
            public Token()
            {
            }
        }

        /// &lt;summary&gt;
        /// Perform lexical analysis of simple expressions.
        /// &lt;/summary&gt;
        private class Lexer
        {
            /// &lt;summary&gt;
            /// Gets or sets the input string to scan.
            /// &lt;/summary&gt;
            internal string Input { get; set; }

            /// &lt;summary&gt;
            /// Gets or sets the current position of the lexer.
            /// &lt;/summary&gt;
            internal int Position { get; set; }

            /// &lt;summary&gt;
            /// Gets a value indicating whether the lexer has room to advance
            /// through the input.
            /// &lt;/summary&gt;
            internal bool CanAdvance
            {
                get { return Position &lt; Input.Length; }
            }

            /// &lt;summary&gt;
            /// Gets the character at the current input position.
            /// &lt;/summary&gt;
            private char Current
            {
                get { return Input[Position]; }
            }

            /// &lt;summary&gt;
            /// Gets or sets the lookahead token.
            /// &lt;/summary&gt;
            private Token Lookahead { get; set; }

            /// &lt;summary&gt;
            /// Initializes a new instance of the Lexer class.
            /// &lt;/summary&gt;
            /// &lt;param name="input"&gt;The input to analyze.&lt;/param&gt;
            public Lexer(string input)
            {
                Debug.Assert(!string.IsNullOrEmpty(input), "input shuould not be null or empty!");
                Input = input;
            }

            /// &lt;summary&gt;
            /// Advance the token to the next input.
            /// &lt;/summary&gt;
            /// &lt;returns&gt;The token that was read.&lt;/returns&gt;
            private Token ReadNext()
            {
                // Eat as much whitespace as possible
                while (CanAdvance &amp;&amp; char.IsWhiteSpace(Current))
                {
                    Position++;
                }

                // Match a literal token
                Token token =
                    MatchLiteral('(', TokenType.LeftParenthesis) ??
                    MatchLiteral(')', TokenType.RightParenthesis) ??
                    MatchLiteral('+', TokenType.Addition) ??
                    MatchLiteral('-', TokenType.Subtraction) ??
                    MatchLiteral('*', TokenType.Multiplication) ??
                    MatchLiteral('/', TokenType.Division) ??
                    MatchLiteral('^', TokenType.Exponentiation);

                // Match identifier or number tokens
                if (token == null)
                {
                    int start = Position;
                    
                    // Try and match identifiers
                    while (CanAdvance &amp;&amp; char.IsLetter(Current))
                    {
                        Position++;
                    }
                    if (start != Position)
                    {
                        token = new Token { TokenType = TokenType.Identifier };
                        token.Value = Input.Substring(start, Position - start);
                    }
                    else
                    {
                        // Try and match numbers
                        while (CanAdvance &amp;&amp; char.IsDigit(Current))
                        {
                            Position++;
                        }
                        if (CanAdvance &amp;&amp; Current == '.')
                        {
                            Position++;
                        }
                        while (CanAdvance &amp;&amp; char.IsDigit(Current))
                        {
                            Position++;
                        }

                        if (start != Position)
                        {
                            token = new Token { TokenType = TokenType.Number };
                            token.Value = double.Parse(Input.Substring(start, Position - start), CultureInfo.InvariantCulture);
                        }
                    }
                }

                if (token != null)
                {
                    return token;
                }
                else if (CanAdvance)
                {
                    throw new FormatException(string.Format(CultureInfo.InvariantCulture, "Unknown token at position {0}!", Position));
                }
                else
                {
                    return null;
                }
            }

            /// &lt;summary&gt;
            /// Match a literal token.
            /// &lt;/summary&gt;
            /// &lt;param name="tokenChar"&gt;Character of the token.&lt;/param&gt;
            /// &lt;param name="tokenType"&gt;The type of the token.&lt;/param&gt;
            /// &lt;returns&gt;The literal token, if matched.&lt;/returns&gt;
            private Token MatchLiteral(char tokenChar, TokenType tokenType)
            {
                if (CanAdvance &amp;&amp; Current == tokenChar)
                {
                    Position++;
                    return new Token { TokenType = tokenType };
                }

                return null;
            }

            /// &lt;summary&gt;
            /// Get the next input token.
            /// &lt;/summary&gt;
            /// &lt;returns&gt;The next input token.&lt;/returns&gt;
            public Token Get()
            {
                Token token = null;
                if (Lookahead != null)
                {
                    token = Lookahead;
                    Lookahead = null;
                }
                else
                {
                    token = ReadNext();
                }
                return token;
            }

            /// &lt;summary&gt;
            /// Peek at the lookahead token.
            /// &lt;/summary&gt;
            /// &lt;returns&gt;The lookahead token.&lt;/returns&gt;
            public Token Peek()
            {
                if (Lookahead == null)
                {
                    Lookahead = ReadNext();
                }
                return Lookahead;
            }
        }

        /// &lt;summary&gt;
        /// Perform syntactic analysis of simple expressions.
        /// &lt;/summary&gt;
        /// &lt;remarks&gt;
        /// The parser uses the following grammar:
        ///    {Expression}
        ///         := {Term} '+' {Expression}
        ///         |  {Term} '-' {Expression}
        ///         |  {Term}
        ///    {Term}
        ///         := {Exponent} '*' {Term}
        ///         |  {Exponent} '/' {Term}
        ///         |  {Exponent}
        ///    {Exponent}
        ///         := {Factor} '^' {Exponent}
        ///         |  {Factor}
        ///    {Factor}
        ///         := {Number}
        ///         |  {Identifier}
        ///         | '(' {Expression} ')'
        /// &lt;/remarks&gt;
        private class Parser
        {
            /// &lt;summary&gt;
            /// Gets or sets the lexer used for lexical analysis.
            /// &lt;/summary&gt;
            private Lexer Lexer { get; set; }

            /// &lt;summary&gt;
            /// Gets or sets the single variable of the function.
            /// &lt;/summary&gt;
            private ParameterExpression Parameter { get; set; }

            /// &lt;summary&gt;
            /// Gets the function created from the input.
            /// &lt;/summary&gt;
            public Func&lt;double, double&gt; Function { get; private set; }

            /// &lt;summary&gt;
            /// Initializes a new instance of the Parser class.
            /// &lt;/summary&gt;
            /// &lt;param name="input"&gt;The input to analyze.&lt;/param&gt;
            public Parser(string input)
            {
                Lexer = new Lexer(input);
                Parse();
            }

            /// &lt;summary&gt;
            /// Parse the input and create a function.
            /// &lt;/summary&gt;
            private void Parse()
            {
                // Build the expression
                Expression expression = GetExpression();

                // Ensure we exhausted the input
                int finalPosition = Lexer.Position;
                Token finalToken = Lexer.Get();
                if (finalToken != null)
                {
                    throw new FormatException(string.Format(CultureInfo.InvariantCulture, "Unexpected token {0} at position {1}!", finalToken.TokenType, finalPosition));
                }

                // Wrap the expression in a function
                Expression&lt;Func&lt;double, double&gt;&gt; functionExpression =
                    Expression.Lambda&lt;Func&lt;double, double&gt;&gt;(
                        expression,
                        Parameter ?? Expression.Parameter(typeof(double), "x"));

                // Compile the expression into a function
                Function = functionExpression.Compile();
            }

            /// &lt;summary&gt;
            /// Read an expression.
            /// &lt;/summary&gt;
            /// &lt;returns&gt;The parsed expression.&lt;/returns&gt;
            private Expression GetExpression()
            {
                Expression term = GetTerm();
                if (TryMatch(TokenType.Addition))
                {
                    Expression expr = GetExpression();
                    return Expression.Add(term, expr);
                }
                else if (TryMatch(TokenType.Subtraction))
                {
                    Expression expr = GetExpression();
                    return Expression.Subtract(term, expr);
                }
                return term;
            }

            /// &lt;summary&gt;
            /// Read a term.
            /// &lt;/summary&gt;
            /// &lt;returns&gt;The parsed term.&lt;/returns&gt;
            private Expression GetTerm()
            {
                Expression exponent = GetExponent();
                if (TryMatch(TokenType.Multiplication))
                {
                    Expression term = GetTerm();
                    return Expression.Multiply(exponent, term);
                }
                else if (TryMatch(TokenType.Division))
                {
                    Expression term = GetTerm();
                    return Expression.Divide(exponent, term);
                }
                return exponent;
            }

            /// &lt;summary&gt;
            /// Read an exponent.
            /// &lt;/summary&gt;
            /// &lt;returns&gt;The parsed exponent.&lt;/returns&gt;
            private Expression GetExponent()
            {
                Expression factor = GetFactor();
                if (TryMatch(TokenType.Exponentiation))
                {
                    Expression power = GetExponent();
                    return Expression.Power(factor, power);
                }
                else
                {
                    return factor;
                }
            }

            /// &lt;summary&gt;
            /// Read a factor.
            /// &lt;/summary&gt;
            /// &lt;returns&gt;The parsed factor.&lt;/returns&gt;
            private Expression GetFactor()
            {
                Token token = Lexer.Get();
                if (token != null)
                {
                    if (token.TokenType == TokenType.Number)
                    {
                        return Expression.Constant(token.Value, typeof(double));
                    }
                    else if (token.TokenType == TokenType.Identifier)
                    {
                        string name = token.Value as string;

                        // Linq expressions use referential equality on
                        // parameters, so we need to use the same instance
                        if (Parameter != null &amp;&amp; Parameter.Name != name)
                        {
                            throw new FormatException("Only single variable functions are supported!");
                        }
                        else if (Parameter == null)
                        {
                            Parameter = Expression.Parameter(typeof(double), name);
                        }
                        return Parameter;
                    }
                    else if (token.TokenType == TokenType.LeftParenthesis)
                    {
                        Expression nested = GetExpression();
                        if (TryMatch(TokenType.RightParenthesis))
                        {
                            return nested;
                        }
                    }
                }

                throw new FormatException(string.Format(CultureInfo.InvariantCulture, "Unexpected token type at position {0}!", Lexer.Position));
            }
            
            /// &lt;summary&gt;
            /// Try to match a token.
            /// &lt;/summary&gt;
            /// &lt;param name="tokenType"&gt;The type of token to match.&lt;/param&gt;
            /// &lt;returns&gt;
            /// A value indicating whether the token was matched.
            /// &lt;/returns&gt;
            private bool TryMatch(TokenType tokenType)
            {
                Token token = Lexer.Peek();
                if (token != null &amp;&amp; token.TokenType == tokenType)
                {
                    Lexer.Get();
                    return true;
                }

                return false;
            }
        }
    }
}</sys:String>
    </src:SourceFile.Source>
  </src:SourceFile>
</src:SourceViewer>
    </StackPanel>
</UserControl>

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
South Africa South Africa
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions