Using the Microsoft Charting Library from F#

Since Microsoft released the charting controls used in SQL Server 2008 reporting as a standalone product, I decided to try them out from F#.

This makes a lot of sense because F# allows you to write scripts and run them from within Visual Studio in F# Interactive mode.  I love it because it’s the flexibility of MATLAB scripting (just try new things out without compiling) with the power and speed of the .NET Framework (MATLAB is based on layers and layers of Java and is by no means fast…).

To use the charting library from F#, the first thing we need is a form with a single chart control docked in it.  This is relatively simple, though I’ve copy/pasted/modified a lot of the visual designer code from one of the demo projects, so it looks like a lot, but it’s not.

type ChartForm =

    inherit Form

    val private chart1 : Chart

    val private legend1 : Legend

    val private series1 : Series

    val private title1 : Title

    val private chartArea1 : ChartArea

    new() as this =

        { chart1 = new Chart()

          legend1 = new Legend()

          series1 = new Series()

          title1 = new Title()

          chartArea1 = new ChartArea() } then

            this.chart1.BeginInit()

            this.SuspendLayout()

            //

            // chart1

            //

            this.chart1.BackColor <- System.Drawing.Color.WhiteSmoke;

            this.chart1.BackGradientStyle <- System.Windows.Forms.DataVisualization.Charting.GradientStyle.TopBottom;

            this.chart1.BackSecondaryColor <- System.Drawing.Color.White;

            this.chart1.BorderlineColor <- System.Drawing.Color.FromArgb(((int)(((byte)(26)))), ((int)(((byte)(59)))), ((int)(((byte)(105)))));

            this.chart1.BorderlineDashStyle <- System.Windows.Forms.DataVisualization.Charting.ChartDashStyle.Solid;

            this.chart1.BorderlineWidth <- 2;

            this.chart1.BorderSkin.SkinStyle <- System.Windows.Forms.DataVisualization.Charting.BorderSkinStyle.Emboss;

            this.chart1.Dock <- DockStyle.Fill

            this.chartArea1.Area3DStyle.Inclination <- 15;

            this.chartArea1.Area3DStyle.IsClustered <- true;

            this.chartArea1.Area3DStyle.IsRightAngleAxes <- false;

            this.chartArea1.Area3DStyle.Perspective <- 10;

            this.chartArea1.Area3DStyle.Rotation <- 10;

            this.chartArea1.Area3DStyle.WallWidth <- 0;

            // Should be using Enum.combine…

            this.chartArea1.AxisX.LabelAutoFitStyle <- System.Windows.Forms.DataVisualization.Charting.LabelAutoFitStyles.IncreaseFont + System.Windows.Forms.DataVisualization.Charting.LabelAutoFitStyles.DecreaseFont + System.Windows.Forms.DataVisualization.Charting.LabelAutoFitStyles.WordWrap

            this.chartArea1.AxisX.LabelStyle.Font <- new System.Drawing.Font(“Trebuchet MS”, 8.25F, System.Drawing.FontStyle.Bold);

            this.chartArea1.AxisX.LineColor <- System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));

            this.chartArea1.AxisX.MajorGrid.LineColor <- System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));

            this.chartArea1.AxisX.ScrollBar.LineColor <- System.Drawing.Color.Black;

            this.chartArea1.AxisX.ScrollBar.Size <- 10.0;

            this.chartArea1.AxisY.LabelStyle.Font <- new System.Drawing.Font(“Trebuchet MS”, 8.25F, System.Drawing.FontStyle.Bold);

            this.chartArea1.AxisY.LineColor <- System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));

            this.chartArea1.AxisY.MajorGrid.LineColor <- System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));

            this.chartArea1.AxisY.ScrollBar.LineColor <- System.Drawing.Color.Black;

            this.chartArea1.AxisY.ScrollBar.Size <- 10.0;

            this.chartArea1.BackColor <- System.Drawing.Color.Gainsboro;

            this.chartArea1.BackGradientStyle <- System.Windows.Forms.DataVisualization.Charting.GradientStyle.TopBottom;

            this.chartArea1.BackSecondaryColor <- System.Drawing.Color.White;

            this.chartArea1.BorderColor <- System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));

            this.chartArea1.BorderDashStyle <- System.Windows.Forms.DataVisualization.Charting.ChartDashStyle.Solid;

            this.chartArea1.CursorX.IsUserEnabled <- true;

            this.chartArea1.CursorX.IsUserSelectionEnabled <- true;

            this.chartArea1.CursorY.IsUserEnabled <- true;

            this.chartArea1.CursorY.IsUserSelectionEnabled <- true;

            this.chartArea1.Name <- “Default”;

            this.chartArea1.ShadowColor <- System.Drawing.Color.Transparent;

            this.chart1.ChartAreas.Add(this.chartArea1);

            this.legend1.BackColor <- System.Drawing.Color.Transparent;

            this.legend1.Enabled <- false;

            this.legend1.Font <- new System.Drawing.Font(“Trebuchet MS”, 8.25F, System.Drawing.FontStyle.Bold);

            this.legend1.IsTextAutoFit <- false;

            this.legend1.Name <- “Default”;

            this.chart1.Legends.Add(this.legend1);

            this.chart1.Location <- new System.Drawing.Point(16, 58);

            this.chart1.Name <- “chart1″;

            this.series1.BorderColor <- System.Drawing.Color.FromArgb(((int)(((byte)(180)))), ((int)(((byte)(26)))), ((int)(((byte)(59)))), ((int)(((byte)(105)))));

            this.series1.ChartArea <- “Default”;

            this.series1.ChartType <- System.Windows.Forms.DataVisualization.Charting.SeriesChartType.FastLine;

            this.series1.Legend <- “Default”;

            this.series1.Name <- “Series1″;

            this.series1.ShadowColor <- System.Drawing.Color.Black;

            this.chart1.Series.Add(this.series1);

            this.chart1.Size <- new System.Drawing.Size(412, 296);

            this.chart1.TabIndex <- 0;

            this.title1.Font <- new System.Drawing.Font(“Trebuchet MS”, 12.0f, System.Drawing.FontStyle.Bold);

            this.title1.ForeColor <- System.Drawing.Color.FromArgb(((int)(((byte)(26)))), ((int)(((byte)(59)))), ((int)(((byte)(105)))));

            this.title1.Name <- “Title1″;

            this.title1.ShadowColor <- System.Drawing.Color.FromArgb(((int)(((byte)(32)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));

            this.title1.ShadowOffset <- 3;

            this.title1.Text <- “Two series with 20000 points each”;

            this.chart1.Titles.Add(this.title1);

            //

            // ChartForm

            //

            this.BackColor <- System.Drawing.Color.White;

            this.Controls.Add(this.chart1);

            this.Font <- new System.Drawing.Font(“Verdana”, 9.0f, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));

            this.Name <- “ChartForm”

            this.chart1.EndInit()

            this.ResumeLayout(false)

    member this.Chart = this.chart1

    member this.Legend = this.legend1

    member this.Title = this.title1

    member this.ChartArea = this.chartArea1

At the end, I also expose the internal Chart, Legend, Title, and ChartArea objects.

With that out of the way, we need a few helper methods:

// Create a list of linearly-spaced numbers from start to stop that is count long

let linspace (start:float) (stop:float) (count:int) =

    [0..count]

    |> List.map (fun x -> ((stop-start)/(float count))*(float x)+start)

 

// Populate the series with the x- and y- values

let populateSeries (series:Series) (xList : float list) (yList : float list) =

    List.iter2 (fun (x:float) (y:float) -> series.Points.AddXY(x,y) |> ignore ) xList yList

The first method, linspace, just creates a uniformly-spaced list of floating point values from start to stop.  MATLAB users recognize linspace without a doubt.

The populateSeries method takes equal-length lists xList and yList and goes through each pair and adds those points to the specified series.

With those out of the way, we can go to town!

// Create our chart form

let testChart =

    let f = new ChartForm()

    f.Text <- “Charting Demo”

    f.Size <- new System.Drawing.Size(400, 250)

    f.Title.Text <- “Sine Waves”

    f

 

// Set up the series options

let sineSeries = testChart.Chart.Series.["Series1"]

sineSeries.ChartType <- SeriesChartType.FastLine

 

// x range is [0,10] with 100 numbers

let xValues = linspace 0.0 10.0 100

// plot y=2sin(x)+4sin(2x)

let yValues = xValues |> List.map (fun x -> 2.0*sin(x) + 4.0*sin(2.0*x))

 

// Populate the series

populateSeries sineSeries xValues yValues

 

// Show the chart

showForm testChart

Wow, that was pretty painless. We start by creating a ChartForm and setting a few properties like the title and size, set up the series display options, and then set up our x- and y-values of numbers according to the function y=2sin(x)+4sin(2x).  Then we populate the series with the values and show the form. You can’t get much simpler than that. I’ve reused the showForm method from my previous post.

Here’s the result:

image

Slick, eh? :)


You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

AddThis Social Bookmark Button

2 Responses to “Using the Microsoft Charting Library from F#”

  1. That’s a lot of code for such simple chart ;-)

  2. Yeah…I know :P Ideally I’d have a number of standard chart “themes” tucked away in a reusable library somewhere. The default chart look-and-feel is less than ideal, but this one from the demo is alright.

Leave a Reply