<|start_filename|>src/FnuPlot/FnuPlot.fs<|end_filename|>
// ----------------------------------------------------------------------------
// GnuPlot.fs - Provides simple wrappers for calling gnuplot from F#
// ----------------------------------------------------------------------------
namespace FnuPlot

open System
open System.Drawing
open System.Diagnostics

// ----------------------------------------------------------------------------
// Various basic types for representing filling, ranges and for formatting
// ----------------------------------------------------------------------------

/// Represents possible fill styles of a plot. A plot can be filled using
/// a solid fill or using a specified pre-defined pattern (represented by 
/// an integer that `gnuplot` understands)
type FillStyle = 
  /// Fill the plot with a solid fill
  | Solid
  /// Fill the plot with a pre-defined gnuplot pattern
  | Pattern of int


/// [omit]
/// Represents an abstract command that sets some property
/// of the plot (and allows undoing the change)
type ICommand = 
  abstract Command : string
  abstract Cleanup : string


/// [omit]
/// Utilities for formatting values and for working with commands
module InternalUtils = 
  /// Formats a value of type option<'a> as a string
  /// using the emtpy string if the value is missing
  let formatArg (f:'T -> string) (a:option<'T>) =
    match a with 
    | None -> ""
    | Some(v) -> (f v)
      
  /// Turns an option value containing some class implementing
  /// ICommand into a list containing exaclty ICommand values
  let commandList (opt:option<'T>) =
    match opt with 
    | Some(cmd) -> [cmd :> ICommand]
    | None -> []

  /// Formats a value of type option<float>
  let formatNum = formatArg (sprintf "%f")
  
open InternalUtils

/// [omit]
module Internal = 
  /// Type that represents a range for a plot (this type is not
  /// intended to be constructed directly - use 'Range.[ ... ]` instead)
  type Range(?xspec, ?yspec) = 
    let xspec = sprintf "set xrange %s\n" (defaultArg xspec "[:]")
    let yspec = sprintf "set yrange %s\n" (defaultArg yspec "[:]")
    interface ICommand with 
      member x.Command = xspec + yspec
      member x.Cleanup = "set autoscale xy" 

  /// Type that allows elegant construction of ranges specifying both X and Y
  type RangeImplXY() = 
    member x.GetSlice(fx, tx, fy, ty) = 
      Range(sprintf "[%s:%s]" (formatNum fx) (formatNum tx),
            sprintf "[%s:%s]" (formatNum fy) (formatNum ty))

  /// Type that allows elegant construction of ranges specifying only X range
  type RangeImplX() = 
    member x.GetSlice(fx, tx) = 
      Range(xspec = sprintf "[%s:%s]" (formatNum fx) (formatNum tx))

  /// Type that allows elegant construction of ranges specifying only Y range
  type RangeImplY() = 
    member x.GetSlice(fy, ty) = 
      Range(yspec = sprintf "[%s:%s]" (formatNum fy) (formatNum ty))

/// Module with values for elegant construction of ranges. This module is automatically
/// opened, so you do not need to open it explicitly. This lets you specify ranges using
/// the following notation:
///
///     // Specify the upper bound on X axis
///     RangeX.[ .. 10.0]
///
///     // Specify the upper bound on Y axis
///     RangeY.[ .. 10.0]
///
///     // Specify both X and Y axes
///     Range.[-10.0 .. , 2.0 .. 8.0]
///
[<AutoOpen>]
module Ranges =
  open Internal
  
  /// Ranges can be constructed using the slicing syntax.
  /// For example:
  ///
  ///     Range.[-10.0 .. , 2.0 .. 8.0]
  let Range = RangeImplXY()
  /// Ranges can be constructed using the slicing syntax.
  /// For example:
  ///
  ///     RangeX.[ .. 10.0]
  let RangeX = RangeImplX()
  /// Ranges can be constructed using the slicing syntax.
  /// For example:
  ///
  ///     RangeY.[ .. 10.0]
  let RangeY = RangeImplY()

// ----------------------------------------------------------------------------
// Formatting of arguments passed to plot and constriction of various types
// of series (Lines, Histogram, ... other options TBD.)
// ----------------------------------------------------------------------------

/// [omit]
/// Module that contains formatting of various gnuplot arguments
module InternalFormat =   
  let formatNumArg s = formatArg (sprintf " %s %d" s)
  let formatTitle = formatArg (sprintf " t '%s'")
  let formatColor s = formatArg (fun (color:Color) ->
    sprintf " %s rgb '#%02x%02x%02x'" s color.R color.G color.B)
  let formatFill = formatArg (function
    | Solid -> " fs solid"
    | Pattern(n) -> sprintf " fs pattern %d" n)
  let formatRotate s = formatArg (sprintf "set %s rotate by %d" s)
  let formatTitles tics = formatArg (fun list ->
    let titles = 
      [ for t, n in Seq.zip list [0 .. (List.length list) - 1] do 
          yield sprintf "\"%s\" %d" t n ]
      |> String.concat ", " 
    sprintf "set %s (%s)" tics titles )
  let formatTimeForXaxis = formatArg (sprintf "set timefmt \"%s\" \nset xdata time")
     
open InternalFormat

/// Data that is passed as an argument to the `Series` type. For most of the types, 
/// we use one of the `Data` cases; `Function` is used when specifying function as a string.
/// You do not need to create `Data` values directly. Instead, use `Series.Line`,
/// `Series.Points`, etc. with the appropriate input type.
type Data = 
  /// Sequence of numerical values. The index of an item is the X value, the number is the Y value
  | DataY of float seq 
  /// Sequence of X and Y coordinates. The first element of the tuple is the X value, the second is the Y value
  | DataXY of (float*float) seq  
  /// Sequence of X and Y coordinates where the X is `DateTime`. The `DateTime` determines the position on the 
  /// X axis. This cannot be mixed with the other Data type options such as DataXY.
  | DataTimeY of (DateTime*float) seq 
  /// A string holding a function of X in the gnuplot format, e.g. `sin(x)`. The range of X comes from the other 
  /// Data series on the plot, or from the optional `Range` object.
  | Function of string 

/// Represents the different types or styles of series.  Roughly corresponds to the gnuPlot 'with lines', 'with points' etc.
type SeriesType =
  /// Series will be displayed as lines
  | Lines
  /// Series will be displayed as a histogram.
  | Histogram
  /// Series will be displayed as points
  | Points
  /// Series will be displayed as impulses
  | Impulses

/// Represents a series of data for the `gp.Plot` function
/// The type can be constructed directly (by setting the `plot` parameter
/// to the desired series type) or indirectly using static
/// members such as 'Series.Histogram'
type Series(plot, data, ?title, ?lineColor, ?weight, ?fill) = 
  let plotText =
    match plot with
      | Lines -> "lines"
      | Histogram -> "histogram"
      | Points -> "points"
      | Impulses -> "impulses"
  let cmd = 
    (match data with 
     | DataY _ -> " '-' using 1 with " + plotText
     | DataXY _ | DataTimeY _ -> " '-' using 1:2 with " + plotText
     | Function f -> f + " with " + plotText)
      + (formatTitle title) 
      + (formatNumArg "lw" weight)
      + (formatColor "lc" lineColor) 
      + (formatFill fill)

  /// Returns the data of the series
  member x.Data = data
  /// Returns the formatted gnuplot command
  member x.Command = cmd

  /// Creates a line data series for a plot of y values
  static member Lines(data, ?title, ?lineColor, ?weight) = 
    Series(Lines, DataY data, ?title=title, ?lineColor=lineColor, ?weight=weight)
  /// Creates a line data series for a plot of xy values
  static member Lines(data, ?title, ?lineColor, ?weight) = 
    Series(Lines, DataXY data, ?title=title, ?lineColor=lineColor, ?weight=weight)
  /// Creates a line data series for a function
  static member Lines(data, ?title, ?lineColor, ?weight) = 
    Series(Lines, Function data, ?title=title, ?lineColor=lineColor, ?weight=weight)
  /// Creates a line data series for a plot of DateTime and y values.
  static member Lines(data, ?title, ?lineColor, ?weight) = 
    Series(Lines, DataTimeY data, ?title=title, ?lineColor=lineColor, ?weight=weight)

  /// Creates a points data series for a plot of y values
  static member Points(data, ?title, ?lineColor, ?weight) = 
    Series(Points, DataY data, ?title=title, ?lineColor=lineColor, ?weight=weight)
  /// Creates a points data series for a plot of xy values
  static member Points(data, ?title, ?lineColor, ?weight) = 
    Series(Points, DataXY data, ?title=title, ?lineColor=lineColor, ?weight=weight)
  /// Creates a points data series for a function
  static member Points(data, ?title, ?lineColor, ?weight) = 
    Series(Points, Function data, ?title=title, ?lineColor=lineColor, ?weight=weight)
  /// Creates a points data series for a plot of DateTime and y values
  static member Points(data, ?title, ?lineColor, ?weight) = 
    Series(Points, DataTimeY data, ?title=title, ?lineColor=lineColor, ?weight=weight)

  /// Creates an impulse data series for a plot of y values
  static member Impulses(data, ?title, ?lineColor, ?weight) = 
    Series(Impulses, DataY data, ?title=title, ?lineColor=lineColor, ?weight=weight)
  /// Creates an impulse data series for a plot of xy values
  static member Impulses(data, ?title, ?lineColor, ?weight) = 
    Series(Impulses, DataXY data, ?title=title, ?lineColor=lineColor, ?weight=weight)
  /// Creates an impulse data series for a function
  static member Impulses(data, ?title, ?lineColor, ?weight) = 
    Series(Impulses, Function data, ?title=title, ?lineColor=lineColor, ?weight=weight)
  /// Creates an impulse data series for a plot of DateTime and y values
  static member Impulses(data, ?title, ?lineColor, ?weight) = 
    Series(Impulses, DataTimeY data, ?title=title, ?lineColor=lineColor, ?weight=weight)

  /// Creates a histogram data series for a plot  
  static member Histogram(data, ?title, ?lineColor, ?weight, ?fill) = 
    Series(Histogram, DataY data, ?title=title, ?lineColor=lineColor, ?weight=weight, ?fill=fill)

/// Represents a style of a plot (can be passed to the Plot method
/// to set style for single plotting or to the Set method to set the
/// style globally)
type Style(?fill) = 
  let cmd = 
    [ formatFill fill; ]
    |> List.filter (String.IsNullOrEmpty >> not)
    |> List.map (sprintf "set style %s\n")
    |> String.concat ""
  interface ICommand with 
    member x.Command = cmd
    member x.Cleanup = "" // not implemented
  

/// Various output types that can be specified to gnuplot. Currently, the
/// wrapper supports the following options:
///
///  - `OutputType.X11` for charts that are opened in a new window
///  - `OutputType.Png` for saving charts to a PNG file.
///  - `OutputType.Eps` for saving charts to an EPS file.
type OutputType = 
  /// Creates charts in a new window
  | X11
  /// Creates charts in a new Qt window on OS X
  | Qt
  /// Saves charts to a specified PNG file
  | Png of string
  /// Saves charts to a specified EPS file
  | Eps of string

/// The type can be used to specify output type for `gnuplot`. The type
/// combines a value of `OutputType` with additional parameters such as fonts.
/// For example, to create a PNG, you can use:
///
///     gp.Set(output = Output(Png("/temp/test.png")))
///
type Output(output:OutputType, ?font) =
  interface ICommand with
    member x.Command = 
      let font = font |> formatArg (sprintf " font '%s'")
      match output with 
      | X11 -> "set term x11" + font
      | Qt -> "set terminal qt" + font
      | Png(s) -> sprintf "set term png%s\nset output '%s'" font s
      | Eps(s) -> sprintf "set term postscript eps enhanced%s\nset output '%s'" font s
    member x.Cleanup = "set term x11"

/// Used to specify titles for the X and Y axes. In addition to the text for the labels,
/// you can also specify the rotation of the labels. For example:
/// 
///     // specify rotated titles for x axis
///     Titles(x=["one"; "two"], xrotate=-70)
///
type Titles(?x, ?xrotate, ?y, ?yrotate) = 
  let cmd =
    [ formatRotate "xtic" xrotate, "set xtic rotate by 0"
      formatRotate "ytic" yrotate, "set ytic rotate by 0"
      formatTitles "xtics" x, "set xtics auto"
      formatTitles "ytics" y, "set ytics auto" ]
    |> List.filter (fun (s, _) -> s <> "")
  interface ICommand with
    member x.Command = 
      cmd |> List.map fst |> String.concat "\n"
    member x.Cleanup =
      cmd |> List.map snd |> String.concat "\n"


/// Used to specify datetime format for the x and y axes, if they contain time data.
/// The parameter `format` is a gnuplot time format; for more information
/// [refer to the gnuplot documentation](http://gnuplot.sourceforge.net/docs_4.2/node274.html).
type TimeFormatX(?format) = 
  let cmd =
    [ formatTimeForXaxis format , "set xdata"]
    |> List.filter (fun (s, _) -> s <> "")
  interface ICommand with
    member x.Command = 
      cmd |> List.map fst |> String.concat "\n"
    member x.Cleanup =
      cmd |> List.map snd |> String.concat "\n"

/// [omit]
/// The below two values control Gnuplot's display of DateTimes. They must match.
[<AutoOpen>]
module timeFormatting = 
  let selectedTimeGnuplotFormat = """%d-%b-%Y-%H:%M:%S""" //format that Gnuplot will expect. see http://gnuplot.sourceforge.net/docs_4.2/node274.html
  let dateTimeToSelectedGnuplotFormat (t:DateTime) = t.ToString("dd-MMM-yyyy-HH:mm:ss") //converts a DateTime to a string in the above format. See http://msdn.microsoft.com/en-us/library/8kb3ddd4(v=vs.110).aspx


// ----------------------------------------------------------------------------
// The main type that wraps the gnuplot process
// ----------------------------------------------------------------------------

/// The main type of the library. It provides a wrapper for calling gnuplot from F#. 
/// Plots are drawn using the `Plot` function and can be created using the `Series` type. 
/// For example:
///
///     // Create gnuplot process
///     let gp = GnuPlot()
///
///     // Plot a function specified as a string
///     gp.Plot("sin(x)")
///
///     // Create a simple line plot
///     gp.Plot(Series.Lines [2.0; 1.0; 2.0; 5.0])   
///
///     // Create a histogram plot drawn using blue color 
///     gp.Plot(Series.Histogram(lineColor=Color.Blue, data=[2.0; 1.0; 2.0; 5.0]))
///
type GnuPlot private (actualPath:string) =
  // Start the gnuplot process when the class is created
  let gp = 
    new ProcessStartInfo
      (FileName = actualPath, UseShellExecute = false, Arguments = "", 
       RedirectStandardError = true, CreateNoWindow = true, 
       RedirectStandardOutput = true, RedirectStandardInput = true) 
    |> Process.Start
  

  [<Literal>]
  let DEBUGGING = false

  // Provide event for reading gnuplot messages
  let msgEvent = 
    Event.merge gp.OutputDataReceived gp.ErrorDataReceived
      |> Event.map (fun de -> de.Data)
  do 
    if DEBUGGING then
        msgEvent.Add (fun output -> System.Diagnostics.Debug.Print(output + "\n"))
    gp.BeginOutputReadLine()  
    gp.BeginErrorReadLine()
    gp.EnableRaisingEvents <- true

  // Send command to gnuplot process
  let sendCommand(str:string) =
    gp.StandardInput.Write(str + "\n")
    if DEBUGGING then
        System.Diagnostics.Debug.Print (">>" + str + "\n")

  /// Create a new instance of the `GnuPlot` object. This starts the `gnuplot`
  /// process in the background. The optional parameter `path` can be used to
  /// specify `gnuplot` location if it is not available in `PATH`.
  new(?path:string) = new GnuPlot(actualPath=defaultArg path "gnuplot")

  // We want to dipose of the running process when the wrapper is disposed
  // The following bits implement proper 'disposal' pattern
  member private x.Dispose(disposing) =
    try
      gp.Kill()  
      if disposing then gp.Dispose()
    with
      _->()

  /// [omit]
  override x.Finalize() =
    try
      x.Dispose(false)
    with
      _->()
    
  interface System.IDisposable with
    member x.Dispose() = 
      x.Dispose(true)
      System.GC.SuppressFinalize(x)

  // Write data to the gnuplot command line
  member private x.WriteData(data:Data) = 
    match data with 
    | DataY data ->
      for yPt in data do 
        x.SendCommand(string yPt)
      x.SendCommand("e")
    | DataXY data ->
      for (xPt,yPt) in data do 
        x.SendCommand((string xPt) + " " + (string yPt))
      x.SendCommand("e")
    | DataTimeY data ->
      for (timePt,yPt) in data do 
        x.SendCommand( (dateTimeToSelectedGnuplotFormat timePt) + " " + (string yPt))
      x.SendCommand("e")
    | _ -> ()
    
  // --------------------------------------------------------------------------
  // Public members that can be called by the user
  
  /// Send a string command directly to the gnuplot process.
  member x.SendCommand(str) = sendCommand(str)
  
  /// Set a style or a range of the gnuplot session. For example:
  ///
  ///     // set fill style to a numbered pattern
  ///     gp.Set(style = Style(fill = Pattern(3)))
  ///  
  ///     // set the X range of the output plot to [-10:]
  ///     gp.Set(range = RangeX.[-10.0 .. ]
  ///
  member x.Set(?style:Style, ?range:Internal.Range, ?output:Output, ?titles:Titles, ?TimeFormatX:TimeFormatX) = 
    let commands = List.concat [ commandList output; commandList style; commandList range; commandList titles ; commandList TimeFormatX]
    for cmd in commands do
      //printfn "Setting:\n%s" cmd.Command
      x.SendCommand(cmd.Command)

  /// Reset style or range set previously (used mainly internally)
  member x.Unset(?style:Style, ?range:Internal.Range) = 
    let commands = List.concat [ commandList style; commandList range ]
    for cmd in commands do
      if "" <> cmd.Cleanup then x.SendCommand(cmd.Cleanup)
  
  /// Draw a plot specified as a string. Range and style can
  /// be specified using optional parameters. For example:
  ///
  ///     // draw sin(x) function
  ///     gp.Plot("sin(x)")
  ///
  member x.Plot(func:string, ?style, ?range, ?output, ?titles) = 
    x.Plot([Series.Lines(func)], ?style=style, ?range=range, ?output=output, ?titles=titles)

  /// Draw a plot of a single data series. Range and style can 
  /// be specified using optional parameters. For example:
  ///
  ///     // Create a simple line plot
  ///     gp.Plot(Series.Lines [2.0; 1.0; 2.0; 5.0],
  ///             range = RangeY.[-1.0 ..])   
  ///    
  member x.Plot(data:Series, ?style, ?range, ?output, ?titles) = 
    x.Plot([data], ?style=style, ?range=range, ?output=output, ?titles=titles)

  /// Draw a plot consisting of multiple data series. Range and 
  /// style can be specified using optional parameters. For example:
  ///
  ///     // Create a simple line plot
  ///     gp.Plot
  ///      [ Series.Lines(title="Lines", data=[2.0; 1.0; 2.0; 5.0])
  ///        Series.Histogram(fill=Solid, data=[2.0; 1.0; 2.0; 5.0]) ]
  ///    
  member x.Plot(data:seq<Series>, ?style:Style, ?range:Internal.Range, ?output:Output, ?titles:Titles) = 
    //Set up the plot format. 
    match (Seq.head data).Data with
        | DataTimeY _ ->    //Plotting time ranges requires special setup of the time format
                            let timeFmt = Some (TimeFormatX(selectedTimeGnuplotFormat))
                            x.Set(?style=style, ?range=range, ?output=output, ?titles=titles, ?TimeFormatX = timeFmt)
        | _ ->  //normal non-time plot
                x.Set(?style=style, ?range=range, ?output=output, ?titles=titles)
    
    //plot each Series
    let cmd = 
      "plot \\\n" +
      ( [ for s in data -> s.Command ]
        |> String.concat ", \\\n" )
    x.SendCommand(cmd)
    for s in data do
      x.WriteData(s.Data)
    //undo plot format setup
    x.Unset(?style=style, ?range=range)



<|start_filename|>docs/content/tutorial.fsx<|end_filename|>
(*** hide ***)
#I "../../bin"
let pathOpt = 
  [ // MAINTAINERS: Feel free to add other paths where Gnuplot lives on your machine..
    @"C:\Programs\Data\gnuplot\bin\gnuplot.exe"
    @"C:\Program Files (x86)\gnuplot\bin\gnuplot.exe"]
  |> Seq.tryFind System.IO.File.Exists
let path = defaultArg pathOpt "gnuplot"
(**
Getting started with FnuPlot 
============================

FnuPlot is a lightweight wrapper for the [gnuplot](http://www.gnuplot.info/) charting and 
visualization library. Since gnuplot is cross-platform (works on Mac, Linux and Windows),
you can use this library on all of the platforms mentioned above.

FnuPlot provides two features on top of gnuplot. First, it hides the gnuplot process, so you 
do not have to start and control the process; you can use the `GnuPlot` type and call it using
`SendCommand`. Second, FnuPlot implements a simple domain-specific language for building a 
number of common chart types.

Installing and configuring FnuPlot
----------------------------------
 * [Install gnuplot](http://sourceforge.net/projects/gnuplot/files/) if you haven't already.

 * The library is available as [FnuPlot on NuGet](https://www.nuget.org/packages/FnuPlot). To get the
   code also, [get the code from GitHub](https://github.com/fsprojects/FnuPlot/).

 * Alternatively: Because FnuPlot is implemented in a single source file, you can instead just copy the 
   `FnuPlot.fs` file into your project. A robust way to do this is to use [Paket](http://fsprojects.github.io/Paket/) 
   for managing your references and use [GitHub reference](http://fsprojects.github.io/Paket/github-dependencies.html)
   specified in your `paket.dependencies` file using `github fsprojects/FnuPlot src/FnuPlot/FnuPlot.fs`.

 * Once you have the reference, you can either add `FnuPlot.dll` to your reference (when using
   a compiled project) or use `#load "FnuPlot.fs"` to load the FnuPlot file obtained using 
   Paket. Alternatively, you can use `#r` if you're referencing FnuPlot using NuGet package:
*)
#r "FnuPlot.dll"
open FnuPlot
open System
open System.Drawing
(**
The access to gnuplot is managed through the `GnuPlot` type. When creating an instance, you
can specify the path to the `gnuplot` executable. If you do not pass a path explicitly, then
FnuPlot assumes that you have gnuplot in your `PATH` variable:
*)
let gp_default = new GnuPlot()
let gp = new GnuPlot(path)
(**
The `Set` method on the `GnuPlot` object provides a way to configure `gnuplot`. Here, we specify
that plots should appear in a new window (using X11) and we also specify the default font:
*)
gp.Set(output = Output(X11, font="arial"))
(**

Plotting functions and line charts
----------------------------------

The `Plot` method provided by `GnuPlot` has a number of overloads. You can specify a function
to be drawn (as a string), you can specify a single data series or you can specify multiple
series (which can also use different chart types).

The following shows how to call `Plot` with a function specified as a string:
*)
gp.Plot(Series.Lines "sin(x)")
(**
<img src="img/tutorial-1.png" alt="Sin function" />

This creates a chart with a single series, created (implicitly) by specifying the function
as a string. If you want to create other kinds of series, you need to use the `Series` type.
The following uses `Series.Lines` to create a single line chart from a specified list of
data points:
*)
gp.Plot(Series.Lines [2.0; 1.0; 2.0; 5.0]) 
(**
<img src="img/tutorial-2.png" alt="Line series" />

The `Series.Lines` method takes a number of optional parameters that you can use to specify
the title, colour and other properties of the line chart. You can also make the call using
the pipelining operator:
*)
Series.Lines
  ( title="Some plot", lineColor = Color.OliveDrab, 
    weight = 3, data = [2.0; 1.0; 2.0; 5.0])
|> gp.Plot
(**
<img src="img/tutorial-3.png" alt="Line series with configuration" />

When creating a line series from data points consisting of both X and Y values, you can also use
the `Series.Lines` method with a series of that type (note that FnuPlot also supports time-series data sets, which are 
discussed below).
*)
Series.Lines( [0.0,1.0; 0.2,2.0; 2.0,1.5; 2.1,3.0] , title = "Some xy plot")
|> gp.Plot
(**
<img src="img/tutorial-4.png" alt="Line series with X and Y values" />
*)
(**
Plotting histograms and time-series
-----------------------------------

Another kind of chart for which FnuPlot provides an easy-to-use wrapper is histogram. This can
be created using the `Series.Histogram` function. Again, we can specify additional properties
using a number of optional parameters:
*)
Series.Histogram
  ( [2.0; 5.0; 2.0; 1.0], title = "Some plot", fill = Solid, 
    lineColor = Color.SteelBlue, weight = 3) 
|> gp.Plot
(**
<img src="img/tutorial-5.png" alt="Histogram" />

As already mentioned, FnuPlot also supports time-series charts. These can be created by passing
values as pairs of `DateTime` and `float`:
*)
let values =
  [ DateTime(2014,1,1), 1.0
    DateTime(2014,1,5), 3.0
    DateTime(2014,2,1), 1.5 ]

Series.Lines(values, title = "Some time series" )
|> gp.Plot
(**
<img src="img/tutorial-6.png" alt="Time-series" />

Combining multiple chart series
-------------------------------

As mentioned earlier, the `Plot` function has a number of overloads. We have already
seen an overload that takes a string (to plot a specified function) and an overload that
takes a single series. However, you can also call `Plot` with a collection of series. 
In that case, gnuplot will render multiple series into a single chart.

The following (slightly silly) demo combines the `sin(x*3)+3)` function specified as
a string together with line chart and histogram created from values:
*)
[ Series.Lines
    ( "sin(x*3) + 3", title = "Sinus", 
      lineColor = Color.Goldenrod, weight = 3)
  Series.Lines
    ( [2.0; 1.0; 2.0; 5.0], title = "Line", 
      lineColor = Color.OliveDrab, weight = 3)
  Series.Histogram
    ( [2.0; 5.0; 2.0; 1.0], title = "Hist", fill = Solid, 
      lineColor = Color.SteelBlue, weight = 3) ]
|> gp.Plot
(**
<img src="img/tutorial-7.png" alt="Combining multiple charts" />

Configuring ranges and styles
-----------------------------

So far, we always specified properties of individual chart series locally using optional
parameters of a `Series.<Some>` method. However, the `gp.Plot` method also takes a number
of additional parameters that can be used to specify properties of the whole chart. Most
importantly, you can use these to specify ranges:
*)
gp.Plot(
  range = RangeY.[-10.0 .. 10.0 ],
  data = Series.Lines [2.0; 1.0; 2.0; 5.0])  
(**
<img src="img/tutorial-8.png" alt="Combining multiple charts" />

To specify a range, you can use `RangeY` (as shown in the code snippet) or `RangeX` to set
the range for one of the two axes. If you want to configure both ranges, you can use the
`Range` type. For example, to specify X axis range from -10 to 10 and Y axis range from 
-1 to 1, you can write `Range.[-10.0 .. 10.0, -1.0 .. 1.0]`.

The next example shows how to specify fill style for the whole chart, so that you do not have
to repeat this for every single histogram that is being combined:
*)
gp.Plot(
  style = Style(fill=Solid),
  range = Range.[-0.5 .. 3.7, 0.0 .. 6.0],
  data = 
    [ Series.Histogram([2.0; 1.0; 2.0; 5.0], lineColor = Color.OliveDrab)
      Series.Histogram([1.5; 2.0; 2.5; 4.5], lineColor = Color.SteelBlue) ])
(**
<img src="img/tutorial-9.png" alt="Combining multiple charts" />

Saving charts to a file
-----------------------
At the beginning of the tutorial, we used the `gp.Set` method to specify the output kind for
gnuplot. We used `Output(X11, font="arial")` to use the X11 server, which means that all 
charts are created in a gnuplot window. 

If you want to save charts to a file, then you can use the `Png` output. The following 
(slightly longer) example also demonstrates other optional parameters of the `gp.Set` 
method. This gives you another way to specify the range and style - this time, the configuration
will apply to all created charts:
*)
// Set global properties using the 'Set' method
gp.Set(style = Style(fill = Solid), range = Range.[-0.5 .. 3.7, 0.0 .. 6.0])
// Specify the 'Png' output kind
gp.Set(output = Output(Png("/home/tomas/Temp/test1.png")))

// Create a plot (which will be saved to a file)
gp.Plot
  [ Series.Histogram([2.0; 1.0; 2.0; 5.0], lineColor = Color.OliveDrab)
    Series.Histogram([1.5; 2.0; 2.5; 4.5], lineColor = Color.SteelBlue) ]

// Reset the configuration back to X11 plots
gp.Set(output = Output(X11, font="arial"))

(**

Impulses and Points
-------------------
Using the `Series.Impulses` and `Series.Points` methods we can plot impulses and points.
*)

[ Series.Impulses( "besj0(x)*0.12e1", title = "Plot as Impulses")
  Series.Points( "(x**besj0(x))-2.5", title = "Plot as Points")]
|> gp.Plot

(**
<img src="img/tutorial-10.png" alt="Impulses and Points" />
*)


<|start_filename|>docs/content/worldbank.fsx<|end_filename|>
(*** hide ***)
#I "../../bin/"
#I "../../packages/FSharp.Data/lib/net40"
let pathOpt = 
  [ // MAINTAINERS: Feel free to add other paths where Gnuplot lives on your machine..
    "C:/Programs/Data/gnuplot/bin/gnuplot.exe" 
    "/usr/local/bin/gnuplot"
    "C:/Program Files (x86)/gnuplot/bin/gnuplot.exe"]
  |> Seq.tryFind System.IO.File.Exists
let path = defaultArg pathOpt "gnuplot"
(**
Visualizing population using WorldBank
=====================================

In this walkthrough, we look at a larger example of data analysis that uses FnuPlot to
visualize the results. We use the [WorldBank](http://data.worldbank.org) as the data source
and we plot how the total population in the countries of the world changed between 1990 and 2005. The
example is inspired by the [Asynchrnous and data-driven programming chapter](http://manning.com/petricek)
from Real-World Functional Programming.

Downloading data from the WorldBank
-----------------------------------

In the chapter used as the inspiration, the data download is done using the LINQ to XML library
and the `XDocument` type. Here, we use F# type providers instead. We use XML type provider to
parse the data and WorldBank type provider to find the ID of the required indicator.

First, we connect to the WorldBank to get the ID and write a function that sends HTTP request
to download one page of data for the specified indicator:
*)
#r "FSharp.Data.dll"
open FSharp.Data

// Get indicator code from WorldBank
let wb = WorldBankData.GetDataContext()
let indCode = wb.Countries.World.Indicators.``Population, total``.IndicatorCode
let root = "http://api.worldbank.org/countries/indicators/"
let key = "<KEY>"

/// Asynchronously downloads the population 
/// data for the specified year & page
let asyncGetPage year page = 
  let range = sprintf "%d:%d" year year
  Http.AsyncRequestString
    ( root + indCode, 
      query=[ "api_key", key; "per_page", "100"; 
              "date", range; "page", string page ] )
(**
Now, we want to save a sample response to a local file and use the XML type provider
to parse the response. The easiest way to do this is to use `File.WriteAllText` to 
save a sample result, say for `asyncGetPage 2000 1` to a local file. Here, we use 
`worldbank.xml` in the current folder, so we can load the type provider as follows:
*)
#r "System.Xml.Linq.dll"
type WB = XmlProvider<"worldbank.xml">
(**
The generated type `WB` lets us parse the responses returned by `asyncGetPage`. To
download all data, we first need to request the first page, so that we know how many
pages in total are there. Then we can download the rest of the pages in parallel. This
is done in the following `async` function:
*)
let asyncGetPopulation year = async {
  // Download the first page & get total no. of pages
  let! first = asyncGetPage year 1
  let parsed = WB.Parse(first)

  // Download the remaining pages in parallel
  let! rest = 
    [ for pg in 2 .. parsed.Pages -> async {
        let! response = asyncGetPage year pg 
        return WB.Parse(response) } ]
    |> Async.Parallel
  // Return all pages
  return Seq.append [parsed] rest }
(**
The return type of `asyncGetPopulation` is `Async<seq<WB.Data>>`, which means that it 
_asynchronously_ returns a collection of `<data>` elements from the XML document. Now,
we use `Async.Parallel` again to download data for 3 different years:
*)
let allData = 
  [ for y in [ 1990; 2000; 2005 ] ->
      asyncGetPopulation y ]
  |> Async.Parallel
  |> Async.RunSynchronously
  |> Seq.concat
(**
The operation creates a list of asynchronous operations, composes them so that they are
performed asynchronously, runs them and then it concatenates the results, so that we get
just a single collection of `<data>` nodes.

Processing downloaded data
--------------------------

Before we can do the visualization, we need to find the countries for which we have all
the data. To do that, we build two lookup tables. One that maps country code and year to
a value and another, which maps country code to a country name. 
*)
// Lookup table mapping IDs to country names
let idToName = 
  [ for d in allData do
      for item in d.Datas do
        yield item.Country.Id, item.Country.Value ]
  |> dict

// Lookup table mapping ID and Year to a value
let idAndYearToValue =
  [ for d in allData do
      for item in d.Datas do
        if item.Value.IsSome then
          yield (item.Country.Id, item.Date), item.Value.Value ]
  |> dict

(**
Now that we have the two dictionaries, we can create collections of numbers in a format
that can be passed to `gnuplot`. First of all, we want to display data only for some of
the countries. We choose countries and regions that have data for all 3 years and have 
population over 500 million. This will give us large countries and aggregated regions that
WorldBank monitors as a whole.

Next, we need to pick the names of the regions/countries (for axis labels) and a list of
collections with numerical values for each year:
*)

/// Returns true if data is available for all 3 monitored years
/// and the country or region has over 500 million
let isVisibleCountry id =
  [1990; 2000; 2005] |> Seq.forall (fun y ->
    match idAndYearToValue.TryGetValue( (id, y) ) with
    | true, v -> v > int64 5e8
    | _ -> false )

/// Names of visible countries/regions for axis labels
let names = 
  [ for KeyValue(id, name) in idToName do
      if isVisibleCountry id then yield name ]

/// List of tuples consisting of a year and data for the year
let stats = 
  [ for y in [1990; 2000; 2005] do
     let data =
      [ for KeyValue(id, name) in idToName do
          if isVisibleCountry id then 
            yield float (idAndYearToValue.[id, y]) ]
     yield y, data ] 
(**
Visualizing data with gnuplot
-----------------------------

To create a plot using `gnuplot`, we first need to reference the `FnuPlot.dll`
library, open the required namespace and create `GnuPlot` instance. Here, we pass
the path to the `gnuplot` executable as an argument. If it is available directly in
your path, you can just call `new GnuPlot()` without parameters:
*)
#r "FnuPlot.dll"
open FnuPlot
open System.Drawing

let gp = new GnuPlot(path)
(**
To make the chart nicer, we configure a number of options first. We specify the output
type to X11 (to create chart in a window) and a font. Then we also set the range and 
the style of histogram bars (filled). Finally, we add titles and specify that the chart
should be rotated (from the bottom to the top):
*)
gp.Set
  ( output = Output(X11, font = "arial"),
    style = Style(Solid), 
    range = RangeY.[ 5e8 .. 75e8 ],
    titles = Titles(x = names, xrotate = -90) )
(**
We want to create histogram using custom colors, so we'll zip the data with the following
list of colors:
*)
let colors = [ Color.OliveDrab; Color.SteelBlue; Color.Goldenrod ]
(**
Now we have all we need to create the chart. We use `gp.Plot` to display the plot. As an 
argument, we give it a collection of series created using `Series.Histogram`. When 
createing a series, we specify the `lineColor` parameter to get the required color (one
color for each year):
*)        
gp.Plot
  [ for (y, values), clr in Seq.zip stats colors ->
      Series.Histogram
        ( data = values, title = string y, lineColor = clr) ]
(**
<img src="img/worldbank.png" alt="WorldBank data visualization" />
*)
