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:
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.
November 3rd, 2008 at 5:09 pm
That’s a lot of code for such simple chart
November 3rd, 2008 at 5:37 pm
Yeah…I know
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.