LChart: displaying charts in F# – Part III
Luca Bolognese -The last post is here. In this post we’ll look at how things work under the cover and to why I came to believe that they shouldn’t work this way.
First of all each one of the functions to create charts looks something 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 returns an object of type lc (this is the type of ‘c’). But lc inherits 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 reference such controls.
#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 convenient that the return value of each function is a subtype of Chart. You can then go and customize this object as you like (i.e. changing graphical appearance) before calling display. Given the Chart inherits from Control you can code the display method as follows:
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 convolutions to implement a Copy function, this just put the Chart control on a newly created Form. The Create method called inside bar looks like the following.
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 standard imperative code here. Creating a chart and assigning its properties. Read the documentation for the Chart Control to understand what I’m doing here. I’m not even sure I remember what I’m doing. Given that we have our own lc class (which is a type of Chart) we can then override the ‘+’ operator and ‘++’ operator 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 utility functions, this is how it all works. Why do I say that it is wrong? It is my opinion that the right way to do it would be to use ’+’, ‘++’ and all the lc.XXX functions to create an object model that is completely independent from the Microsoft Chart controls. The display method would then translate it to the appropriate displayable Chart. It would work like a compiler translating to IL and then a Jitter producing native code. This would:
- Make possible to do more interesting compositions of graphs. Now I’m very constrained in what I can do by the fact that I’m working directly with Chart objects
- Make possible to change the backend. Using something different than Microsoft Chart controls to draw the chart
Why I have not done it? I didn’t know that was the right design until I used the wrong one. Now that I know, I have no time to do it.
Tags
- FSHARP
6 Comments
Comments
Edmondo Pentangelo
2010-02-20T01:55:04ZCongratulazioni
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…
lucabol
2010-02-20T03:47:56ZNon 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:06ZFammi 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:19ZHi Luca,
are you planning to put this project on github?
Regards,
Steffen
lucabol
2010-02-25T17:43:43ZI 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:52ZAn all round good blog!!