Fix/refactor `sh` for CCL and others
author |
Steve Losh <steve@stevelosh.com> |
date |
Thu, 05 Dec 2019 19:35:58 -0500 |
parents |
dc630773f98a |
children |
e9553a14c887 |
(in-package :losh.gnuplot)
(defun gnuplot-args% (&rest args)
(mapcan (lambda (arg) (list "-e" arg))
(remove nil args)))
(defun gnuplot-args (&key
(output :qt)
(filename "plot.png")
(style :lines)
(size-x 1200)
(size-y 800)
(label-x)
(label-y)
(line-title 'data)
(line-width 4)
(smooth nil)
(axis-x nil)
(axis-y nil)
(min-x nil)
(max-x nil)
(min-y nil)
(max-y nil)
(tics-x nil)
(tics-y nil)
(graph-title)
(logscale-x nil)
(logscale-y nil)
(box-width nil)
&allow-other-keys)
"Return the formatted command line arguments for the given gnuplot arguments.
You shouldn't call this function directly — it's exposed just so you can see
the list of possible gnuplot arguments all in one place.
"
(check-type axis-x boolean)
(check-type axis-y boolean)
(check-type logscale-x boolean)
(check-type logscale-y boolean)
(check-type line-width (or integer float))
(check-type box-width (or null integer float))
(check-type min-x (or null integer float))
(check-type min-y (or null integer float))
(check-type max-x (or null integer float))
(check-type max-y (or null integer float))
(check-type tics-x (or null integer float))
(check-type tics-y (or null integer float))
(check-type graph-title (or null string))
(check-type label-x (or null string))
(check-type label-y (or null string))
(check-type smooth (member nil :unique :frequency :csplines :acsplines :bezier :sbezier))
(flet ((esc (string) (remove #\' (aesthetic-string string)))
(f (&rest args) (apply #'format nil (substitute "" nil args))))
(gnuplot-args%
(ccase output
((:x :x11) (f "set terminal x11 persist"))
(:qt (f "set terminal qt"))
(:wxt (f "set terminal wxt persist"))
(:png
(f "set terminal pngcairo dashed size ~D,~D font \"Lucida Grande,20\""
size-x size-y)
(f "set output '~A'" (esc filename))))
(f "set border linewidth 1")
(f "set style line 10 dashtype 2 linewidth 3 linecolor \"#666666\"")
(when axis-x (f "set xzeroaxis linestyle 10"))
(when axis-y (f "set yzeroaxis linestyle 10"))
(when tics-x (f "set xtics ~A" tics-x))
(when tics-y (f "set ytics ~A" tics-y))
(when box-width (f "set boxwidth ~A" box-width))
(when graph-title (f "set title '~A'" (esc graph-title)))
(when label-x (f "set xlabel '~A'" (esc label-x)))
(when label-y (f "set ylabel '~A'" (esc label-y)))
(when logscale-x (f "set logscale x"))
(when logscale-y (f "set logscale y"))
(f "set xrange [~A:~A]" min-x max-x)
(f "set yrange [~A:~A]" min-y max-y)
(f "plot '-' using 1:2 title '~A' with ~(~A~) linewidth ~D ~A"
(esc line-title) style line-width
(when smooth (f "smooth ~(~A~)" smooth)))
(f "pause mouse close"))))
(defun gnuplot (data &rest args &key
(x #'car)
(y #'cdr)
(spew-output nil)
&allow-other-keys)
"Plot `data` to `filename` with gnuplot.
This will (silently) quickload the `external-program` system to handle the
communication with gnuplot.
`data` should be a sequence of data points to plot.
`x` should be a function to pull the x-values from each item in data.
`y` should be a function to pull the y-values from each item in data.
See the docstring of `gnuplot-args` for other keyword arguments.
"
(funcall (read-from-string "ql:quickload") 'external-program :silent t)
(let* ((process (funcall (read-from-string "external-program:start")
"gnuplot"
(apply #'gnuplot-args args)
:input :stream
:output (if spew-output *standard-output* nil)))
(in (funcall (read-from-string "external-program:process-input-stream")
process)))
(unwind-protect
(progn
(iterate (for item :in-whatever data)
(format in "~F ~F~%" (funcall x item) (funcall y item)))
(finish-output in))
(close in))
process))
(defun gnuplot-function (function &rest args &key
(start 0.0)
(end 1.0)
(step 0.1)
(include-end nil)
&allow-other-keys)
"Plot `function` over `[start, end)` by `step` with gnuplot.
If `include-end` is `t` the `end` value will also be plotted.
See the docstring of `gnuplot-args` for other keyword arguments.
"
(let* ((x (range start end :step step))
(x (append x
(when (and include-end
(/= (car (last x)) end))
(list end))))
(y (mapcar function x))
(data (mapcar #'cons x y)))
(apply #'gnuplot data args)))
(defun gnuplot-histogram (data &rest args &key
(bin-width 1)
&allow-other-keys)
"Plot `data` as a histogram with gnuplot.
`bin-width` should be the desired width of the bins. The bins will be
centered on multiples of this number, and data will be rounded to the nearest
bin.
"
(-<> data
(mapcar (lambda (y)
(* bin-width (round y bin-width)))
<>)
frequencies
hash-table-alist
(apply #'gnuplot <>
:style :boxes
:min-y 0
:line-width 1
:box-width (* bin-width 1.0)
args)))