#| File:cgn.lisp Content: package for using gnuplot from Lisp Author: Felip Alàez Nadal Last update: 9-2-2006 Revision number: 18 |# #| cgn Copyright (C) Felip Alàez Nadal 2006-2007 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02111-1307 USA |# (declaim (optimize (speed 0) (safety 3) (debug 3) (compilation-speed 0))) (provide :cgn) (defpackage :cgn (:use :cl :ltk) (:export #:start-gnuplot #:format-gnuplot #:close-gnuplot #:set-title #:set-grid #:on #:off #:plot-function #:replot-function #:x #:y #:set-range #:plot-points #:postscript-copy #:w32 #:print-graphic #:plot #:save-cgn #:load-cgn #:with-gnuplot #:animate-function #:enable-debug #:disable-debug #:show-release-info #:plot-scatter-graphic #:create-scatter-graphic-description #:create-scatter-data-serie) (:documentation "cgn is a library to control gnuplot from inside Common Lisp.")) (in-package :cgn) (defvar *gnuplot* nil "Gnuplot's connection") (defvar *os* nil "The Operative System used by the user. You should not bind It directly, use the with-gnuplot macro instead.") (defvar *debug* nil "A flag controlling if cgn has to do debug.") (defvar *release* 009 "The number of release.") (defvar *release-date* "9/2/2007" ) (defstruct data-serie () ; "A struct to save the information about a data serie of a scatter graphic." x y error_x error_y title) (defstruct scatter-graphic () ; "A struct to save all the data-series of a scatter graphic." series title ) (defmethod create-scatter-graphic-description ( title ) "Creates a scatter-graphic struct to save information about the graphic." (make-scatter-graphic :title title )) (defmethod create-scatter-data-serie ( ( sc scatter-graphic ) x y error_x error_y title) "Creates a new serie on sc. Note that you must always give the errors. If you don't want errors, just set them to zero." (setf (scatter-graphic-series sc) (concatenate 'list (scatter-graphic-series sc) (list (make-data-serie :x x :y y :error_x error_x :error_y error_y :title title))))) (defmethod plot-scatter-graphic ( (sc scatter-graphic )) "Plots a scatter graphic." (let ((numero_de_serie 0)) (dolist ( graphic (scatter-graphic-series sc )) (let ( (x (data-serie-x graphic)) (y (data-serie-y graphic)) (error_x (data-serie-error_x graphic)) (error_y (data-serie-error_y graphic)) (titol (data-serie-title graphic)) (nom_arxiu (format nil ".cgn~A" numero_de_serie)) ) (set-title (scatter-graphic-title sc)) (with-open-file ( arxiu nom_arxiu :direction :output :if-exists :overwrite :if-does-not-exist :create ) (do ((i 0 (incf i ))) ((= i (length x ) ) (values)) (format arxiu " ~A ~A ~A ~A~%" (nth i x) (nth i y) (nth i error_x ) (nth i error_y )))) (if (= numero_de_serie 0) (progn (format-gnuplot "plot '~A' with xyerrorbars title '~A'" nom_arxiu titol) (incf numero_de_serie)) (format-gnuplot "replot '~A' with xyerrorbars title '~A'" nom_arxiu titol) ) ) ) ) ) (defun show-release-info () "Show the release info." (format t "Cgn ~A released ~A~%" *release* *release-date*) (values) ) (defun enable-debug () "Enables debug for cgn." (setq *debug* t)) (defun disable-debug () "Disables debug for cgn." (setq *debug* nil)) (defun start-gnuplot (&key ( persistent nil) (path "gnuplot") ) ;&optional (path "gnuplot") ) "Runs gnuplot as a subprocess. This should be the first function to call." (let ( (options (if persistent (list "-persist" ) nil ) ) ) (cond ((eq *gnuplot* nil) (setq *gnuplot* (do-execute path options))) ( t nil)) (values) ) ) (defun format-gnuplot (text &rest arguments ) "Sends a command to gnuplot" (cond ((eq *gnuplot* nil) (format t "Gnuplot is not running")) (t (apply #'format *gnuplot* text arguments) (format *gnuplot* "~%") (if *debug* (progn (apply #'format t text arguments ) (format t "~%"))) (force-output *gnuplot*))) (values) ) (defun close-gnuplot () "Closes gnuplot" (cond ( (eq *gnuplot* nil) t) ( t (format-gnuplot "quit" ) (close *gnuplot*) (setq *gnuplot* nil))) (values) ) (defun update () "Refreshes the screen. This should not be called by the user." (format-gnuplot "replot")) (defun set-title (text) "Sets the window title" (cond ((eq *gnuplot* nil) ) ( t (format-gnuplot "set title '~A'" text) (update))) (values)) (defun set-grid (on-off) "Activates/deactivates the grid." (cond ( (eq *gnuplot* nil)) ( t (cond ((eq on-off 'on) (format-gnuplot "set grid") (update) ) ((eq on-off 'off) (format-gnuplot "unset grid") (update) ) (t (format t "I don't understand~%"))) )) (values)) (defun plot-function (funcio) "Shows a function. It can show functions like f(x) (2D representation) or g(x,y) (3D representation)." (let ( (variables nil) parametric_2D parametric_3D ) (dolist (letter (coerce funcio 'list)) (cond ((eq letter #\t) (setq parametric_2D t ) ) ((eq letter #\u) (setq parametric_3D t ) ) ((eq letter #\y) (setq variables t)))) (cond (parametric_2D (format-gnuplot "set parametric; plot ~A ; unset parametric ;" funcio )) ( parametric_3D (format-gnuplot "set parametric; splot ~A ; unset parametric ;" funcio)) ( t ;Si el dibuixat es normal (if variables (format-gnuplot "splot ~A" funcio)) (format-gnuplot "plot ~A" funcio))) (values))) (defun replot-function (funcio) "Shows a n-variable function on gnuplot / replot version. '" (let ( (variables nil) parametric_2D parametric_3D ) (dolist (letter (coerce funcio 'list)) (cond ((eq letter #\t) (setq parametric_2D t ) ) ((eq letter #\u) (setq parametric_3D t ) ) ((eq letter #\y) (setq variables t)))) (if (or parametric_2D parametric_3D) (format-gnuplot "set parametric; replot ~A ; unset parametric ;" funcio) (progn (format t "~%replot ~A" funcio) (format-gnuplot "~%replot ~A " funcio))) (update) (values))) (defun animate-function ( function &key variable initial_value increment number_of_frames rapidity ) "Animates function, changing the value of variable in increments of increment at velocity rapidity, until number_of_frames is reached. At the moment, only one paramether is permited." (let ( (variables nil) parametric_2D parametric_3D ) (with-open-file ( stream ".cgn" :direction :output :if-does-not-exist :create :if-exists :overwrite ) (format stream " ~A = ~A + ~A~%" variable variable increment) (dolist (letter (coerce function 'list)) (cond ((eq letter #\t) (setq parametric_2D t ) (print 'parametric)) ((eq letter #\u) (setq parametric_3D t ) (print '3D)) ((eq letter #\y) (setq variables t)))) (cond (parametric_2D (print '2D ) (format stream " plot ~A ~% " function )) ( parametric_3D (print '3D) (format stream " splot ~A ~%" function)) ( t ;Si el dibuixat es normal (if variables (format stream "splot ~A~%" function)) (format stream "plot ~A~%" function))) (format stream "pause ~F~%" (coerce (/ 1 rapidity ) 'float)) (format stream "if (~A < ~F) reread ~% " variable (+ initial_value (* number_of_frames increment ))) ( if (or parametric_2D parametric_3D ) (format-gnuplot "set parametric")) (format-gnuplot "~A = ~A" variable initial_value) (format-gnuplot "load '.cgn'") (if (or parametric_2D parametric_3D ) (format-gnuplot "unset parametric")) )) (values) ) (defun set-range ( eix min max) "Sets the x or y range " (cond ((eq eix 'x) (format-gnuplot "set xrange [~F:~F]" min max) (update)) ((eq eix 'y) (format-gnuplot "set yrange [~F:~F]" min max) (update)) (t (format t "I don't understand~%"))) (values)) (defun plot-points ( x y &key x_error y_error) "This functions shows scatter plots, with x and y errors if desired" (cond ((eq x nil) ; Error (format t "I can't plot a serie without x values~%") (return-from plot-points (values))) ((eq y nil) ; Error (format t "I can't plot a serie without y values~%") (return-from plot-points (values))) ( t ;Si els casos anteriors no han sortit, tenim una sèrie x i una y -> podem fer el gràic. Ara cal que ens preguntem pels errors (cond ((and x_error y_error) ;Tenim barres d'error en les x i en les y (let ( (l_x (length x)) (l_y (length y)) (l_ey (length y_error)) (l_ex (length x_error)) len) (setq len (min l_x l_y l_ey l_ex)) (with-open-file (stream ".cgn.dat" :direction :output :if-does-not-exist :create :if-exists :overwrite ) (do ((i 0 (incf i))) ((= i len) t) (format stream "~F ~F ~F ~F ~%" (nth i x ) (nth i y) (nth i x_error) (nth i y_error)) )) (format-gnuplot "plot '.cgn.dat' with xyerrorbars") )) ( x_error ;Tenim barres d'error en les x només (let ( (l_x (length x)) (l_y (length y)) (l_ex (length x_error)) len) (setq len (min l_x l_y l_ex)) (with-open-file (stream ".cgn.dat" :direction :output :if-does-not-exist :create :if-exists :overwrite ) (do ((i 0 (incf i))) ((= i len ) t) (format stream "~F ~F ~F~%" (nth i x ) (nth i y) (nth i x_error)) )) (format-gnuplot "plot '.cgn.dat' with xerrorbars") )) ( y_error ;Tenim barres d'error en les y només (let ( (l_x (length x)) (l_y (length y)) (l_ey (length y_error)) len) (setq len (min l_x l_y l_ey)) (with-open-file (stream ".cgn.dat" :direction :output :if-does-not-exist :create :if-exists :overwrite ) (do ((i 0 (incf i))) ((= i len ) t) (format stream "~F ~F ~F~%" (nth i x ) (nth i y) (nth i y_error)) )) (format-gnuplot "plot '.cgn.dat' with yerrorbars") )) ( t ; No tenim barres d'error (let ( (l_x (length x)) (l_y (length y)) len) (setq len (min l_x l_y)) (with-open-file (stream ".cgn.dat" :direction :output :if-does-not-exist :create :if-exists :overwrite ) (do ((i 0 (incf i))) ((= i len ) t) (format stream "~F ~F ~%" (nth i x ) (nth i y)) )) (format-gnuplot "plot '.cgn.dat'") )) )) ) ) (defun postscript-copy ( filename ) "Saves a postscript copy of the screen ." (format-gnuplot "set terminal postscript color") (format-gnuplot "set output '~F'" filename) (update) (format-gnuplot "set terminal pop") (values)) (defun print-graphic ( &optional (os *os*) ) "Sends the graphic to the printer. You must specify which os you're using: w32 for Windows, unix for others." (cond ((eq os 'w32) ;Windows (format-gnuplot "screendump")) ( t ;Unix-like systems (postscript-copy "tmp001.ps" ) (format-gnuplot "!lpr -p tmp001.ps") (format-gnuplot "!rm -f tmp001.ps") )) (values) ) (defun save-cgn ( file ) "Saves the screen to a file that can be loaded by gnuplot using load." (format-gnuplot "save '~F'" file) (values)) (defun load-cgn (file ) "Loads a file saved previously with save." (format-gnuplot "load '~F'" file) (values)) (defmacro with-gnuplot ( ( os &key (path "gnuplot") ) &body body) "Creates a new gnuplot connection, and evaluates body. New from cgn 007. This macro takes care of always having a running connection when you try to comunicate with gnuplot, and to close the connection when you finish using It. Also binds the *os* special variable to the os paramether in order to not having to pass It to the methods that are os-dependents (at the moment, only print-graphic). You can too specify the path where gnuplot resides ( pgnuplot.exe, on Windows ). This can be useful if you don't have gnuplot at the path. This is a tipical Windows problem. This should be the prefered way to use cgn. But you can continue opening and closing the connection manually. Just keep using (start-gnuplot) and (close-gnuplot) for that. I don't recommend to do that. E.g : supose you want to print cos(x)*atan ( exp (y)) on a linux machine. You should use : (with-gnuplot ( 'linux ) (plot-function \"cos(x)* atan (exp(y)) \" ) (print-graphic)) " `(unwind-protect (progn (let ( (*os* ,os) ) (start-gnuplot :persistent t :path ,path) ,@body (values) ) ) (if *gnuplot* (close-gnuplot) (values) ) ) )