FSharpPlot comes with the possibility to save charts as png file or copy them (also as png) to the clipboard (just right click the chart). That is fine, if you want to look at the chart on a computer screen. However if you want to produce printable reports you might want to resort to a vector file format like emf.
Microsoft Chart Controls come with the possibility to produce three kinds of emf-files. Examples how to save a plot can be found in the Samples for Chart Controls. But as it turns out copying an emf as metafile to the clipboard is not straightforward from the .Net Framework as described in KB article 323530.
Following this Knowledge Base article, I introduced a new private module to FSharpChart to be able to access the functionality from the native Windows API.
module private MetafileToClipboard = // Following http://support.microsoft.com/kb/323530 module private Intern = open System open System.Runtime.InteropServices [<DllImport("user32.dll")>] extern bool OpenClipboard(IntPtr hWndNewOwner); [<DllImport("user32.dll")>] extern bool EmptyClipboard(); [<DllImport("user32.dll")>] extern IntPtr SetClipboardData(uint32 uFormat, IntPtr hMem); [<DllImport("user32.dll")>] extern bool CloseClipboard(); [<DllImport("gdi32.dll")>] extern IntPtr CopyEnhMetaFile(IntPtr hemfSrc, IntPtr hNULL); [<DllImport("gdi32.dll")>] extern bool DeleteEnhMetaFile(IntPtr hemf); open Intern open System let PutEnhMetafileOnClipboard(hWnd:IntPtr, mf:System.Drawing.Imaging.Metafile) = let mutable bResult = false let hEMF = mf.GetHenhmetafile() if not <| hEMF.Equals(new IntPtr(0)) then let hEMF2 = CopyEnhMetaFile(hEMF, new IntPtr(0)) if not <| hEMF2.Equals(new IntPtr(0)) then if OpenClipboard(hWnd) then if EmptyClipboard() then let hRes = SetClipboardData(uint32 14 , hEMF2) let bResult = hRes.Equals(hEMF2) CloseClipboard() |> ignore DeleteEnhMetaFile(hEMF) |> ignore bResult
Using this module, copying a chart as emf from within the ChartControl class to the clipboard is as easy as
let copyEmfToClipboard (_) = use ms = new IO.MemoryStream() chart.SaveImage(ms, ChartImageFormat.EmfPlus) ms.Seek(0L, IO.SeekOrigin.Begin) |> ignore use mf = new Drawing.Imaging.Metafile(ms) MetafileToClipboard.PutEnhMetafileOnClipboard(self.Handle, mf) |> ignorelet miCopyEmf = new ToolStripMenuItem("Copy Emf to Clipboard",ShortcutKeys = (Keys.Control ||| Keys.Shift ||| Keys.C))miCopyEmf.Click.Add(copyEmfToClipboard)
I also extended the module ChartExtensions to be able to save the chart directly from a script without opening a ChartForm.
module ChartExtensions =type FSharpChart with static member SaveImage filename format (width, height) ch = let chart = new ChartControl(ch, Size = new Size(width, height)) chart.SaveImage(filename, format) ch
Using this tools you can easily create charts and paste them into Word, Powerpoint or many other applications.
The complete extended script can be downloaded here. It is based on FSharpPlot 0.2 from Don Syme, Tomas Petricek and their team.
The zip file also contains a unified diff file created with TortoiseMerge. You can see all changes with respect to version 0.2 published by Don Syme.
In addition to the functionality for copying emf’s to the clipboard there are some other minor changes:
- I found a subtle bug at line 3670 of the FSharpChart.fsx where it says
let typesToClone = [ typeof<LabelStyle>; typeof<Axis>; typeof<Grid>; typeof<TickMark> typeof<ElementPosition>; typeof<AxisScaleView>; typeof<AxisScrollBar>; ]
instead of
let typesToClone = [ typeof<DataVisualization.Charting.LabelStyle>; typeof<Axis>; typeof<Grid>; typeof<TickMark>; typeof<ElementPosition>; typeof<AxisScaleView>; typeof<AxisScrollBar>; ]
- I changed ChartData.Internal.ChartData and ChartTypes.GenericChart.Create from internal to public since the compiler complained when I tried to build the script into a dll.
Original version: FSharpPlot
My extended version: FSharpScript.zip