While wanting to customize the Google DataVisualization, I wanted to parameterize the constructor of a control, but for some reason it doesn't seem to work when I pass the inputDataTable
as a parameter.
Here is the default data I use :
module Example =
let headers = [|"Country"; "Population (mil)"; "Area (km2)" |]
let inputDataTable = [|
[|box "CN"; box 1234000; box 9640821|]
[|box "IN"; box 1133000; box 3287263|]
[|box "US"; box 304000; box 9629091|]
[|box "ID"; box 232000; box 1904569|]
[|box "BR"; box 187000; box 8514877|]
|]
The code to generate the chart on the webpage.
let GeoChart headers inputDataTable =
Div []
|>! OnAfterRender (fun container ->
let dataFormatter = DefaultOptions.NumberFormatter ""
let options = DefaultOptions.GeoChart
let visualization = new GeoChart(container.Dom)
let data = Data.GeoMapData headers inputDataTable
dataFormatter.format(data, 1)
visualization.draw(data, options)
)
The "expected" control.
type GoogleGeoChartViewer(headers, inputDataTable) =
inherit Web.Control()
[<JavaScript>]
override this.Body =
Google.Charts.GeoChart headers inputDataTable :> _
And a test using the basic template.
let HomePage =
let headers = Google.Example.headers
let inputDataTable = Google.Example.inputDataTable
Skin.WithTemplate "HomePage" <| fun ctx ->
[
Div [Text "HOME"]
Div [new Controls.EntryPoint()]
Div [new Controls.GoogleGeoChartViewer(headers, inputDataTable)]
Links ctx
]
When I use this code however : the geo chart does not get displayed.
But if I remove the inputDataTable
from the list of parameters and use :
let GeoChart headers =
Div []
|>! OnAfterRender (fun container ->
let dataFormatter = DefaultOptions.NumberFormatter ""
let options = DefaultOptions.GeoChart
let visualization = new GeoChart(container.Dom)
let inputDataTable = Example.inputDataTable
let data = Data.GeoMapData headers inputDataTable
dataFormatter.format(data, 1)
visualization.draw(data, options)
)
It works...
The headers
array does not pose any issue however.
Would anyone have any idea what is going on ?
Thank you for your insights.
What happens is that values passed to the constructor of a Web.Control
are server-side values that get serialized to JSON during the server-side construction of the page. However, there is no serializer for the type obj
, so the serialization of your inputDataTable
(of type obj[][]
) fails. In contrast, in the version that works, inputDataTable
is constructed on the client, so no serialization needs to take place.
If inputDataTable
does indeed need to come from the server-side, you need to use a serializable type. I don't know exactly how you fill your data object in the GeoMapData
function, but the solution is probably to define inputDataTable
as follows:
let inputDataTable = [|
("CN", 1234000, 9640821)
("IN", 1133000, 3287263)
("US", 304000, 9629091)
("ID", 232000, 1904569)
("BR", 187000, 8514877)
|]
and then on the client-side, do something like:
let data = new Base.DataTable()
data.addRows(inputDataTable) |> ignore
The trick is that WebSharper represents tuples using JavaScript arrays on the client-side. And the method addRows
, which normally takes obj[][]
, also has an overload that takes 'T[]
. This allows you to write the correctly-typed F# code above, and it compiles into the exact same JavaScript code as if you were passing an obj[][]
. This trick is used in this example (line 155).