LChart: displaying charts in F# – Part III

-

The last post is here. In this post we’ll look at how things work un­der the cover and to why I came to be­lieve that they should­n’t work this way.

First of all each one of the func­tions to cre­ate charts looks some­thing like this:

static member bar (?y,?x, ?isValueShownAsLabel, ?markerSize, ?markerStyle, ?color, ?xname, ?yname, ?seriesName, ?title, ?drawingStyle) =
    let c = Create (SeriesChartType.Bar, x, y, isValueShownAsLabel, markerSize, markerStyle, color, xname, yname, seriesName, title)
    c.Series.[0].["DrawingStyle"] <- defaultArg drawingStyle (c.Series.[0].["DrawingStyle"])
    c

This re­turns an ob­ject of type lc (this is the type of c’). But lc in­her­its from Chart which is the main class in the Microsoft Chart Controls.

type lc() =
    inherit Chart()

I should have said at the start that you need to ref­er­ence such con­trols.

#r "System.Windows.Forms.DataVisualization.dll"
open System.Collections
open System.Drawing
open System.IO
open System.Windows.Forms
open System.Windows.Forms.DataVisualization.Charting
open System.Windows.Forms.DataVisualization.Charting.Utilities
open System

It is con­ve­nient that the re­turn value of each func­tion is a sub­type of Chart. You can then go and cus­tomize this ob­ject as you like (i.e. chang­ing graph­i­cal ap­pear­ance) be­fore call­ing dis­play. Given the Chart in­her­its from Control you can code the dis­play method as fol­lows:

let display (c:lc) =
    let copy () =
        let stream = new MemoryStream()
        c.SaveImage(stream, Imaging.ImageFormat.Bmp)
        let bmp = new Bitmap(stream)
        Clipboard.SetDataObject(bmp)
    c.KeyDown.Add(fun e -> if e.Control = true && e.KeyCode = Keys.C then copy ())
    let pressToCopy = "(press CTRL+C to copy)"
    let name = if c.Titles.Count = 0 then sprintf "%s %s " "lc" pressToCopy else sprintf "%s %s " c.Titles.[0].Text  pressToCopy
    let f = new Form(Text = name, Size = new Size(800,600), TopMost = true)
    c.Dock <- DockStyle.Fill
    f.Controls.Add(c)
    f.Show()
    c

Apart from a bit of con­vo­lu­tions to im­ple­ment a Copy func­tion, this just put the Chart con­trol on a newly cre­ated Form. The Create method called in­side bar looks like the fol­low­ing.

static let Create (chartType, x, y, isValueShownAsLabel, markerSize, markerStyle, color, xname, yname, seriesName, title) =
    let c = new lc()
    let a = new ChartArea()
    let s = new Series()
    s.ChartType <- chartType
    c.ChartAreas.Add(a)
    c.Series.Add(s)
    match x, y with
        | Some(x), None     -> failwith "You cannot pass only x to a chart drawing function"
        | Some(x), Some(y)  -> s.Points.DataBindXY(x, [|y|])
        | None, Some(y)     -> s.Points.DataBindY([|y|])
        | None, None        -> ()
    s.IsValueShownAsLabel <- defaultArg isValueShownAsLabel s.IsValueShownAsLabel
    s.MarkerSize <- defaultArg markerSize s.MarkerSize
    s.MarkerStyle <- defaultArg markerStyle s.MarkerStyle
    s.Color <- defaultArg color s.Color
    a.AxisX.MajorGrid.Enabled <- false
    a.AxisY.MajorGrid.Enabled <- false
    match xname with
    | Some(xname) ->
        a.AxisX.Title <- xname
        a.AxisX.TitleFont <- axisFont
        a.AxisX.TitleForeColor <- axisColor
    | _ -> ()
    match yname with
    | Some(yname) ->
        a.AxisY.Title <- yname
        a.AxisY.TitleFont <- axisFont
        a.AxisY.TitleForeColor <- axisColor
    | _ -> ()
    match seriesName with
    | Some(seriesName) -> s.Name <- seriesName
    | _ -> ()
    match title with
    | Some(title) ->
        let t = c.Titles.Add(title: string)
        t.Font <- titleFont
        t.ForeColor <- titleColor
    | _ -> ()
    c

Pretty stan­dard im­per­a­tive code here. Creating a chart and as­sign­ing its prop­er­ties. Read the doc­u­men­ta­tion for the Chart Control to un­der­stand what I’m do­ing here. I’m not even sure I re­mem­ber what I’m do­ing. Given that we have our own lc class (which is a type of Chart) we can then over­ride the +’ op­er­a­tor and ++’ op­er­a­tor to do what is needed.

static member (+) (c1:lc, c2:lc) =
    let c = copyChart(c1)
    c1.ChartAreas |> Seq.iter (fun a -> addAreaAndSeries c a c1.Series)
    let lastArea = c.ChartAreas |> Seq.nth ((c.ChartAreas |> Seq.length) - 1)
    c2.Series |> Seq.iter(fun s -> c.Series.Add(copySeries s c lastArea.Name))
    let l = c.Legends.Add("")
    l.Font <- legendFont
    c
static member (++) (c1:lc, c2:lc) =
    let c = copyChart(c1)
    c1.ChartAreas |> Seq.iter (fun a -> addAreaAndSeries c a c1.Series)
    let lastArea = c.ChartAreas |> Seq.nth ((c.ChartAreas |> Seq.length) - 1)
    addAreaAndSeries c c2.ChartAreas.[0] c2.Series
    let firstArea = c.ChartAreas |> Seq.nth ((c.ChartAreas |> Seq.length) - 1)
    c2.ChartAreas |> Seq.skip 1 |> Seq.iter (fun a -> addAreaAndSeries c a c2.Series)
    c    

Apart from some other util­ity func­tions, this is how it all works. Why do I say that it is wrong? It is my opin­ion that the right way to do it would be to use +, ++ and all the lc.XXX func­tions to cre­ate an ob­ject model that is com­pletely in­de­pen­dent from the Microsoft Chart con­trols. The dis­play method would then trans­late it to the ap­pro­pri­ate dis­playable Chart. It would work like a com­piler trans­lat­ing to IL and then a Jitter pro­duc­ing na­tive code. This would:

Why I have not done it? I did­n’t know that was the right de­sign un­til I used the wrong one. Now that I know, I have no time to do it.

Tags

6 Comments

Comments

Edmondo Pentangelo

2010-02-20T01:55:04Z

Congratulazioni
Benvenuto a Londra :)
Ci siamo in­con­trati al PDC meet the ex­pert in 2008. Abbiamo par­lato a lungo di F#.
Io la­voro a Morgan Stanley a Canary Wharf, e prob­a­bil­mente tu la­vor­erai nel palazzo di fronte al mio. Incredibile co­in­ci­denza.
Vieni al meetup April F#unctional Londoners Meetup” il 21 Aprile ?
http://​www.meetup.com/​FShar…

Non saro’ an­cora a Londra il 21 Aprile. Mi trasferisco a Maggio. Teniamoci in con­tatto. E’ una cit­ta’ nuova per noi e potremmo avere bisogno di qualche con­siglio Italiano’. Come posso rag­giungerti via email?

Edmondo Pentangelo

2010-02-21T01:26:06Z

Fammi uno squillo quando sei in zona. Ho gi­rato tutti i ris­toranti Italiani di Canary Wharf quando ho com­in­ci­ato a la­vo­rare qua, ti posso in­di­care i migliori e quali evitare. Io vivo a 10 minuti da Cabot Square e da queste parti ci sono ton­nel­late di ap­par­ta­menti in af­fitto e in ven­dita a pochi passi dal­l’uf­fi­cio (Evita i servizi pub­blici la mat­tina, sono stile gi­ap­ponese).
Se mi mandi un mes­sag­gio pri­vato sul pro­filo di LinkedIn che ti ho mandato prima, ti giro la mia email.
Saluti
Edmondo

Steffen Forkmann

2010-02-25T15:00:19Z

Hi Luca,
are you plan­ning to put this pro­ject on github?
Regards,
Steffen

I did­n’t plan to. You can do that if you want to.
BTW: I usu­ally post my pro­jects on Code Gallery. Should I use github in­stead?

razor electric review

2011-11-12T18:46:52Z

An all round good blog!!