WorldBank

Getting and visualizing data

Charting World Bank data with F#

Comparing university enrollment using F# type provider for the WorldBank data, FunScript F# to JavaScript compiler with a type provider for TypeScript definitions and Highcharts charting library for JavaScript. Select countries you want to compare:

 
 

Chart updates automatically

×

This sample builds a dashboard for comparing university enrollment using F# type provider for the WorldBank data, FunScript F# to JavaScript compiler with a type provider for TypeScript definitions and Highcharts charting library for JavaScript.

The code is based on Tomas Petricek's sample from TechMesh 2012 called How F# Learned to Stop Worrying and Love the Data.

Type providers and jQuery

To get the data, we're using the WorldBank type provider from the F# Data library. FunScript supports the type provider and automatically translates the code to corresponding JavaScript implementation. We want to access the default "World Development Indicators" database that contains the information, but we need to configure the provider to call WorldBank asynchronously:

1: 
2: 
3: 
4: 
5: 
type WorldBank = WorldBankDataProvider<Asynchronous=true>

// Allows writing jq?name for element access
let jq(selector : string) = Globals.Dollar.Invoke selector
let (?) jq name = jq("#" + name)

In addition to referencing the WorldBank, the snippet also loaded jQuery and Highcharts, which is the charting library that we use in this sample. Now we need to connect to the WorldBank and build a list of countries that we want to offer. Here, we just pick a couple of interesting countries by hand:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
let data = WorldBank.GetDataContext()

let countries () = 
 [| data.Countries.``Arab World`` 
    data.Countries.``European Union``
    data.Countries.Australia
    data.Countries.Brazil
    data.Countries.Canada
    (more countries) |]

Building user interface

As a first step, we need to iterate over all the countries and generate check box for for each country. This can be done using the jQuery function defined earlier (which builds the HTML element) and then using standard jQuery methods like attr and append which are exposed automatically using TypeScript type provider:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
// Create check boxes for all countries
let makeCheckInfos() = 
  countries () |> Array.mapi (fun index country ->
    // Create the checkbox element
    let input = jq("<input>")
    input.attr("type", "checkbox") |> ignore
    // Create label with checkbox & text
    let label = jq("<label>")
    label.append([| box input |]) |> ignore
    label.append([| box country.Name |]) |> ignore
    // Append elements to one of 3 columns
    let panel = (index % 3) + 1
    let panelId = "#countryList" + panel.ToString()
    label.appendTo(jq(panelId)) |> ignore
    // Return the country and checkbox element
    country, input )

The function returns a collection of pairs consisting of the country and the checkbox element. This means that we can now easily iterate over the resulting collection, test which checkboxes are clicked and get the data for the corresponding countries.

Generating charts and user interaction

The rest of the code is short enough that we can show it in a single snippet. The main function contains a nested function render followed by a short block that does initialization.

In the render function, we create Highcharts chart object, iterate over all countries, test which country should be included (using standard jQuery is(":checked") call and then we get the data and render it.

Note that the render function is asynchronous (wrapped in the async { .. } block). This means that we can load data using let! without blocking the browser. In JavaScript, this is automatically translated to a callback.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
let main() = 
  let infos = makeCheckInfos()

  // Render the chart based on checkboxes
  let render () = async {
    // Create a line chart
    let opts = createEmpty<HighchartsOptions>()
    let titleOptions = createEmpty<HighchartsTitleOptions>()
    titleOptions.text <- "School enrollment, tertiary (% gross)"
    opts.title <- titleOptions
    let subTitleOptions = createEmpty<HighchartsSubtitleOptions>()
    subTitleOptions.text <- "Source: WorldBank"
    opts.subtitle <- subTitleOptions
    opts.series <- [| |]
    
    
    // Create series we want to render
    for country, check in infos do
      if check._is ":checked" then
        // Asynchronously load data without blocking
        let! vals = country.Indicators.``School enrollment, tertiary (% gross)``
        // Convert data to format required by HighCharts
        let data = 
            vals |> Seq.map (fun (k, v) ->
                let p = createEmpty<HighchartsDataPoint>() 
                p.x <- number k
                p.y <- v
                p)
            |> Seq.toArray
        // Create new data series and add it to the chart
        let series = createEmpty<HighchartsSeriesOptions>()
        series.data <- unbox data
        series.name <- country.Name
        series._type <- "line"
        
        opts.series.pushOverload2(series) |> ignore
    // Invoke constructor on a chart prototype
    let chartElement = jq?chart
    chartElement.highcharts(opts) |> ignore
  }

  // Register click handlers
  render () |> Async.StartImmediate
  infos |> Array.iter (fun (_, check) ->
    // When checkbox is clicked, start the 'render' workflow
    check.click(fun _ -> 
      render() |> Async.StartImmediate |> box) |> ignore)

As already mentioned, the render function is asynchronous. Because we configured the WorldBank type provider to expose only asynchronous API (by setting Asynchronous=true) the type of the property School enrollment, tertiary (% gross) is an asynchronous workflow Async<Indicator>. This means that it represents a computation that can be invoked using a callback. In F#, we do not have to write callbacks explicitly - we can just call the workflow using the let! keyword and the code is automatically transformed. This also allows us to use for loop rather than building a complex chain of callbacks.

Finally, when we want to invoke render() we can use standard Async.StartImmediate operation that starts the work. This runs the first part of the function until the first let! point and then runs the rest of the workflow in a callback (while the caller is free to finish whatever it was doing - like registering event handlers in our main function).

Configuring FunScript runner

To use the WorldBank type provider in FunScript, we need to tell FunScript to use the translation components that handle F# Data type proivders. Then we can use the sample runner as usual:

1: 
2: 
3: 
let components = 
  FunScript.Data.Components.DataProviders
do Runtime.Run(components=components, directory="Web")
type WorldBank = WorldBankDataProvider<...>

Full name: Program.WorldBank
type WorldBankDataProvider

Full name: FSharp.Data.WorldBankDataProvider


<summary>Typed representation of WorldBank data with additional configuration parameters</summary>
                        <param name='Sources'>The World Bank data sources to include, separated by semicolons. Defaults to "World Development Indicators;Global Financial Development". If an empty string is specified, includes all data sources.</param>
                        <param name='Asynchronous'>Generate asynchronous calls. Defaults to false</param>
val jq : selector:string -> JQuery

Full name: Program.jq
val selector : string
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = System.String

Full name: Microsoft.FSharp.Core.string
type Globals

Full name: FunScript.TypeScript.Globals
property Globals.Dollar: JQueryStatic
member JQueryStatic.Invoke : unit -> JQuery
member JQueryStatic.Invoke : element:Element -> JQuery
member JQueryStatic.Invoke : elementArray:Element array -> JQuery
member JQueryStatic.Invoke : _object:AnonymousType436 -> JQuery
member JQueryStatic.Invoke : _object:JQuery -> JQuery
member JQueryStatic.Invoke : callback:Function -> JQuery
member JQueryStatic.Invoke : selector:string * ?context:Element -> JQuery
member JQueryStatic.Invoke : html:string * attributes:Object -> JQuery
val jq : (string -> 'a)
val name : string
val data : WorldBankDataProvider<...>.ServiceTypes.WorldBankDataService

Full name: Program.data
WorldBankDataProvider<...>.GetDataContext() : WorldBankDataProvider<...>.ServiceTypes.WorldBankDataService
val countries : unit -> WorldBankDataProvider<...>.ServiceTypes.Country []

Full name: Program.countries
property WorldBankDataProvider<...>.ServiceTypes.WorldBankDataService.Countries: WorldBankDataProvider<...>.ServiceTypes.Countries
property WorldBankDataProvider<...>.ServiceTypes.Countries.Australia: WorldBankDataProvider<...>.ServiceTypes.Country


The data for country 'Australia'
property WorldBankDataProvider<...>.ServiceTypes.Countries.Brazil: WorldBankDataProvider<...>.ServiceTypes.Country


The data for country 'Brazil'
property WorldBankDataProvider<...>.ServiceTypes.Countries.Canada: WorldBankDataProvider<...>.ServiceTypes.Country


The data for country 'Canada'
data.Countries.Chile
    data.Countries.China
    data.Countries.``Czech Republic``
    data.Countries.Denmark
    data.Countries.France
    data.Countries.Greece
    data.Countries.``Low income``
    data.Countries.``High income``
    data.Countries.``United Kingdom``
    data.Countries.``United States``
val makeCheckInfos : unit -> (WorldBankDataProvider<...>.ServiceTypes.Country * JQuery) []

Full name: Program.makeCheckInfos
module Array

from Microsoft.FSharp.Collections
val mapi : mapping:(int -> 'T -> 'U) -> array:'T [] -> 'U []

Full name: Microsoft.FSharp.Collections.Array.mapi
val index : int
val country : WorldBankDataProvider<...>.ServiceTypes.Country
val input : JQuery
member JQuery.attr : attributeName:string -> string
member JQuery.attr : attributes:Object -> JQuery
member JQuery.attr : attributeName:string * value:string -> JQuery
member JQuery.attr : attributeName:string * value:float -> JQuery
member JQuery.attr : attributeName:string * func:System.Func<float,obj,obj> -> JQuery
val ignore : value:'T -> unit

Full name: Microsoft.FSharp.Core.Operators.ignore
val label : JQuery
member JQuery.append : content1:JQuery -> JQuery
member JQuery.append : content1:obj array -> JQuery
member JQuery.append : content1:Element -> JQuery
member JQuery.append : content1:Text -> JQuery
member JQuery.append : content1:string -> JQuery
member JQuery.append : func:System.Func<float,string,obj> -> JQuery
val box : value:'T -> obj

Full name: Microsoft.FSharp.Core.Operators.box
property RuntimeImplementation.WorldBank.Country.Name: string
val panel : int
val panelId : string
System.Int32.ToString() : string
System.Int32.ToString(provider: System.IFormatProvider) : string
System.Int32.ToString(format: string) : string
System.Int32.ToString(format: string, provider: System.IFormatProvider) : string
member JQuery.appendTo : target:JQuery -> JQuery
member JQuery.appendTo : target:obj array -> JQuery
member JQuery.appendTo : target:Element -> JQuery
member JQuery.appendTo : target:string -> JQuery
val main : unit -> unit

Full name: Program.main
val infos : (WorldBankDataProvider<...>.ServiceTypes.Country * JQuery) []
val render : (unit -> Async<unit>)
val async : AsyncBuilder

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.async
val opts : HighchartsOptions
val createEmpty : unit -> 'a

Full name: TypeExtensions.createEmpty
type HighchartsOptions

Full name: FunScript.TypeScript.HighchartsOptions
val titleOptions : HighchartsTitleOptions
type HighchartsTitleOptions =
  interface
    inherit HighchartsSubtitleOptions
  end

Full name: FunScript.TypeScript.HighchartsTitleOptions
property HighchartsSubtitleOptions.text: string
property HighchartsOptions.title: HighchartsTitleOptions
val subTitleOptions : HighchartsSubtitleOptions
type HighchartsSubtitleOptions

Full name: FunScript.TypeScript.HighchartsSubtitleOptions
property HighchartsOptions.subtitle: HighchartsSubtitleOptions
property HighchartsOptions.series: HighchartsSeriesOptions array
val check : JQuery
member JQuery._is : selector:string -> bool
member JQuery._is : func:System.Func<float,obj> -> bool
member JQuery._is : _obj:JQuery -> bool
member JQuery._is : elements:obj -> bool
val vals : RuntimeImplementation.WorldBank.Indicator
property WorldBankDataProvider<...>.ServiceTypes.Country.Indicators: WorldBankDataProvider<...>.ServiceTypes.Indicators


<summary>The indicators for the country</summary>
val data : HighchartsDataPoint []
module Seq

from Microsoft.FSharp.Collections
val map : mapping:('T -> 'U) -> source:seq<'T> -> seq<'U>

Full name: Microsoft.FSharp.Collections.Seq.map
val k : int
val v : float
val p : HighchartsDataPoint
type HighchartsDataPoint

Full name: FunScript.TypeScript.HighchartsDataPoint
property HighchartsDataPoint.x: float
property HighchartsDataPoint.y: float
val toArray : source:seq<'T> -> 'T []

Full name: Microsoft.FSharp.Collections.Seq.toArray
val series : HighchartsSeriesOptions
type HighchartsSeriesOptions

Full name: FunScript.TypeScript.HighchartsSeriesOptions
property HighchartsSeriesOptions.data: obj array
val unbox : value:obj -> 'T

Full name: Microsoft.FSharp.Core.Operators.unbox
property HighchartsSeriesOptions.name: string
property HighchartsSeriesOptions._type: string
member System.Collections.Generic.IList.pushOverload2 : params items:'T array -> float
val chartElement : JQuery
member JQuery.highcharts : options:HighchartsOptions -> JQuery
member JQuery.highcharts : options:HighchartsOptions * callback:System.Func<HighchartsChartObject,unit> -> JQuery
Multiple items
type Async
static member AsBeginEnd : computation:('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit)
static member AwaitEvent : event:IEvent<'Del,'T> * ?cancelAction:(unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate)
static member AwaitIAsyncResult : iar:IAsyncResult * ?millisecondsTimeout:int -> Async<bool>
static member AwaitTask : task:Task<'T> -> Async<'T>
static member AwaitWaitHandle : waitHandle:WaitHandle * ?millisecondsTimeout:int -> Async<bool>
static member CancelDefaultToken : unit -> unit
static member Catch : computation:Async<'T> -> Async<Choice<'T,exn>>
static member FromBeginEnd : beginAction:(AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg:'Arg1 * beginAction:('Arg1 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * beginAction:('Arg1 * 'Arg2 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * arg3:'Arg3 * beginAction:('Arg1 * 'Arg2 * 'Arg3 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromContinuations : callback:(('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T>
static member Ignore : computation:Async<'T> -> Async<unit>
static member OnCancel : interruption:(unit -> unit) -> Async<IDisposable>
static member Parallel : computations:seq<Async<'T>> -> Async<'T []>
static member RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:CancellationToken -> 'T
static member Sleep : millisecondsDueTime:int -> Async<unit>
static member Start : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions * ?cancellationToken:CancellationToken -> Task<'T>
static member StartChild : computation:Async<'T> * ?millisecondsTimeout:int -> Async<Async<'T>>
static member StartChildAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions -> Async<Task<'T>>
static member StartImmediate : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartWithContinuations : computation:Async<'T> * continuation:('T -> unit) * exceptionContinuation:(exn -> unit) * cancellationContinuation:(OperationCanceledException -> unit) * ?cancellationToken:CancellationToken -> unit
static member SwitchToContext : syncContext:SynchronizationContext -> Async<unit>
static member SwitchToNewThread : unit -> Async<unit>
static member SwitchToThreadPool : unit -> Async<unit>
static member TryCancelled : computation:Async<'T> * compensation:(OperationCanceledException -> unit) -> Async<'T>
static member CancellationToken : Async<CancellationToken>
static member DefaultCancellationToken : CancellationToken

Full name: Microsoft.FSharp.Control.Async

--------------------
type Async<'T>

Full name: Microsoft.FSharp.Control.Async<_>
static member Async.StartImmediate : computation:Async<unit> * ?cancellationToken:System.Threading.CancellationToken -> unit
val iter : action:('T -> unit) -> array:'T [] -> unit

Full name: Microsoft.FSharp.Collections.Array.iter
member JQuery.click : unit -> JQuery
member JQuery.click : handler:System.Func<JQueryEventObject,obj> -> JQuery
val components : InternalCompiler.CompilerComponent list

Full name: Program.components
namespace FunScript
namespace FunScript.Data
module Components

from FunScript.Data
val DataProviders : InternalCompiler.CompilerComponent list

Full name: FunScript.Data.Components.DataProviders

Live demo

Click here to open the sample live, running using JavaScript in a dialog window.

To look at the generated JavaScript, view the source of this web page and scroll to the end of the file.

Tutorials

Learn FunScript from the following introductory tutorials

Samples

Browse other amazing samples created with FunScript

Fork me on GitHub