Search code examples
f#fsharpchart

how to run .fsx in fsi


Exploring F# with FSharp.Charting I thought I would start with a simple 'hello world' but it leaves me with more questions then lines of code.

#load @"..\packages\FSharp.Charting.0.90.14\FSharp.Charting.fsx"
open FSharp.Charting
let chart = Chart.Line([ for x in 0 .. 10 -> x, x*x ])
chart.ShowChart()
chart.SaveChartAs(@"C:\Temp\chart.png",ChartTypes.ChartImageFormat.Png)

This works in interactive window in VS, but what I want to do is execute this script from the cmd line (using fsi.exe). I made an association with fsx files to fsi, but when I execute it it opens fsi but no chart is created. What do I need to do?


Solution

  • Short answer: add the following line at the end of your program:

    System.Windows.Forms.Application.Run()
    

    Long answer:

    The chart does get created, but it immediately disappears, because your program immediately exits, right after creating the chart. This does not happen in the F# Interactive window in Visual Studio, because the F# interactive window doesn't close immediately after executing your program - it just hangs out there, waiting for you to submit more code for execution.

    In order to make your program not exit immediately, you could implement some waiting mechanism, such as waiting for a set amount of time (see System.Threading.Thread.Sleep) or waiting for the user to press Enter (via stdin.ReadLine()), etc.

    However, this won't actually help you, because there is the next problem: the chart is drawn via Windows Forms, which relies on the message loop running - otherwise the window can't receive messages, and so can't event paint itself.

    FSI does have its own built-in event loop, and this is how your program works under VS. However, if you implement a "waiting" mechanism (e.g. stdin.ReadLine()), this event loop will be blocked - won't be able to pump messages. Therefore, the only sane way to keep your program from exiting, while not interfering with the functioning of the chart window, is to start your own event loop. And this is exactly what Application.Run() does.

    Saving to disk without displaying:

    (in response to comment)

    From what I understand, the FSharp.Charting library was intended as a quick-and-dirty way to display charts on the screen, primary use case being exploring datasets live within F# Interactive. More specifically, some key properties of the Chart object, such as ChartAreas and Series are not initialized upon chart creation, but only when it is shown on the screen (see source code), and without these properties the chart remains empty.

    Short of submitting a pull request to the library, I recommend dropping down to the underlying System.Windows.Forms.DataVisualization.Charting.Chart:

    open System.Windows.Forms.DataVisualization.Charting
    
    let ch = new Chart()
    ch.ChartAreas.Add( new ChartArea() )
    let s = new Series( ChartType = SeriesChartType.Line )
    s.Points.DataBind( [for x in 1..10 -> x, x*x], "Item1", "Item2", "" )
    ch.Series.Add s;
    
    ch.SaveImage(@"C:\Temp\chart.png", System.Drawing.Imaging.ImageFormat.Png)