LChart: displaying charts in F# – Part III - Luca Bolognese

LChart: displaying charts in F# – Part III

Luca -

☕ 3 min. read

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:

  • Make pos­si­ble to do more in­ter­est­ing com­po­si­tions of graphs. Now I’m very con­strained in what I can do by the fact that I’m work­ing di­rectly with Chart ob­jects
  • Make pos­si­ble to change the back­end. Using some­thing dif­fer­ent than Microsoft Chart con­trols to draw the chart

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.

6 Comments

Comments

Edmondo Pentangelo

2010-02-20T01:55:04Z

Congratulazioni
Benvenuto a Londra :)
Ci siamo incontrati al PDC meet the expert in 2008. Abbiamo parlato a lungo di F#.
Io lavoro a Morgan Stanley a Canary Wharf, e probabilmente tu lavorerai nel palazzo di fronte al mio. Incredibile coincidenza.
Vieni al meetup "April F#unctional Londoners Meetup" il 21 Aprile ?
http://www.meetup.com/FShar...

Non saro' ancora a Londra il 21 Aprile. Mi trasferisco a Maggio. Teniamoci in contatto. E' una citta' nuova per noi e potremmo avere bisogno di qualche consiglio 'Italiano'. Come posso raggiungerti via email?

Edmondo Pentangelo

2010-02-21T01:26:06Z

Fammi uno squillo quando sei in zona. Ho girato tutti i ristoranti Italiani di Canary Wharf quando ho cominciato a lavorare qua, ti posso indicare i migliori e quali evitare. Io vivo a 10 minuti da Cabot Square e da queste parti ci sono tonnellate di appartamenti in affitto e in vendita a pochi passi dall'ufficio (Evita i servizi pubblici la mattina, sono stile giapponese).
Se mi mandi un messaggio privato sul profilo 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 planning to put this project on github?
Regards,
Steffen

I didn't plan to. You can do that if you want to.
BTW: I usually post my projects on Code Gallery. Should I use github instead?

razor electric review

2011-11-12T18:46:52Z

An all round good blog!!

0 Webmentions

These are webmentions via the IndieWeb and webmention.io.