Поделиться через


Overview: Getting Started with the FSharpChart Library

Applies to: Functional Programming

Authors: Tomas Petricek and Jon Skeet

Referenced Image

Get this book in Print, PDF, ePub and Kindle at manning.com. Use code “MSDN37b” to save 37%.

Summary: This tutorial introduces the FSharpChart library, which is an F#-friendly wrapper for Microsoft Chart Controls. The examples demonstrate how to supply data for a chart using pipelining, how to update data, and how to create charts that add data points automatically.

This topic contains the following sections.

  • Introducing FSharpChart
  • Creating Simple Charts
  • Updating Charts Interactively
  • Creating Dynamic Charts
  • Summary
  • Additional Resources
  • See Also

This article is associated with Real World Functional Programming: With Examples in F# and C# by Tomas Petricek with Jon Skeet from Manning Publications (ISBN 9781933988924, copyright Manning Publications 2009, all rights reserved). No part of these chapters may be reproduced, stored in a retrieval system, or transmitted in any form or by any means—electronic, electrostatic, mechanical, photocopying, recording, or otherwise—without the prior written permission of the publisher, except in the case of brief quotations embodied in critical articles or reviews.

Introducing FSharpChart

The FSharpChart library provides a simple API for creating charts in F#. It is designed to work well with F# Interactive, but can be used to create charts in standalone F# applications as well. The library can be downloaded using the above link or as part of the source code available at the end of the article. It is implemented in a single file that can easily be loaded from other scripts (using the #load directive). The library builds on top of Microsoft Chart Controls that are available as a Windows Forms or ASP.NET components in .NET 4.0 and can be also downloaded for .NET 3.5.

The library hides most of the complexities of the underlying .NET controls. It provides a typed way to create all supported types of charts and it can create a basic chart with a single line of code. This overview demonstrates a few basic chart types and ways of passing data to the library. You will learn how to:

  • Use F# sequence expressions to plot a graph of a function and to draw a 2D curve using a line chart.

  • Create a bar chart with dates as X values and temperatures as Y values.

  • Display a chart using F# Interactive and then modify the displayed data interactively.

  • Create a chart that automatically adds new values as they become available using F# first-class events.

Although it may look like the tutorial covers a wide range of topics, these are all easily accessible features. The library is designed to make the most useful things as simple as possible. The first section discusses how to load the library from F# interactive and how to create the simplest possible chart.

Creating Simple Charts

Since the FSharpChart logic is contained in a single file, the easiest way to use it is to copy the file to the same directory as a script file and use the #load command to load it. The following snippet also opens the namespace used by the library and initializes a random number generator that is needed later:

#load "FSharpChart.fsx"
open System
open MSDN.FSharp.CHarting

let rnd = new Random()

After the namespace is opened, the charting library can be explored using IntelliSense by looking at the type FSharpChart. It encapsulates most of the charting functionality, so all supported chart types will appear as members. Methods starting with With are used to configure the look of the chart (several examples of using them can be found in Tutorial: Visualizing Stock Prices Using F# Charts).

A line chart can be created by calling the FSharpChart.Line member and giving it a list with Y values as the only argument. The listing also shows the F# Interactive output:

> FSharpChart.Line [ for x in 1.0 .. 0.1 .. 10.0 -> 
                         sin (x * 2.0) + cos x ];;
val it : ChartTypes.LineChart = (Chart)

The chart appears automatically because the charting library registers a handler with F# Interactive. Whenever F# Interactive attempts to print a chart value (such as the LineChart instance in the previous example), the library opens a new window containing the chart and tells F# Interactive to print just (Chart).

The above example used a range expression to iterate over all of the values between 1.0 and 10.0 using 0.1 as the step. The expression then generated a list of Y values, so the charting library automatically uses the index into the sequence as a label for the x-axis. To get labels with correct values for the X axis, the list comprehension can generate a list of tuples containing both X and Y:

FSharpChart.Line [ for x in 1.0 .. 0.1 .. 10.0 -> x, sin (x * 2.0) + cos x ]

The only change is that the FSharpChart.Line function is now called with an argument of type list<float * float>. The library is fully type-safe, so it knows that the Line method can either take one value (just Y) or a pair of values (X and Y). Calling the method with three-element tuples gives a compile time error because there is no overload taking such an argument. The chart generated by the last snippet is shown in Figure 1.

Figure 1. A plot of a function generated from X and Y values

Referenced Image

Using a single collection containing tuples is a one way to represent X and Y values for a chart. Another option is to create two separate collections—one for X values and one for Y values. Which of the options is better depends on the scenario. The first option is probably easier when generating data. However, when loading data from a data source that stores X values and Y values separately, the second approach may be more appropriate.

The F# charting library supports both of the options. The following snippet generates two separate lists (xs and ys) and uses them as X and Y values for a line chart:

// Calculate X and Y values for the chart
let range = [ 1.0 .. 0.02 .. 100.0 ]
let xs = [ for f in range -> (cos f) * (f / 100.0) ]
let ys = [ for f in range -> (sin (2.0 * f)) * (f / 100.0) ]

// Enter the following to F# Interactive as a separate command
FSharpChart.Line(xs, ys)

To display the chart automatically, the last line needs to be entered as a separate command to F# Interactive. Otherwise, F# Interactive doesn't print information about the created chart value and so the window doesn't appear (it can be shown explicitly, which is discussed later).

The generated X values are not sorted in an increasing order, so the snippet isn't plotting a graph of a function. Instead, the values oscillate between -1 and +1, so the snippet draws a curve in the 2D area. Figure 2 shows the resulting chart.

Figure 2. A curve drawn using a line chart from separate X- and Y-values

Referenced Image

All three examples demonstrated so far were using float data for both X and Y values. The Y values always represent some numeric value, so they are required to be a type that is convertible to float. However, the X values can be of other types as well. It can be for example DateTime for charts that show values changing with time or a string to provide a label for a value.

The next example shows how to generate a weather forecast. The sample just generates random values between 0 and 10 degrees Celsius (which may be a surprisingly accurate method for weather in the UK). The X values are strings with the name of the day and a date:

[ for i in 1. .. 7. do
     let date = DateTime.Now.AddDays(i)
     yield date.ToString("dddd, d MMMM"), rnd.Next(10) ]
|> FSharpChart.Bar

The expression that calculates the value in this snippet is slightly longer, so the sequence expression is written using a less concise form (the short form uses -> while the longer uses do and yield keywords). The body first gets a date and then yields a tuple consisting of the formatted date and a random temperature. The example also uses pipelining, which makes the code more fluent. The code starts with a calculation, which is then followed by a command to visualize it. The resulting chart is shown in Figure 3.

Figure 3. A bar chart with nicely formatted day names

Referenced Image

You saw earlier that the X value represents the formatted date and the temperature is the Y value. Unlike line charts or column charts, the bar chart draws data points from the bottom to the top, so the actual axes in the rendered chart are reversed. However, the chart still uses the convention that the Y value stands for the number that's being visualized. This makes it possible to change the example to use a column chart just by changing the chart type to FSharpChart.Column.

Updating Charts Interactively

When creating a chart using a function such as FSharpChart.Line, the library creates a new value representing a chart and F# Interactive opens the chart in a new window. Sometimes, it isn't desirable to open a whole new chart each time the data is recalculated.

For example, when just tuning some coefficients, it is easier to keep the chart visible and change it by running some command in F# Interactive. This feature can also be used to write an F# script that waits for some events (e.g., a timer) and updates the chart automatically.

A window with a chart that can be changed can be opened using the FSharpChart.Create function. The function takes a chart value (such as the one generated using FSharpChart.Line), displays it, and returns a handle that can modify the chart:

let chart1 = 
  [ for f in 1.0 .. 0.1 .. 10.0 -> sin (f * 2.0) + cos f ]
  |> FSharpChart.Line |> FSharpChart.Create

Running this snippet should create a new window with a graph of the function. It should look like the first screenshot in Figure 4. Chart.Create doesn't rely on the F# Interactive printer to show the chart, so the function can be used to display chart windows in a compiled application too.

The FSharpChart.Create function knows the type of the created chart, and the returned handle depends on the type. In this case, the result is ChartData.OneValue object that represents charts with one Y value. This object has a method SetData for updating the displayed values:

[ for f in 1.0 .. 0.1 .. 10.0 -> sin (f * 2.0) + 1.5 * cos f ]
|> chart1.SetData

[ for f in 1.0 .. 0.1 .. 10.0 -> sin (f * 2.0) + 3.0 * cos f ]
|> chart1.SetData

Running one of these two snippets should change the look of the chart. The variations are shown in Figure 4. The SetData method expects exactly the same arguments as Chart.Line. This means that it can be called with a collection of Y values, a collection containing X and Y values as a tuple, or two separate collections of X and Y values. The Chart.Create method returns different objects for different types of charts. If a particular chart type requires more than one Y value per data point, the SetData method will expect a different data format. Examples of two such charts can be found in Tutorial: Visualizing Stock Prices Using F# Charts.

Figure 4. Updating the data displayed in a chart interactively

Referenced Image

The SetData method makes it possible to replace all of the data displayed on a chart. In many cases, it is only necessary to add more data points as they are produced by some external data source. The FSharpChart library supports this scenario more directly.

Creating Dynamic Charts

Dynamic charts are useful when the program works with a data source that produces values over time. For example, this may be an input from a sensor (such as CPU usage), a model of some dynamic system, and so one. In F#, data sources like this can be represented using the IObservable<T> type.

An IObservable<T> represents a data source that other objects can register with. After the registration, the object starts receiving updates from the data source. This overview doesn't discuss observables in details, but a comprehensive explanation can be found in Chapter 16 of Real-World Functional Programming. There are also links to additional resources at the end of this article.

The example in this section uses an observable that can be constructed using basic .NET objects and F# functions. It uses a Timer object to create an IObservable<T> that produces a new value every 100 ms. Using an F# library function, the observable is turned into a new one that carries a value representing the current mouse cursor position:

open System.Windows.Forms

// Create timer that runs every 100ms
let tmr = new Timer(Interval = 100, Enabled = true)

// Create observable that reports the current X and Y values
let mouse = tmr.Tick |> Observable.map (fun _ -> 
    Control.MousePosition.X, Control.MousePosition.Y)

The snippet creates a Timer object and then uses the Tick event (exposed as an F# property) to get an observable that produces a value every 100 ms. The type of the produced value is EventArgs. This type doesn't carry any useful information, but it serves as a notification that the time interval has elapsed.

Next, the snippet calls Observable.map to create an observable carrying current X- and Y-coordinates of the mouse pointer as a tuple. The map operation takes a function that is used to transform the original observed value into a new value. The implementation above simply ignores the original value and reads the current cursor location using Control.MousePosition.

The observable represents the changing mouse cursor coordinates. It can be visualized by creating a line chart that draws a line reflecting the mouse movement. Amazingly, this requires just a single line of code:

FSharpChart.FastLine(mouse)

The example uses the FastLine chart type, which is designed for efficient drawing of lines that consist of a large number of data points (a related chart type is FastPoint). When the snippet is executed and the user tries to draw circles using a mouse, the result may look like the screenshot in Figure 5.

Figure 5. A dynamic line chart that shows mouse movement

Referenced Image

The code is so simple because all of the methods for creating charts also provide overloads that take IObservable<T> values as arguments. The overloads are essentially the same as those taking collections of values (the F# seq<T> type, which is an alias for the .NET IEnumerable<T> type).

The example above used an overload that takes a single observable generating both X and Y values as a tuple, but there are other overloads too. The simplest one takes a single observable generating the Y value. It can be used to create a chart showing the mouse's X- and Y-coordinates as two separate lines (both drawn from the left to the right):

FSharpChart.Combine
 [ FSharpChart.FastLine(mouse |> Observable.map fst)
   FSharpChart.FastLine(mouse |> Observable.map snd) ]

The listing uses Observable.map again to create two separate observables that contain just one of the coordinates. The functions fst and snd return the first and the second element of the tuple respectively, so the results are observables with X or Y coordinates, respectively. Next, the snippet creates two separate charts using FSharpChart.FastLine and combine them into a single chart using FSharpChart.Combine.

A sample result is shown in Figure 6. The movement made with the mouse was roughly circular, so the two lines correspond to the sine and cosine functions.

Figure 6. X- and Y-coordinates of the mouse drawn as separate lines

Referenced Image

This overview doesn't explain in detail how the FSharpChart.Combine function works. However, the example demonstrates that it creates a single chart with multiple data series. There are more examples of using this function (as well as other ways of combining charts) in Tutorial: Visualizing Stock Prices Using F# Charts.

Summary

This overview started by looking at how to draw a very simple line chart and a bar chart. The FSharpChart library wraps all the functionality in the type Chart, so the available charts can be easily explored using IntelliSense. The methods for creating charts have various overloads. They can be easily called when data is stored as a collection of tuples or using two collections storing X and Y values separately. The example towards the end of the overview also demonstrated overloads that take IObservable<T> values. These can be used to create charts that automatically update when new values are generated.

The easiest way to create a chart is to enter a computation that generates a chart in F# Interactive. When this is done, the chart is automatically displayed in a new window. Charts can be also created explicitly using the Chart.Create function, which displays the chart and returns a reference to it as a result. The reference can be used later to change the data displayed by the chart (using the SetData method).

Additional Resources

This tutorial discussed several examples of using the FSharpChart library, which is a perfect fit for the interactive style of programming used when exploring and analyzing data. Several advanced features and additional chart types are discussed in the following articles:

An alternative to using the FSharpChart library is to work directly with the underlying Microsoft Chart Controls library. This is mainly useful when creating standalone applications. It is also possible to create charts using Excel, gnuplot, or other third-party libraries. For more information see:

To download the code snippets shown in this article, go to https://code.msdn.microsoft.com/Chapter-6-Visualizing-Data-c68a2296

See Also

This article is based on Real World Functional Programming: With Examples in F# and C#. Book chapters related to the content of this article are:

  • Book Chapter 4: “Exploring F# and .NET libraries by example” demonstrates how to create a simple charting application from scratch. This chapter is useful for learning F# programming when focusing on working with data.

  • Book Chapter 12: “Sequence expressions and alternative workflows” explains how to work with in-memory data sets in F# using sequence expressions and higher-order functions.

  • Book Chapter 13: “Asynchronous and data-driven programming” shows how to use asynchronous workflows to obtain data from the internet, how to convert data to a structured format, and how to chart it using Excel.

  • Book Chapter 16: “Developing reactive functional programs” contains more information about the IObservable<T> type and about writing functional programs that work with dynamic data sources.

The following MSDN documents are related to the topic of this article:

  • FSharpChart library contains the latest version of the F# charting wrapper as well as additional information about the library.

  • IObservable<T> Interface provides more information about the .NET interface that we used to represent data sources that generate values over time.

  • Control.Observable Module (F#) is an F# module that contains functions for working with observables, such as the Observable.map function used in this overview.

Previous article: Tutorial: Creating Charts with Real-Time Data

Next article: Tutorial: Visualizing Stock Prices Using F# Charts