04de8001e1e0

Clean up gnuplot stuff, add `digit`
[view raw] [browse files]
author Steve Losh <steve@stevelosh.com>
date Wed, 21 Dec 2016 15:22:06 -0500
parents 363d33f1b089
children 0f627d8ff8c3
branches/tags (none)
files DOCUMENTATION.markdown losh.lisp make-docs.lisp package.lisp

Changes

--- a/DOCUMENTATION.markdown	Wed Dec 21 15:21:56 2016 -0500
+++ b/DOCUMENTATION.markdown	Wed Dec 21 15:22:06 2016 -0500
@@ -550,6 +550,65 @@
 
   
 
+## Package `LOSH.GNUPLOT`
+
+Utilities for plotting data with gnuplot.
+
+### `GNUPLOT` (function)
+
+    (GNUPLOT DATA &REST ARGS &KEY (X #'CAR) (Y #'CDR) &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.
+
+  
+
+### `GNUPLOT-ARGS` (function)
+
+    (GNUPLOT-ARGS &KEY (FILENAME plot.png) (SIZE-X 1200) (SIZE-Y 800) (LABEL-X)
+                  (LABEL-Y) (LINE-TITLE 'DATA) (LINE-WIDTH 4) (AXIS-X NIL)
+                  (AXIS-Y NIL) (GRAPH-TITLE) &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.
+
+  
+
+### `GNUPLOT-EXPR` (macro)
+
+    (GNUPLOT-EXPR EXPR &REST ARGS)
+
+Plot `expr` (an expression involving `x`) with gnuplot.
+
+  See the docstring of `gnuplot-args` for other keyword arguments.
+
+  
+
+### `GNUPLOT-FUNCTION` (function)
+
+    (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.
+
+  
+
 ## Package `LOSH.HASH-SETS`
 
 Simple hash set implementation.
@@ -833,33 +892,29 @@
 
   
 
+### `DIGIT` (function)
+
+    (DIGIT POSITION INTEGER &OPTIONAL (BASE 10))
+
+Return the value of the digit at `position` in `integer`.
+
+  Examples:
+
+    (digit 0 135) ; => 5
+    (digit 1 135) ; => 3
+    (digit 2 135) ; => 1
+
+    (digit 0 #xD4 16) ; => 4
+    (digit 1 #xD4 16) ; => 13
+
+  
+
 ### `DIVIDESP` (function)
 
     (DIVIDESP N DIVISOR)
 
 Return whether `n` is evenly divisible by `divisor`.
 
-### `GNUPLOT` (function)
-
-    (GNUPLOT DATA &KEY (FILENAME plot.png) (X #'CAR) (Y #'CDR))
-
-Plot `data` to `filename` with gnuplot.
-
-  This will (silently) quickload the `external-program` system to handle the
-  communication with gnuplot.
-
-  
-
-### `GNUPLOT-FUNCTION` (function)
-
-    (GNUPLOT-FUNCTION FUNCTION &KEY (START 0.0) (END 1.0) (STEP 0.1))
-
-Plot `function` with gnuplot.
-
-  See `plot` for more information.
-
-  
-
 ### `IN-RANGE-P` (function)
 
     (IN-RANGE-P LOW VALUE HIGH)
--- a/losh.lisp	Wed Dec 21 15:21:56 2016 -0500
+++ b/losh.lisp	Wed Dec 21 15:22:06 2016 -0500
@@ -164,46 +164,22 @@
        (< value high)))
 
 
-(defun gnuplot (data &key
-                (filename "plot.png")
-                (x #'car)
-                (y #'cdr))
-  "Plot `data` to `filename` with gnuplot.
-
-  This will (silently) quickload the `external-program` system to handle the
-  communication with gnuplot.
+(defun-inline digit (position integer &optional (base 10))
+  "Return the value of the digit at `position` in `integer`.
+
+  Examples:
+
+    (digit 0 135) ; => 5
+    (digit 1 135) ; => 3
+    (digit 2 135) ; => 1
+
+    (digit 0 #xD4 16) ; => 4
+    (digit 1 #xD4 16) ; => 13
 
   "
-  (uiop/package:symbol-call :ql :quickload 'external-program :silent t)
-  (let* ((process (uiop/package:symbol-call
-                    :external-program :start
-                    "gnuplot"
-                    `("-e" "set terminal png"
-                      "-e" ,(format nil "set output '~A'" filename)
-                      "-e" "plot '-' using 1:2 title 'DATA' with lines linewidth 2")
-                    :input :stream
-                    :output nil))
-         (in (uiop/package:symbol-call
-               :external-program :process-input-stream
-               process)))
-    (unwind-protect
-        (progn
-          (iterate (for item :in data)
-                   (format in "~A ~A~%" (funcall x item) (funcall y item)))
-          (finish-output in))
-      (close in))
-    process))
-
-(defun gnuplot-function (function &key (start 0.0) (end 1.0) (step 0.1))
-  "Plot `function` with gnuplot.
-
-  See `plot` for more information.
-
-  "
-  (let* ((x (range start end :step step))
-         (y (mapcar function x))
-         (data (mapcar #'cons x y)))
-    (gnuplot data)))
+  (-<> integer
+    (floor <> (expt base position))
+    (mod <> base)))
 
 
 ;;;; Random -------------------------------------------------------------------
@@ -791,7 +767,7 @@
 
 
 (defmacro-driver (FOR var MODULO divisor &sequence)
-  "Iterate numerically modulo `divisor`.
+  "Iterate numerically with `var` bound modulo `divisor`.
 
   This driver iterates just like the vanilla `for`, but each resulting value
   will be modulo'ed by `divisor` before being bound to `var`.
@@ -845,10 +821,9 @@
                 (cons (first ,current) (car ,l))
               (setf ,current nil)))
 
-           (t
-            (prog1
-                (cons (first ,current) (second ,current))
-              (setf ,current (cdr ,current))))))))))
+           (t (prog1
+                  (cons (first ,current) (second ,current))
+                (setf ,current (cdr ,current))))))))))
 
 
 (defmacro-clause (AVERAGING expr &optional INTO var)
@@ -2040,6 +2015,119 @@
   (apply #'make-bset list))
 
 
+;;;; Gnuplot ------------------------------------------------------------------
+(defun gnuplot-args% (&rest args)
+  (mapcan (lambda (arg) (list "-e" arg))
+          (remove nil args)))
+
+(defun gnuplot-args (&key
+                     (filename "plot.png")
+                     (size-x 1200)
+                     (size-y 800)
+                     (label-x)
+                     (label-y)
+                     (line-title 'data)
+                     (line-width 4)
+                     (axis-x nil)
+                     (axis-y nil)
+                     (graph-title)
+                     &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.
+
+  "
+  (flet ((esc (string) (remove #\' (aesthetic-string string)))
+         (f (&rest args) (apply #'format nil args)))
+    (gnuplot-args%
+      (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 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)))
+      (f "plot '-' using 1:2 title '~A' with lines linewidth ~D"
+         (esc line-title) line-width))))
+
+
+(defun gnuplot (data
+                &rest args
+                &key
+                (x #'car)
+                (y #'cdr)
+                &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.
+
+  "
+  (uiop/package:symbol-call :ql :quickload 'external-program :silent t)
+  (let* ((process (uiop/package:symbol-call
+                    :external-program :start
+                    "gnuplot"
+                    (apply #'gnuplot-args args)
+                    :input :stream
+                    :output nil))
+         (in (uiop/package:symbol-call
+               :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
+                               (not= (car (last x)) end))
+                      (list end))))
+         (y (mapcar function x))
+         (data (mapcar #'cons x y)))
+    (apply #'gnuplot data args)))
+
+(defmacro gnuplot-expr (expr &rest args)
+  "Plot `expr` (an expression involving `x`) with gnuplot.
+
+  See the docstring of `gnuplot-args` for other keyword arguments.
+
+  "
+  `(gnuplot-function (lambda (x) ,expr)
+    :line-title ',expr
+    ,@args))
+
+
 ;;;; Licensing ----------------------------------------------------------------
 ;;; Original code from @dk_jackdaniel:
 ;;; http://paste.lisp.org/display/327154
--- a/make-docs.lisp	Wed Dec 21 15:21:56 2016 -0500
+++ b/make-docs.lisp	Wed Dec 21 15:22:06 2016 -0500
@@ -9,6 +9,7 @@
         "LOSH.DEBUGGING"
         "LOSH.ELDRITCH-HORRORS"
         "LOSH.FUNCTIONS"
+        "LOSH.GNUPLOT"
         "LOSH.HASH-SETS"
         "LOSH.HASH-TABLES"
         "LOSH.ITERATE"
--- a/package.lisp	Wed Dec 21 15:21:56 2016 -0500
+++ b/package.lisp	Wed Dec 21 15:22:06 2016 -0500
@@ -48,9 +48,7 @@
     :precise-lerp
     :radians
     :square
-
-    :gnuplot
-    :gnuplot-function))
+    :digit))
 
 (defpackage :losh.random
   (:documentation "Utilities related to randomness.")
@@ -221,6 +219,15 @@
   (:export
     :print-licenses))
 
+(defpackage :losh.gnuplot
+  (:documentation "Utilities for plotting data with gnuplot.")
+  (:export
+    :gnuplot
+    :gnuplot-args
+    :gnuplot-expr
+    :gnuplot-function
+    :x))
+
 (defpackage :losh.eldritch-horrors
   (:documentation "Abandon all hope, ye who enter here.")
   (:export
@@ -242,6 +249,7 @@
    :losh.debugging
    :losh.eldritch-horrors
    :losh.functions
+   :losh.gnuplot
    :losh.hash-sets
    :losh.hash-tables
    :losh.iterate