Source file plot.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
(** Plot utils *)
module Log = Statocaml.Log
let runs = ref []
let run_durations () = !runs
type gp = Buffer.t
let code = Buffer.contents
let p gp = Printf.kbprintf (fun b -> Buffer.add_string b "\n") gp
let terminal_of_ext str =
match String.lowercase_ascii str with
| "svg" -> "svg"
| _ -> "pngcairo"
let create ?terminal ?background ?(w=600) ?(h=1000) ?title
?title_font_size output =
let terminal =
match terminal with
| Some str -> str
| None ->
let str = Filename.extension output in
let len = String.length str in
if len > 1 then
terminal_of_ext (String.sub str 1 (len - 1))
else
"pngcairo"
in
let gp = Buffer.create 256 in
p gp "set term %s %ssize %d,%d" terminal
(match background with None -> "" | Some c -> Printf.sprintf "background %s " c)
w h;
p gp "set output %S" output;
Option.iter (fun str -> p gp "set title %S%s" str
(match title_font_size with None -> "" | Some n -> Printf.sprintf " font \",%d\"" n)
) title;
gp
let add_run duration = runs := duration :: !runs
let () =
let com = "gnuplot < /dev/null" in
match Sys.command com with
| 0 -> ()
| _ ->
prerr_endline(Printf.sprintf "Command %S failed; is gnuplot installed ?" com);
exit 1
let pool =
Lwt_pool.create 5
(fun () -> Lwt.return (Lwt_process.(open_process_full (shell "gnuplot"))))
let run =
let rec read_until_done acc ic =
match%lwt Lwt_io.read_line ic with
| "DONE" -> Lwt.return (List.rev acc)
| str -> read_until_done (str::acc) ic
in
fun gp ->
Lwt_pool.use pool (fun p ->
let code = Buffer.contents gp in
Log.debug (fun m -> m "running gnuplot code:\n%s" code);
let dstart = Unix.gettimeofday () in
let%lwt () = Lwt_io.write p#stdin code in
let%lwt () = Lwt_io.write p#stdin "print \"DONE\"\n" in
let%lwt _ = read_until_done [] p#stderr in
let%lwt () = Lwt_io.write p#stdin "print GPVAL_ERRNO\n" in
let%lwt errmsg =
match%lwt Lwt_io.read_line p#stderr with
| "0" -> Lwt.return_none
| _ ->
let%lwt () = Lwt_io.write p#stdin "print GPVAL_ERRMSG\n" in
let%lwt () = Lwt_io.write p#stdin "print \"DONE\"" in
match%lwt read_until_done [] p#stderr with
| [] -> Lwt.return_some "No error message"
| lines -> Lwt.return_some (String.concat "\n" lines)
in
let%lwt () = Lwt_io.write p#stdin "reset session\n" in
match errmsg with
| None ->
let dstop = Unix.gettimeofday () in
let duration = dstop -. dstart in
add_run duration ;
Lwt.return_unit
| Some msg ->
failwith (Printf.sprintf "gnuplot code failed:\n===%s\n===\n%s" code msg)
)
let define_float_data gp varname l =
p gp "$%s <<EOF" varname ;
List.iter (fun f -> p gp "%f" f) l ;
p gp "EOF"
let define_float_float_data gp varname l =
p gp "$%s <<EOF" varname ;
List.iter (fun (f, g) -> p gp "%f %f" f g) l ;
p gp "EOF"
let define_float_float_string_data gp ?(sep='\t') varname l =
p gp "$%s <<EOF" varname ;
List.iter (fun (f, g, s) -> p gp "%f%c%f%c\"%s\"" f sep g sep s) l ;
p gp "EOF"
let define_float_float_int_string_data gp ?(sep='\t') varname l =
p gp "$%s <<EOF" varname ;
List.iter (fun (f, g, d, s) -> p gp "%f%c%f%c%d%c\"%s\"" f sep g sep d sep s) l ;
p gp "EOF"
type month = int * int
module Mmap = Map.Make(struct type t = month let compare = Stdlib.compare end)
let month_name = function
| 1 -> "Jan" | 2 -> "Feb" | 3 -> "Mar" | 4 -> "Apr" | 5 -> "May" | 6 -> "Jun"
| 7 -> "Jul" | 8 -> "Aug" | 9 -> "Sep" | 10 -> "Oct" | 11 -> "Nov" | 12 -> "Dec"
| x -> failwith (Printf.sprintf "Invalid month %d" x)
let month_label (y,m) =
let year = if y = 0 then "" else Printf.sprintf "%d-" y in
Printf.sprintf "%s%s" year (month_name m)
let month_labels =
let rec iter acc cur_y = function
| [] -> List.rev acc
| (y,m) :: q ->
let v = if cur_y = y then (0,m) else (y,m) in
iter (v::acc) y q
in
fun months -> List.map month_label (iter [] 0 months)
let month_in_interval ?after ?before () =
match after, before with
| None, None -> (fun _ -> true)
| Some bound, None ->
let (yb,mb,_) = Ptime.to_date bound in
(fun (y,m) -> y > yb || (y = yb && m >= mb))
| None, Some bound ->
let (yb,mb,_) = Ptime.to_date bound in
(fun (y,m) -> y < yb || (y = yb && m < mb))
| Some b1, Some b2 ->
let (yb1,mb1,_) = Ptime.to_date b1 in
let (yb2,mb2,_) = Ptime.to_date b2 in
(fun (y,m) ->
(y > yb1 || (y = yb1 && m >= mb1))
&& (y < yb2 || (y = yb2 && m < mb2))
)
let min_max_of_month_maps = function
| [] -> failwith"min_max_of_month_maps: No months"
| m :: maps ->
let (min0,_) = Mmap.min_binding m in
let (max0,_) = Mmap.max_binding m in
List.fold_left
(fun (acc_min, acc_max) m ->
let (min1,_) = Mmap.min_binding m in
let (max1,_) = Mmap.max_binding m in
(min acc_min min1, max acc_max max1)
) (min0, max0) maps
let add_missing_months ~default min_month max_month =
let (min_y, min_m) = min_month in
let (max_y, max_m) = max_month in
let rec iter_m y stop acc m =
if m > stop then
acc
else
let acc = match Mmap.find_opt (y, m) acc with
| Some _ -> acc
| None -> Mmap.add (y,m) default acc
in
iter_m y stop acc (m+1)
in
let rec iter_y acc y =
if y > max_y then
acc
else
let start = if y = min_y then min_m else 1 in
let stop = if y = max_y then max_m else 12 in
let acc = iter_m y stop acc start in
iter_y acc (y+1)
in
fun map -> iter_y map min_y
let gnuplot_init_for_months gp ?fontsize map =
let labels = month_labels (List.map fst (Mmap.bindings map)) in
let set_xrange = Printf.sprintf "set xrange [%d:%d]" (-1) (Mmap.cardinal map) in
let set_xtics =
let labels =
let rec iter acc n = function
| [] -> List.rev acc
| h :: q ->
let label = Printf.sprintf "%S %d" h n in
let acc = label :: acc in
iter acc (n+1) q
in
iter [] 0 labels
in
Printf.sprintf "set xtics %srotate (%s)"
(match fontsize with None -> "" | Some n -> Printf.sprintf "font \",%d\" " n)
(String.concat ", " labels)
in
let coms =
[
"set style histogram clustered gap 2";
"set key bottom center outside" ;
set_xrange ;
set_xtics ;
]
in
List.iter (fun s -> p gp "%s" s) coms
let spider_plot gp axes values =
p gp "set spiderplot";
p gp "set nokey";
p gp "set style spiderplot fs transparent solid 0.2 border";
List.iteri (fun i (label,(low,hi),total) ->
let v = List.nth values i in
let pct = (v /. total) *. 100. in
let i = i + 1 in
p gp "set paxis %d range [%f:%f]" i low hi ;
p gp "set paxis %d tics font \",9\" format ''" i ;
p gp "set paxis %d label \"%s: %d (%d%%)\\n(max=%d/%d, %d%%)\" font \",9\"" i label
(truncate (List.nth values (i-1)))
(truncate pct)
(truncate hi)
(truncate total)
(truncate ((hi /. total) *. 100.));
p gp "set paxis %d lw 0.2" i;
p gp "set grid spiderplot"
) axes;
p gp "$data <<EOF" ;
p gp "%s" (String.concat " " (List.map string_of_float values));
p gp "EOF";
p gp "plot for [i=1:%d] $data using i:key(1)" (List.length values)