Repository navigation
Expand file tree
/
Copy pathgnuplot-pipe.sls
More file actions
147 lines (124 loc) · 4.89 KB
/
Copy pathgnuplot-pipe.sls
File metadata and controls
147 lines (124 loc) · 4.89 KB
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
(library (gnuplot-pipe)
(export
call/gnuplot
send
plot
plot3d
save
end-data)
(import (chezscheme)
(only (srfi :13 strings) string-join)
(only (srfi :1 lists) fold))
;;---------------------------------------------------------------------
;; Public procedures and macros.
(define-syntax call/gnuplot
(syntax-rules ()
((_ expr ...)
(with-output-to-pipe "gnuplot --persist"
(lambda ()
(send "set style data lines")
expr ...
(flush-output-port)
(newline)
(send "quit"))))))
;; Send arbitrary command to Gnuplot.
(define (send . cmdline)
(for-each (lambda (arg) (display arg))
cmdline)
(newline)
(flush-output-port))
;; Plot inline data.
(define (plot . element)
(plot-generic "plot" element "(plot element)"))
;; 3d plot inline data.
(define (plot3d . element)
(plot-generic "splot" element "(plot3d element)"))
;; Save last plot to file.
(define (save fname)
(define ext (path-extension fname))
(cond [(not ext)
(send "set terminal png")]
[(or (equal? ext "pdf") (equal? ext "png") (equal? ext "svg"))
(send (format "set terminal ~A" ext))]
[(equal? ext "txt")
(send "set terminal dumb")]
[else
(assertion-violation "(save fname)" "Extension must be png, pdf, svg or txt")])
(send "set output")
(send (format "set output '~A'" fname))
(send "replot")
(send "unset output")
(send "unset terminal"))
(define (end-data)
(display "e")
(newline)
(flush-output-port))
;;---------------------------------------------------------------------
;; Internal procedures.
;; (with-output-to-pipe) contributed by oaktownsam on Scheme discord server
;; Gnuplot's stderr is merged into stdout and drained until gnuplot exits.
;; Closing the read end early makes gnuplot (or gnuplot_qt, which inherits
;; the pipe) die from SIGPIPE on its first diagnostic message, e.g., the
;; qt font warning on macOS, before any plot is drawn or saved.
(define (with-output-to-pipe cmdline thunk)
(define-values (to-cmd from-cmd from-cmd-err cmd-pid)
(open-process-ports (string-append cmdline " 2>&1") 'line (native-transcoder)))
(dynamic-wind
void
(lambda ()
(parameterize ((current-output-port to-cmd))
(thunk)))
(lambda ()
(close-output-port to-cmd)
(let ([output (get-string-all from-cmd)])
(unless (eof-object? output)
(display output (current-error-port))))
(close-input-port from-cmd)
(close-input-port from-cmd-err))))
(define (plot-row . row)
(display (string-join (map number->string row)))
(newline))
(define (dataset-same-length? data)
(apply = (map length data)))
(define (plot-dataset-single data who)
(cond [(and (list? data) ; '((1 2 3 4) (10 20 30 40) ...)
(list? (car data))
(number? (caar data)))
(unless (dataset-same-length? data)
(assertion-violation who "Data rows of different length"))
(apply for-each plot-row data)]
[else
(assertion-violation who "Invalid data set")])
(end-data))
(define (plot-dataset data who)
(for-each (lambda (x) (plot-dataset-single x who)) data))
;; Plot using cmdline the list of descriptions and wait for data input.
(define (newplot cmdline description)
(apply send
cmdline " '-' " (car description)
(string-join (cdr description) ", '-' " 'prefix)
'()))
;; Extract a list of descriptions from a list of plot elements.
(define (plot-element-description element)
(map car element))
;; Extract a list of data from a list of plot elements.
(define (plot-element-data element)
(map cdr element))
;; Are all element descriptions strings?
(define (element-description-string? element)
(fold (lambda (x y) (and x y))
#t
(map string? (plot-element-description element))))
;; Dispatch according to arguments format.
(define (plot-generic cmdline element who)
(let ((plot-element (cond [(null? element)
(assertion-violation who "No plot element specified")]
[(string? (car element))
(list element)] ; ((str lst1 lst2 ...))
[(element-description-string? (car element))
(car element)] ; ((str l1st lst2 ...) ...)
[else
(assertion-violation who "Plot element format not recognized")])))
(newplot cmdline (plot-element-description plot-element))
(plot-dataset (plot-element-data plot-element) who)))
)