Revision 1143fc9798ec6e5f58164caffdaca7178402e80b authored by Alain Mebsout on 16 November 2022, 15:55:34 UTC, committed by Alain Mebsout on 18 November 2022, 17:03:24 UTC
1 parent 9bd2352
plot.ml
(*****************************************************************************)
(* *)
(* Open Source License *)
(* Copyright (c) 2021 Nomadic Labs. <contact@nomadic-labs.com> *)
(* Copyright (c) 2021 Ilias Garnier. <igarnier@protonmail.com> *)
(* *)
(* Permission is hereby granted, free of charge, to any person obtaining a *)
(* copy of this software and associated documentation files (the "Software"),*)
(* to deal in the Software without restriction, including without limitation *)
(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *)
(* and/or sell copies of the Software, and to permit persons to whom the *)
(* Software is furnished to do so, subject to the following conditions: *)
(* *)
(* The above copyright notice and this permission notice shall be included *)
(* in all copies or substantial portions of the Software. *)
(* *)
(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*)
(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *)
(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *)
(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*)
(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *)
(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *)
(* DEALINGS IN THE SOFTWARE. *)
(* *)
(*****************************************************************************)
(*
Module copied from https://github.com/igarnier/prbnmcn-gnuplot
*)
(* Small interface to gnuplot *)
let unit_interval x = x >= 0.0 && x <= 1.
type color = {r : float; g : float; b : float}
module Color = struct
type t = color
let rgb r g b =
if not (unit_interval r && unit_interval g && unit_interval b) then
invalid_arg "rgb" ;
{r; g; b}
let red = {r = 1.0; g = 0.0; b = 0.0}
let green = {r = 0.0; g = 1.0; b = 0.0}
let blue = {r = 0.0; g = 0.0; b = 1.0}
let black = {r = 0.0; g = 0.0; b = 0.0}
end
type pointtype =
| Dot
| Plus
| Cross
| Star
| Square
| Box
| Circle
| Disk
| Delta
| Delta_solid
module Pointtype = struct
type t = pointtype
let square = Square
let dot = Dot
let circle = Circle
let delta = Delta
let delta_solid = Delta_solid
let plus = Plus
let cross = Cross
let star = Star
let box = Box
let disk = Disk
end
type fill_style =
| Empty
| Solid of {density : float; transparent : bool}
| Pattern of {code : int; transparent : bool}
type border_style = Noborder
type fill = {
fill_style : fill_style;
border : border_style;
fill_color : Color.t option;
}
module Fill = struct
type t = fill
let default = {fill_style = Empty; border = Noborder; fill_color = None}
let set_empty fill = {fill with fill_style = Empty}
let set_solid ~density ~transparent fill =
if not (unit_interval density) then invalid_arg "set_solid" ;
{fill with fill_style = Solid {density; transparent}}
let set_pattern ~code ~transparent fill =
{fill with fill_style = Pattern {code; transparent}}
let set_color color fill = {fill with fill_color = Some color}
end
type style = {shape_type : shape_type; color : Color.t option}
and shape_type =
| Point of {ptyp : Pointtype.t option; psize : float option}
| Circle of {fill : Fill.t option; radius : float}
module Style = struct
type t = style
let default = {shape_type = Point {ptyp = None; psize = None}; color = None}
let set_point ?ptyp ?psize style =
{style with shape_type = Point {ptyp; psize}}
let set_circle ?fill ~radius style =
{style with shape_type = Circle {fill; radius}}
let set_color color style = {style with color = Some color}
end
type tics_position =
| Tics_increment of float
| Tics of {start : float; incr : float; stop : float option}
type tics = {
border : bool;
mirror : bool;
in_ : bool;
rotate_by : float option;
position : tics_position option;
logscale : bool;
}
module Tics = struct
type t = tics
let default =
{
border = true;
mirror = true;
in_ = true;
rotate_by = None;
position = None;
logscale = false;
}
let set_border border tics = {tics with border}
let set_mirror mirror tics = {tics with mirror}
let set_outwards inwards tics = {tics with in_ = not inwards}
let set_rotate ~degrees tics = {tics with rotate_by = Some degrees}
let set_position ?start ?incr ?stop tics =
match (start, incr, stop) with
| None, None, None -> {tics with position = None}
| None, Some incr, None -> {tics with position = Some (Tics_increment incr)}
| Some start, Some incr, stop ->
{tics with position = Some (Tics {start; incr; stop})}
| _ -> invalid_arg "Tics.set_position"
let set_logscale logscale tics = {tics with logscale}
end
type r1 = {x : float} [@@unboxed]
type r2 = {x : float; y : float}
type r3 = {x : float; y : float; z : float}
type r4 = {w : float; x : float; y : float; z : float}
let r1 x = {x}
let r2 x y = {x; y}
let r3 x y z = {x; y; z}
let r4 w x y z = {w; x; y; z}
let tup_r2 (x, y) = {x; y}
let tup_r3 (x, y, z) = {x; y; z}
module Data = struct
(* This could be refined later into something smarter (allowing to replot the same
data without having to duplicate it in the script for instance) *)
type 'a t = 'a Seq.t
let of_seq s = s
let of_array = Array.to_seq
let of_list = List.to_seq
end
type histogram_options = {
color : color option;
bins : int option;
binwidth : float option;
}
type 'data with_metadata = {data : 'data; style : style; legend : string option}
type _ axes =
| Dim2_axes : {
xaxis : string;
yaxis : string;
xtics : Tics.t;
ytics : Tics.t;
}
-> r2 axes
| Dim3_axes : {
xaxis : string;
yaxis : string;
zaxis : string;
xtics : Tics.t;
ytics : Tics.t;
ztics : Tics.t;
}
-> r3 axes
type _ spec =
| Scatter : {
data : 'dim Data.t with_metadata;
error_bars : r2 Seq.t option;
(** Error bars for 2d point plots, always none for 3d plots *)
}
-> 'dim spec
| Histogram : {
data : r1 Seq.t;
options : histogram_options;
legend : string option;
}
-> r2 spec
| Line : {
data : 'dim Data.t with_metadata;
with_points : bool;
error_bars : r2 Seq.t option;
(** Error bars for 2d line plots, always none for 3d plots *)
}
-> 'dim spec
| Boxes : {
data : (string * float) Data.t with_metadata;
box_width : float option;
fill : Fill.t;
}
-> r2 spec
(*
- error bars: with scatter and with lines
- box: only with lines
*)
type plot =
| Plot : {
axes : 'dim axes;
plots : 'dim spec list;
title : string option;
}
-> plot
module Scatter = struct
let points_2d ~points ?(style = Style.default) ?legend ?error_bars () =
Scatter {data = {data = points; style; legend}; error_bars}
let points_3d ~points ?(style = Style.default) ?legend () =
Scatter {data = {data = points; style; legend}; error_bars = None}
end
module Line = struct
let compute_with_points style legend with_points =
match (with_points, style.shape_type) with
| None, Point {ptyp = Some _; psize = _} -> true
| None, _ -> false
| Some false, Point {ptyp = Some _; psize = _} ->
let legend = Option.value ~default:"none" legend in
let msg =
Printf.sprintf
"line_2d: style inconsistent with with_points = false (legend = \
\"%s\")"
legend
in
invalid_arg msg
| Some false, _ -> false
| Some true, _ -> true
let line_2d ~points ?(style = Style.default) ?legend ?with_points ?error_bars
() =
let with_points = compute_with_points style legend with_points in
Line {data = {data = points; style; legend}; with_points; error_bars}
let line_3d ~points ?(style = Style.default) ?legend ?with_points () =
let with_points = compute_with_points style legend with_points in
Line {data = {data = points; style; legend}; with_points; error_bars = None}
let to_2d (ys : r1 Seq.t) : r2 Seq.t =
let c = ref 0.0 in
Seq.map
(fun ({x = y} : r1) ->
let x = !c in
c := x +. 1. ;
{x; y})
ys
let line ~(points : r1 Seq.t) ?style ?legend ?with_points ?error_bars () =
let points = to_2d points in
line_2d ~points ?style ?legend ?with_points ?error_bars ()
end
module Histogram = struct
type options = histogram_options = {
color : color option;
bins : int option;
binwidth : float option;
}
let hist ~points ?color ?bins ?binwidth ?legend () =
match (bins, binwidth) with
| None, None ->
let bins = Some 20 in
Histogram {data = points; options = {color; bins; binwidth}; legend}
| _ -> Histogram {data = points; options = {color; bins; binwidth}; legend}
end
module Bar = struct
(* type bars = r1 Data.t * Fill.t
*
* let bars data ?(fill = Fill.default) = (data, fill)
*
* let boxes ~xtics ~bars ?(boxwidth = 0.5) ?legend () =
* if bars = [] then invalid_arg "boxes: empty data" ;
* if xtics = [] then invalid_arg "boxes: empty xtics" ;
* let xtics_count = List.length xtics in
* List.iter
* (fun data ->
* if List.(length (of_seq data)) <> xtics_count then
* invalid_arg "boxes: data length inconsistent with xtics")
* bars *)
let simple data ?fill ?box_width ?legend () =
let default =
Fill.(
default |> set_color Color.red |> set_pattern ~code:1 ~transparent:false)
in
let fill = Option.value ~default fill in
Boxes {data = {data; style = Style.default; legend}; fill; box_width}
end
module Axes = struct
let _2 ~xaxis ~yaxis ~xtics ~ytics = Dim2_axes {xaxis; yaxis; xtics; ytics}
let _3 ~xaxis ~yaxis ~zaxis ~xtics ~ytics ~ztics =
Dim3_axes {xaxis; yaxis; zaxis; xtics; ytics; ztics}
end
let plot2 ~xaxis ~yaxis ?xtics ?ytics ?title plots =
let xtics = Option.value ~default:Tics.default xtics in
let ytics = Option.value ~default:Tics.default ytics in
Plot {axes = Axes._2 ~xaxis ~yaxis ~xtics ~ytics; plots; title}
let plot3 ~xaxis ~yaxis ~zaxis ?xtics ?ytics ?ztics ?title plots =
let xtics = Option.value ~default:Tics.default xtics in
let ytics = Option.value ~default:Tics.default ytics in
let ztics = Option.value ~default:Tics.default ztics in
Plot {axes = Axes._3 ~xaxis ~yaxis ~zaxis ~xtics ~ytics ~ztics; plots; title}
(* ------------------------- *)
(* Producing gnuplot scripts *)
let sf = Printf.sprintf
let concat l = String.concat "\n" l
module GP_data : sig
type t = private string
val _1d : data_name:string -> r1 Seq.t -> t
val _2d : data_name:string -> r2 Seq.t -> t
val _3d : data_name:string -> r3 Seq.t -> t
val _4d : data_name:string -> r4 Seq.t -> t
val _s3d : data_name:string -> (string * r3) Seq.t -> t
end = struct
type t = string
let _1d ~data_name points =
let acc = ref [] in
Seq.iter (fun ({x} : r1) -> acc := string_of_float x :: !acc) points ;
concat ([sf "$%s << EOD" data_name] @ List.rev !acc @ [sf "EOD"])
let _2d ~data_name points =
let acc = ref [] in
Seq.iter (fun ({x; y} : r2) -> acc := sf "%f %f" x y :: !acc) points ;
concat ([sf "$%s << EOD" data_name] @ List.rev !acc @ [sf "EOD"])
let _3d ~data_name points =
let acc = ref [] in
Seq.iter (fun ({x; y; z} : r3) -> acc := sf "%f %f %f" x y z :: !acc) points ;
concat ([sf "$%s << EOD" data_name] @ List.rev !acc @ [sf "EOD"])
let _4d ~data_name points =
let acc = ref [] in
Seq.iter
(fun ({w; x; y; z} : r4) -> acc := sf "%f %f %f %f" w x y z :: !acc)
points ;
concat ([sf "$%s << EOD" data_name] @ List.rev !acc @ [sf "EOD"])
let _s3d ~data_name points =
let acc = ref [] in
Seq.iter
(fun ((s, {x; y; z}) : string * r3) ->
acc := sf "%s %f %f %f" s x y z :: !acc)
points ;
concat ([sf "$%s << EOD" data_name] @ List.rev !acc @ [sf "EOD"])
end
module GP_style : sig
type t = private string
val linecolor : color -> t
val fillcolor : color -> t [@@ocaml.warning "-32"]
val fill : fill -> t
val render : style -> t
end = struct
type t = string
let ptcode s =
match s with
| Dot -> 0
| Plus -> 1
| Cross -> 2
| Star -> 3
| Square -> 4
| Box -> 5
| Disk -> 6
| Circle -> 7
| Delta -> 8
| Delta_solid -> 9
(* UI from the 70's *)
(* Note: this mapping should depend on the terminal type ... *)
let pointtype (s : pointtype option) (sz : float option) =
match (s, sz) with
| None, None -> ""
| None, Some sz -> sf "pointsize %f" sz
| Some pt, None -> sf "pointtype %d" (ptcode pt)
| Some pt, Some sz -> sf "pointtype %d pointsize %f" (ptcode pt) sz
let linecolor {r; g; b} =
let r = int_of_float (255. *. r) in
let g = int_of_float (255. *. g) in
let b = int_of_float (255. *. b) in
sf "lc rgb '0x%02X%02X%02X'" r g b
let fillcolor {r; g; b} =
let r = int_of_float (255. *. r) in
let g = int_of_float (255. *. g) in
let b = int_of_float (255. *. b) in
sf "fillcolor rgb '0x%02X%02X%02X'" r g b
let fill (f : fill) =
let fill_style_str =
match f.fill_style with
| Empty -> sf "fs empty"
| Solid {density; transparent = true} ->
sf "fs transparent solid %f" density
| Solid {density; transparent = false} -> sf "fs solid %f" density
| Pattern {code; transparent = true} ->
sf "fs transparent pattern %d" code
| Pattern {code; transparent = false} -> sf "fs pattern %d" code
in
let fill_color_str = Option.fold ~none:"" ~some:fillcolor f.fill_color in
String.concat " " [fill_style_str; fill_color_str]
let render (style : style) =
let clr_str = Option.fold ~none:"" ~some:linecolor style.color in
match style.shape_type with
| Point {ptyp; psize} ->
let pointtype_str = pointtype ptyp psize in
String.concat " " [pointtype_str; clr_str]
| Circle {fill = None; _} -> clr_str
| Circle {fill = Some fillspec; _} ->
let fill_str = fill fillspec in
let clr_str = Option.fold ~none:"" ~some:linecolor style.color in
String.concat " " [clr_str; fill_str]
end
module GP_subcommand : sig
type t = private string
val scatter_2d : data_name:string -> style -> legend_opt:string option -> t
val y_error_bars : data_name:string -> style -> legend_opt:string option -> t
val y_error_lines : data_name:string -> style -> legend_opt:string option -> t
val scatter_3d : data_name:string -> style -> legend_opt:string option -> t
val lines_2d : data_name:string -> style -> legend_opt:string option -> t
val lines_3d : data_name:string -> style -> legend_opt:string option -> t
val linespoints_2d :
data_name:string -> style -> legend_opt:string option -> t
val linespoints_3d :
data_name:string -> style -> legend_opt:string option -> t
val histogram :
data_name:string -> histogram_options -> legend_opt:string option -> t
val boxes : data_name:string -> legend_opt:string option -> fill:Fill.t -> t
end = struct
type t = string
let print_legend legend_opt =
Option.fold ~none:"notitle" ~some:(fun x -> sf "title '%s'" x) legend_opt
let scatter_2d ~data_name s ~legend_opt =
let sty = (GP_style.render s :> string) in
match s.shape_type with
| Point _ ->
sf
"$%s using 1:2 %s with points %s"
data_name
(print_legend legend_opt)
sty
| Circle {radius; _} ->
sf
"$%s using 1:2:(%f) %s with circles %s"
data_name
radius
(print_legend legend_opt)
sty
let y_error_bars ~data_name s ~legend_opt =
let sty = (GP_style.render s :> string) in
sf
"$%s using 1:2:3:4 %s with yerrorbars %s"
data_name
(print_legend legend_opt)
sty
let y_error_lines ~data_name s ~legend_opt =
let sty = (GP_style.render s :> string) in
sf
"$%s using 1:2:3:4 %s with yerrorlines %s"
data_name
(print_legend legend_opt)
sty
let scatter_3d ~data_name s ~legend_opt =
let sty = (GP_style.render s :> string) in
match s.shape_type with
| Point _ ->
sf
"$%s using 1:2:3 %s with points %s"
data_name
(print_legend legend_opt)
sty
| Circle {radius; _} ->
sf
"$%s using 1:2:3:(%f) %s with circles %s"
data_name
radius
(print_legend legend_opt)
sty
let lines_2d ~data_name s ~legend_opt =
let sty = (GP_style.render s :> string) in
sf "$%s using 1:2 %s with lines %s" data_name (print_legend legend_opt) sty
let lines_3d ~data_name s ~legend_opt =
let sty = (GP_style.render s :> string) in
sf
"$%s using 1:2:3 %s with lines %s"
data_name
(print_legend legend_opt)
sty
let linespoints_2d ~data_name s ~legend_opt =
let sty = (GP_style.render s :> string) in
sf
"$%s using 1:2 %s with linespoints %s"
data_name
(print_legend legend_opt)
sty
let linespoints_3d ~data_name s ~legend_opt =
let sty = (GP_style.render s :> string) in
sf
"$%s using 1:2:3 %s with linespoints %s"
data_name
(print_legend legend_opt)
sty
let hist_preprocessing_text {bins; binwidth; _} =
match (bins, binwidth) with
| None, None -> ""
| Some nbins, None -> sf "bins=%d" nbins
| None, Some width -> sf "bins binwidth=%f" width
| Some nbins, Some width -> sf "bins=%d binwidth=%f" nbins width
let histogram ~data_name preprocessing_opts ~legend_opt =
sf
"$%s using 1 %s %s with boxes %s"
data_name
(print_legend legend_opt)
(hist_preprocessing_text preprocessing_opts)
(Option.fold
~none:""
~some:(fun c -> (GP_style.linecolor c :> t))
preprocessing_opts.color)
let boxes ~data_name ~legend_opt ~fill =
sf
"$%s using 2:3:4:xtic(1) %s with boxes %s"
data_name
(print_legend legend_opt)
(GP_style.fill fill :> string)
end
module GP_command : sig
type t = private string
val plot : GP_subcommand.t list -> t
val splot : GP_subcommand.t list -> t
end = struct
type t = string
let concat_subcommands plots =
List.map (fun (x : GP_subcommand.t) -> (x :> string)) plots
|> String.concat ", "
let splot plots = sf "splot %s" (concat_subcommands plots)
let plot plots = sf "plot %s" (concat_subcommands plots)
end
module GP_script : sig
type t = private string
val render : plot -> t
val render_matrix : title:string -> plot option array array -> t
end = struct
type t = string
let set_xlabel l = sf "set xlabel \"%s\"" l
let set_ylabel l = sf "set ylabel \"%s\"" l
let set_zlabel l = sf "set zlabel \"%s\"" l
let set_title t = sf "set title \"%s\"" t
let tics_spec ~with_logscale (tics : Tics.t) =
let {border; mirror; in_; rotate_by; position; logscale} = tics in
let border = if border then "border" else "axis" in
let mirror = if mirror then "mirror" else "nomirror" in
let in_ = if in_ then "in" else "out" in
let rotate_by =
match rotate_by with
| None -> "norotate"
| Some degrees -> sf "rotate by %f" degrees
in
let position =
match position with
| None -> "autofreq"
| Some (Tics_increment incr) -> string_of_float incr
| Some (Tics {start; incr; stop}) -> (
let pp_float x =
(* prevent parsing errors by gnuplot, see doc for [xtics series] *)
if x < 0.0 then sf "0-%f" (-.x) else string_of_float x
in
match stop with
| None -> sf "%s, %s" (pp_float start) (pp_float incr)
| Some stop -> sf "%s, %s, %f" (pp_float start) (pp_float incr) stop)
in
let logscale =
if with_logscale then if logscale then "logscale" else "nologscale"
else ""
in
sf "%s %s %s %s %s %s" border mirror in_ rotate_by position logscale
let branch_version_check version script_passed script_failed =
concat
([sf "if (GPVAL_VERSION >= %s) {" version]
@ script_passed @ ["} else {"] @ script_failed @ ["}"])
let set_tics tics_kind (tics : Tics.t) =
branch_version_check
"5.2"
[sf "set %s %s" tics_kind (tics_spec ~with_logscale:true tics)]
[sf "set %s %s" tics_kind (tics_spec ~with_logscale:false tics)]
let set_xtics (tics : Tics.t) = set_tics "xtics" tics
let set_ytics (tics : Tics.t) = set_tics "ytics" tics
let set_ztics (tics : Tics.t) = set_tics "ztics" tics
let namegen =
let x = ref (-1) in
fun () ->
incr x ;
sf "name_%d" !x
let set_fixed_z_range data =
let fulldata = namegen () in
concat
([sf "set print $%s" fulldata]
@ List.map (fun x -> sf "print $%s" x) data
@ ["set print"]
@ [sf "stats $%s using 3 nooutput" fulldata]
@ ["set zrange [STATS_min:STATS_max]"])
let rec seq_map2 (seq1 : 'a Seq.t) (seq2 : 'b Seq.t) f : _ Seq.t =
fun () ->
match (seq1 (), seq2 ()) with
| Nil, Nil -> Nil
| Cons (x, tl1), Cons (y, tl2) -> Cons (f x y, seq_map2 tl1 tl2 f)
| _ -> invalid_arg "seq_map2: uneven length"
let make_error_bars (data : r2 Seq.t) (error_bars : r2 Seq.t) =
seq_map2 data error_bars (fun ({x; y} : r2) ({x = y1; y = y2} : r2) ->
r4 x y (y +. y1) (y -. y2))
let scatter (type dim) (axes : dim axes)
({data; style; legend} : dim Data.t with_metadata)
(error_bars : r2 Seq.t option) =
match axes with
| Dim2_axes _ -> (
let data_name = namegen () in
match error_bars with
| None ->
let data_block = GP_data._2d ~data_name data in
let command_chunk =
GP_subcommand.scatter_2d ~data_name style ~legend_opt:legend
in
(`Data data_block, `Command command_chunk, data_name)
| Some error_bars ->
let data_with_errors =
GP_data._4d ~data_name (make_error_bars data error_bars)
in
let command_chunk =
GP_subcommand.y_error_bars ~data_name style ~legend_opt:legend
in
(`Data data_with_errors, `Command command_chunk, data_name))
| Dim3_axes _ ->
let data_name = namegen () in
let data_block = GP_data._3d ~data_name data in
let command_chunk =
GP_subcommand.scatter_3d ~data_name style ~legend_opt:legend
in
(`Data data_block, `Command command_chunk, data_name)
let line (type dim) (axes : dim axes)
({data; style; legend} : dim Data.t with_metadata) with_points error_bars
=
match axes with
| Dim2_axes _ -> (
let data_name = namegen () in
match error_bars with
| None ->
let data_block = GP_data._2d ~data_name data in
let command_chunk =
if with_points then
GP_subcommand.linespoints_2d ~data_name style ~legend_opt:legend
else GP_subcommand.lines_2d ~data_name style ~legend_opt:legend
in
(`Data data_block, `Command command_chunk, data_name)
| Some error_bars ->
(* assert (not with_points) ; *)
let data_with_errors =
GP_data._4d ~data_name (make_error_bars data error_bars)
in
let command_chunk =
GP_subcommand.y_error_lines ~data_name style ~legend_opt:legend
in
(`Data data_with_errors, `Command command_chunk, data_name))
| Dim3_axes _ ->
let data_name = namegen () in
let data_block = GP_data._3d ~data_name data in
let command_chunk =
if with_points then
GP_subcommand.linespoints_3d ~data_name style ~legend_opt:legend
else GP_subcommand.lines_3d ~data_name style ~legend_opt:legend
in
(`Data data_block, `Command command_chunk, data_name)
let histogram (data : r1 Seq.t) (options : histogram_options) legend_opt =
let data_name = namegen () in
let data_block = GP_data._1d ~data_name data in
let command_chunk =
GP_subcommand.histogram ~data_name options ~legend_opt
in
(`Data data_block, `Command command_chunk, data_name)
let boxes (data : (string * float) Data.t with_metadata) box_width fill =
let ys = List.of_seq data.data in
let len = List.length ys in
let xs = Stdlib.List.init len (fun i -> float_of_int (1 + i)) in
let data_name = namegen () in
let data_block =
let box_width = match box_width with None -> 1. | Some w -> w in
GP_data._s3d
~data_name
(List.to_seq
(Stdlib.List.map2
(fun x (s, y) -> ("\"" ^ s ^ "\"", r3 x y box_width))
xs
ys))
in
let command_chunk =
GP_subcommand.boxes ~data_name ~legend_opt:data.legend ~fill
in
(`Data data_block, `Command command_chunk, data_name)
let rec spec_list :
type dim.
dim axes ->
dim spec list ->
GP_data.t list ->
GP_subcommand.t list ->
string list ->
GP_data.t list * GP_subcommand.t list * string list =
fun (type dim)
(axes : dim axes)
(specs : dim spec list)
data_acc
cmd_acc
name_acc ->
match specs with
| [] -> (List.rev data_acc, List.rev cmd_acc, List.rev name_acc)
| Scatter {data; error_bars} :: tl ->
let `Data data_block, `Command command_chunk, data_name =
scatter axes data error_bars
in
spec_list
axes
tl
(data_block :: data_acc)
(command_chunk :: cmd_acc)
(data_name :: name_acc)
| Histogram {data; options; legend} :: tl ->
let `Data data_block, `Command command_chunk, data_name =
histogram data options legend
in
spec_list
axes
tl
(data_block :: data_acc)
(command_chunk :: cmd_acc)
(data_name :: name_acc)
| Line {data; with_points; error_bars} :: tl ->
let `Data data_block, `Command command_chunk, data_name =
line axes data with_points error_bars
in
spec_list
axes
tl
(data_block :: data_acc)
(command_chunk :: cmd_acc)
(data_name :: name_acc)
| Boxes {data; box_width; fill} :: tl ->
let `Data data_block, `Command command_chunk, data_name =
boxes data box_width fill
in
spec_list
axes
tl
(data_block :: data_acc)
(command_chunk :: cmd_acc)
(data_name :: name_acc)
let render ?save (Plot {axes; plots; title}) =
let title_cmd =
match title with None -> set_title "" | Some title -> set_title title
in
let save_cmd =
match save with None -> [] | Some savefile -> [sf "save %s" savefile]
in
match axes with
| Dim2_axes {xaxis; yaxis; xtics; ytics} ->
let all_data, cmds, _all_data_names = spec_list axes plots [] [] [] in
let all_data = (all_data :> string list) in
let cmd = (GP_command.plot cmds :> string) in
concat
([
set_xlabel xaxis;
set_ylabel yaxis;
set_xtics xtics;
set_ytics ytics;
concat all_data;
title_cmd;
cmd;
]
@ save_cmd)
| Dim3_axes {xaxis; yaxis; zaxis; xtics; ytics; ztics} ->
let all_data, cmds, all_data_names = spec_list axes plots [] [] [] in
let all_data = (all_data :> string list) in
let cmd = (GP_command.splot cmds :> string) in
concat
([
set_xlabel xaxis;
set_ylabel yaxis;
set_zlabel zaxis;
set_xtics xtics;
set_ytics ytics;
set_ztics ztics;
concat all_data;
title_cmd;
set_fixed_z_range all_data_names;
cmd;
]
@ save_cmd)
let save_file i j = sf "ARG0.'save_%d_%d.option'" i j
let multiplot ~title ~matrix =
let rows = Array.length matrix in
let cols = Array.length matrix.(0) in
let size = rows * cols in
let plots =
List.flatten (Array.to_list ((Array.map Array.to_list) matrix))
in
concat
([
sf
"set multiplot layout %d, %d rowsfirst downwards title '%s'"
rows
cols
title;
]
@ plots
@ [sf "unset multiplot"]
@ [sf "N = %d" size]
@ ["array plots[N]"]
@ Stdlib.List.init size (fun c ->
let i = c mod rows in
let j = c / rows in
sf "plots[%d] = %s" (i + 1) (save_file i j))
@ ["multi = 1"] @ ["c = 1"]
(* Macro for plotting *)
@ [
sf
"PLOT = \"if (multi == 1) {min = 1; max = N; rn = %d; cn = %d} \
else {min = c; max = c; rn = 1; cn = 1}; set multiplot layout rn, \
cn rowsfirst downwards title ''; do for [i=min:max] { load \
plots[i]; if (multi == 0) {clear; replot}}; unset multiplot\""
rows
cols;
]
(* Key bindings to navigate the plots *)
(* Ctrl+Left/Right: previous/next plot*)
@ [
"bind 'ctrl-Right' 'if (multi == 1) { multi = 0 } else { c = (c % N) \
+ 1 };'.PLOT";
]
@ [
"bind 'ctrl-Left' 'if (multi == 1) { multi = 0 } else { c = ((c + N \
- 2) % N) + 1 };'.PLOT";
]
(* Ctrl+Down: Show multiplot *)
@ ["bind 'ctrl-Down' 'if (multi == 0) { multi = 1 };'.PLOT"]
(* Ctrl+Up: Show single plot *)
@ ["bind 'ctrl-Up' 'if (multi == 1) { multi = 0 };'.PLOT"])
let render_matrix ~title plots =
let plot_matrix =
Array.mapi
(fun i ->
Array.mapi (fun j -> function
| None -> "set multiplot next"
| Some p -> render ~save:(save_file i j) p))
plots
in
multiplot ~title ~matrix:plot_matrix
let render = render ?save:None
end
(* Plot targets *)
type target =
| Pdf_target of {cm_size : (float * float) option; pdf_file : string}
| Png_target of {pixel_size : (int * int) option; png_file : string}
| X11_target
| Qt_target of {pixel_size : (int * int) option}
let pdf ?cm_size ~pdf_file () = Pdf_target {cm_size; pdf_file}
let png ?pixel_size ~png_file () = Png_target {pixel_size; png_file}
let x11 = X11_target
let qt ?pixel_size () = Qt_target {pixel_size}
module GP_run = struct
let set_target (t : target) =
let print_pixel_size pixel_size =
Option.fold ~none:"" ~some:(fun (x, y) -> sf "size %d, %d" x y) pixel_size
in
match t with
| Png_target {pixel_size; png_file} ->
concat
[
sf "set terminal pngcairo %s" (print_pixel_size pixel_size);
sf "set output '%s'" png_file;
]
| Pdf_target {cm_size; pdf_file} ->
concat
[
sf
"set terminal pdf %s"
(Option.fold
~none:""
~some:(fun (x, y) -> sf "size %fcm, %fcm" x y)
cm_size);
sf "set output '%s'" pdf_file;
]
| X11_target -> "set terminal x11 noreplotonresize"
| Qt_target {pixel_size} ->
sf "set terminal qt %s" (print_pixel_size pixel_size)
let is_target_interactive (t : target) =
match t with
| Png_target _ | Pdf_target _ -> false
| X11_target | Qt_target _ -> true
let make_script ~matrix_mode ~target ~(plot : GP_script.t) =
concat
([set_target target; (plot :> string)]
@ (if is_target_interactive target then ["pause mouse close"] else [])
@ if matrix_mode then ["do for [i=1:N]{system 'rm '.plots[i]}"] else [])
let write_script ~filename ~matrix_mode ~target ~(plot : GP_script.t) =
match open_out_gen [Open_wronly; Open_creat; Open_trunc] 0o666 filename with
| exception _ ->
Format.eprintf "write_script: could not open file %s, exiting" filename ;
exit 1
| oc ->
let script = make_script ~matrix_mode ~target ~plot in
output_string oc script ;
close_out oc
let run_script ?(path = "gnuplot") ?(detach = false) ~matrix_mode ~target
~(plot : GP_script.t) () =
let name, oc = Filename.open_temp_file ~perms:0o666 "gnuplot" ".gp" in
let full_command =
concat
([set_target target; (plot :> string)]
@ (if is_target_interactive target then ["pause mouse close"] else [])
@ if matrix_mode then ["do for [i=1:N]{system 'rm '.plots[i]}"] else []
)
in
output_string oc full_command ;
close_out oc ;
match Unix.fork () with
| 0 -> (
match Unix.system (sf "%s %s 2>/dev/null" path name) with
| WEXITED 0 ->
Unix.unlink name ;
exit 0
| _ ->
Unix.unlink name ;
Format.eprintf "run_script: call to gnuplot failed, exiting" ;
exit 1)
| pid -> if not detach then ignore @@ Unix.waitpid [] pid else ()
let write_and_run_script ?(path = "gnuplot") ?(detach = false) ~filename
~matrix_mode ~target ~(plot : GP_script.t) () =
match open_out_gen [Open_wronly; Open_creat; Open_trunc] 0o666 filename with
| exception _ ->
Format.eprintf "write_script: could not open file %s, exiting" filename ;
exit 1
| oc -> (
let script = make_script ~matrix_mode ~target ~plot in
output_string oc script ;
close_out oc ;
match Unix.fork () with
| 0 -> (
match Unix.system (sf "%s %s 2>/dev/null" path filename) with
| WEXITED 0 -> exit 0
| _ ->
Format.eprintf
"run_script: call to gnuplot failed, exiting (wrote script \
to %s)"
filename ;
exit 1)
| pid -> if not detach then ignore @@ Unix.waitpid [] pid else ())
end
type action =
| Exec
| Save_to of string
| Exec_and_save_to of string
| Exec_detach
let exec = Exec
let exec_detach = Exec_detach
let save_to filename = Save_to filename
let exec_and_save_to filename = Exec_and_save_to filename
let write_plot ~filename ~target ~plot =
let plot = GP_script.render plot in
GP_run.write_script ~filename ~matrix_mode:false ~target ~plot
let run_plot ?path ?detach ~target ~plot () =
let plot = GP_script.render plot in
GP_run.run_script ?path ?detach ~matrix_mode:false ~target ~plot ()
let write_and_run_plot ?path ~filename ~target ~plot () =
let plot = GP_script.render plot in
GP_run.write_and_run_script
?path
~filename
~matrix_mode:false
~target
~plot
()
let write_matrix ~filename ~title ~target ~plots =
let plot = GP_script.render_matrix ~title plots in
GP_run.write_script ~filename ~matrix_mode:true ~target ~plot
let run_matrix ?path ?detach ~title ~target ~plots () =
let plot = GP_script.render_matrix ~title plots in
GP_run.run_script ?path ?detach ~matrix_mode:true ~target ~plot ()
let write_and_run_matrix ?path ~title ~filename ~target ~plots () =
let plot = GP_script.render_matrix ~title plots in
GP_run.write_and_run_script ?path ~filename ~matrix_mode:true ~target ~plot ()
let run ?path ~target action plot =
match action with
| Exec -> run_plot ?path ~plot ~target ()
| Save_to filename -> write_plot ~filename ~target ~plot
| Exec_and_save_to filename ->
write_and_run_plot ?path ~filename ~target ~plot ()
| Exec_detach -> run_plot ?path ~detach:true ~plot ~target ()
let run_matrix ?path ~target ?(title = "") action plots =
match action with
| Exec -> run_matrix ?path ~title ~plots ~target ()
| Save_to filename -> write_matrix ~filename ~title ~target ~plots
| Exec_and_save_to filename ->
write_and_run_matrix ?path ~title ~filename ~target ~plots ()
| Exec_detach -> run_matrix ?path ~detach:true ~title ~plots ~target ()
let get_targets ?(path = "gnuplot") () =
let for_reading_by_parent, for_writing_by_child =
Unix.pipe ~cloexec:false ()
in
match Unix.fork () with
| 0 ->
let _ = Unix.dup2 ~cloexec:false for_writing_by_child Unix.stdout in
let _ = Unix.dup2 ~cloexec:false for_writing_by_child Unix.stderr in
Unix.execvp path [|path; "-e"; "print(GPVAL_TERMINALS); quit"|]
| child_pid ->
Unix.close for_writing_by_child ;
let rec waitloop () =
match Unix.waitpid [] child_pid with
| exception Unix.Unix_error (Unix.EINTR, _, _) ->
(* [waitpid] gets interrupted, probably because some other child of
the parent process terminated while waiting (?) *)
waitloop ()
| _, WEXITED 0 ->
let ic = Unix.in_channel_of_descr for_reading_by_parent in
let buf = Buffer.create 1024 in
(try
while true do
Buffer.add_channel buf ic 512
done
with End_of_file -> ()) ;
close_in ic ;
let file = Buffer.contents buf in
Option.some (String.split_on_char ' ' (String.trim file))
| _ ->
Format.eprintf "Child process terminated abnormally@." ;
None
in
waitloop ()
![swh spinner](/static/img/swh-spinner.gif)
Computing file changes ...