1 ;;Copyright William F. Schelter 1990, All Rights Reserved
8 /* plot of z^
(1/3)...
*/
9 plot3d
(r^
.33*cos
(th/3),[r
,0,1],[th
,0,6*%pi
],['grid
,12,80],['transform_xy
,polar_to_xy
],['plot_format
,geomview
]) ;
11 /* plot of z^
(1/2)...
*/
12 plot3d
(r^
.5*cos
(th/2),[r
,0,1],[th
,0,6*%pi
],['grid
,12,80],['transform_xy
,polar_to_xy
],['plot_format
,xmaxima
]) ;
15 plot3d
([cos
(x)*(3+y
*cos
(x/2)),sin
(x)*(3+y
*cos
(x/2)),y
*sin
(x/2)],[x
,-%pi
,%pi
],[y
,-
1,1],['grid
,50,15]) ;
18 plot3d
([5*cos
(x)*(cos(x/2)*cos
(y)+sin
(x/2)*sin
(2*y
)+3.0) -
10.0,
19 -
5*sin
(x)*(cos(x/2)*cos
(y)+sin
(x/2)*sin
(2*y
)+3.0),
20 5*(-sin(x/2)*cos
(y)+cos
(x/2)*sin
(2*y
))],[x
,-%pi
,%pi
],[y
,-%pi
,%pi
],
23 plot3d
([cos
(y)*(10.0
+6*cos
(x)),
24 sin
(y)*(10.0
+6*cos
(x)),
25 -
6*sin
(x)], [x
,0,2*%pi
],[y
,0,2*%pi
],['grid
,40,40]) ;
28 (defun ensure-string (x)
31 ((symbolp x
) (print-invert-case (stripdollar x
)))
32 (t (maybe-invert-string-case (string (implode (strgrind x
)))))))
35 (if (and ($listp x
) ($listp y
))
36 (cons '(mlist) (loop for w in
(cdr x
) for u in
(cdr y
) collect w collect u
))
37 (merror (intl:gettext
"join: both arguments must be lists."))))
39 (defun coerce-float (x) ($float
(meval* x
)))
41 (defvar *maxima-plotdir
* "")
42 (declare-top (special *maxima-tempdir
* *maxima-prefix
*))
44 ;; *ROT* AND FRIENDS ($ROT, $ROTATE_PTS, $ROTATE_LIST) CAN PROBABLY GO AWAY !!
45 ;; THEY ARE UNDOCUMENTED AND UNUSED !!
46 (defvar *rot
* (make-array 9 :element-type
'flonum
))
49 ;; Global plot options list. It is not a Maxima variable, to discourage
50 ;; users from changing it directly; it should be changed via set_plot_option
52 (defvar *plot-options
*
54 ,(if (string= *autoconf-win32
* "true")
57 :grid
(30 30) :run_viewer t
:axes t
58 ;; With adaptive plotting, 29 nticks should be enough; adapt_depth
59 ;; controls the number of splittings adaptive-plotting will do.
60 :nticks
29 :adapt_depth
5
61 :color
($blue $red $green $magenta $black $cyan
)
62 :point_type
($bullet $box $triangle $plus $times $asterisk
)
63 :palette
(((mlist) $gradient $green $cyan $blue $violet
)
64 ((mlist) $gradient $magenta $violet $blue $cyan $green $yellow
65 $orange $red $brown $black
))
66 :gnuplot_preamble
"" :gnuplot_term $default
))
71 ,(if (string= *autoconf-win32
* "true")
75 ;; $plot_realpart option is false by default but *plot-realpart* is true
76 ;; because coerce-float-fun is used outside of plot package too.
77 (defvar *plot-realpart
* t
)
79 (defun maybe-realpart (x)
82 (if (zerop1 ($imagpart x
))
86 (defvar *missing-data-indicator
* "NaN")
88 (defvar *gnuplot-stream
* nil
)
89 (defvar *gnuplot-command
* "")
91 (defvar $gnuplot_command
(if (string= *autoconf-win32
* "true")
95 (defun start-gnuplot-process (path)
96 #+clisp
(setq *gnuplot-stream
* (ext:make-pipe-output-stream path
))
97 #+lispworks
(setq *gnuplot-stream
* (system:open-pipe path
))
98 #+cmu
(setq *gnuplot-stream
*
99 (ext:process-input
(ext:run-program path nil
:input
:stream
100 :output nil
:wait nil
)))
101 #+scl
(setq *gnuplot-stream
*
102 (ext:process-input
(ext:run-program path nil
:input
:stream
103 :output nil
:wait nil
)))
104 #+sbcl
(setq *gnuplot-stream
*
105 (sb-ext:process-input
(sb-ext:run-program path nil
107 :output nil
:wait nil
109 #+gcl
(setq *gnuplot-stream
*
110 (open (concatenate 'string
"| " path
) :direction
:output
))
112 (setq *gnuplot-stream
* (ext:run-program path nil
:input
:stream
:output t
:error
:output
:wait nil
)))
113 #+ccl
(setf *gnuplot-stream
*
114 (ccl:external-process-input-stream
115 (ccl:run-program path nil
116 :wait nil
:output nil
118 #+allegro
(setf *gnuplot-stream
* (excl:run-shell-command
119 path
:input
:stream
:output nil
:wait nil
))
120 #+abcl
(setq *gnuplot-stream
* (system::process-input
(system::run-program path nil
:wait nil
)))
121 #-
(or clisp cmu sbcl gcl scl lispworks ecl ccl allegro abcl
)
122 (merror (intl:gettext
"plotting: I don't know how to tell this Lisp to run Gnuplot."))
124 ;; set mouse must be the first command send to gnuplot
125 (send-gnuplot-command "set mouse"))
127 (defun check-gnuplot-process ()
128 (if (null *gnuplot-stream
*)
129 (start-gnuplot-process $gnuplot_command
)))
131 (defun $gnuplot_close
()
132 (stop-gnuplot-process)
135 (defun $gnuplot_start
()
136 (check-gnuplot-process)
139 (defun $gnuplot_restart
()
143 (defun stop-gnuplot-process ()
144 (unless (null *gnuplot-stream
*)
146 (close *gnuplot-stream
*)
147 (setq *gnuplot-stream
* nil
))))
149 (defun send-gnuplot-command (command)
150 (if (null *gnuplot-stream
*)
151 (start-gnuplot-process $gnuplot_command
))
152 (when (not (null command
))
153 (format *gnuplot-stream
* "~a ~%" command
)
154 (force-output *gnuplot-stream
*)))
156 (defun $gnuplot_reset
()
157 (send-gnuplot-command "unset output")
158 (send-gnuplot-command "reset"))
160 (defun $gnuplot_replot
(&optional s
)
161 (if (null *gnuplot-stream
*)
162 (merror (intl:gettext
"gnuplot_replot: Gnuplot is not running.")))
164 (send-gnuplot-command "replot"))
166 (send-gnuplot-command s
)
167 (send-gnuplot-command "replot"))
169 (merror (intl:gettext
"gnuplot_replot: argument, if present, must be a string; found: ~M") s
)))
172 ;; allow this to be set in a system init file (sys-init.lsp)
174 (defun $get_plot_option
(&optional name n
)
175 (let ((options '((mlist))) key value
)
176 ;; converts the options property list into a Maxima list
177 (dotimes (i (/ (length *plot-options
*) 2))
179 (intern (concatenate 'string
"$"
180 (symbol-name (nth (* i
2) *plot-options
*)))))
181 (setq value
(nth (+ (* i
2) 1) *plot-options
*))
183 (push (cons '(mlist) (cons key value
)) options
)
184 (push (list '(mlist) key value
) options
)))
186 (loop for v in
(cdr options
)
187 when
(eq (second v
) name
) do
(return (if n
(nth n v
) v
)))
190 (defun quote-strings (opt)
193 (format nil
"~s" opt
)
195 (cons (quote-strings (car opt
))
196 (quote-strings (cdr opt
)))))
198 (defun get-plot-option-string (option &optional
(index 1))
199 (let* ((val ($get_plot_option option
2))
200 (val-list (if ($listp val
)
203 (ensure-string (nth (mod (- index
1) (length val-list
)) val-list
))))
205 (defun $set_plot_option
(&rest value
)
206 (setq *plot-options
* (plot-options-parser value
*plot-options
*))
209 (defun $remove_plot_option
(name)
212 ($adapt_depth
:adapt_depth
) ($axes
:axes
) ($azimuth
:azimuth
)
213 ($box
:box
) ($color
:color
) ($color_bar
:color_bar
)
214 ($color_bar_tics
:color_bar_tics
) ($elevation
:elevation
)
215 ($grid
:grid
) ($grid2d
:grid2d
) ($iterations
:iterations
)
216 ($label
:label
) ($legend
:legend
) ($logx
:logx
) ($logy
:logy
)
217 ($mesh_lines_color
:mesh_lines_color
) ($nticks
:nticks
)
218 ($palette
:palette
) ($plot_format
:plot_format
)
219 ($plot_realpart
:plot_realpart
) ($point_type
:point_type
)
220 ($pdf_file
:pdf_file
) ($png_file
:png_file
) ($ps_file
:ps_file
)
221 ($run_viewer
:run_viewer
) ($same_xy
:samexy
)
222 ($same_xyz
:same_xyz
) ($style
:style
) ($svg_file
:svg_file
)
223 ($t
:t
) ($title
:title
) ($transform_xy
:transform_xy
)
224 ($x
:x
) ($xbounds
:xbounds
) ($xlabel
:xlabel
)
225 ($xtics
:xtics
) ($xvar
:xvar
) ($xy_scale
:xy_scale
)
226 ($y
:y
) ($ybounds
:ybounds
) ($ylabel
:ylabel
) ($ytics
:ytics
)
227 ($yvar
:yvar
) ($yx_ratio
:yx_ratio
)
228 ($z
:z
) ($zlabel
:zlabel
) ($zmin
:zmin
) ($ztics
:ztics
)
229 ($gnuplot_4_0
:gnuplot_4_0
)
230 ($gnuplot_curve_titles
:gnuplot_curve_titles
)
231 ($gnuplot_curve_styles
:gnuplot_curve_styles
)
232 ($gnuplot_default_term_command
:gnuplot_default_term_command
)
233 ($gnuplot_dumb_term_command
:gnuplot_dumb_term_command
)
234 ($gnuplot_out_file
:gnuplot_out_file
)
235 ($gnuplot_pm3d
:gnuplot_pm3d
)
236 ($gnuplot_preamble
:gnuplot_preamble
)
237 ($gnuplot_postamble
:gnuplot_postamble
)
238 ($gnuplot_pdf_term_command
:gnuplot_pdf_term_command
)
239 ($gnuplot_png_term_command
:gnuplot_png_term_command
)
240 ($gnuplot_ps_term_command
:gnuplot_ps_term_command
)
241 ($gnuplot_svg_term_command
:gnuplot_svg_term_command
)
242 ($gnuplot_term
:gnuplot_term
))))
244 (defun get-gnuplot-term (term)
245 (let* ((sterm (string-downcase (ensure-string term
)))
246 (pos (search " " sterm
)))
251 (defvar $pstream nil
)
253 (defun print-pt1 (f str
)
256 (format str
"~a " *missing-data-indicator
*)))
258 (defstruct (polygon (:type list
)
259 (:constructor %make-polygon
(pts edges
)))
260 (dummy '($polygon simp
))
265 #-gcl
(:compile-toplevel
:execute
)
267 (defmacro z-pt
(ar i
) `(aref ,ar
(the fixnum
(+ 2 (* ,i
3)))))
268 (defmacro y-pt
(ar i
) `(aref ,ar
(the fixnum
(1+ (* ,i
3)))))
269 (defmacro x-pt
(ar i
) `(aref ,ar
(the fixnum
(* ,i
3))))
270 (defmacro rot
(m i j
) `(aref ,m
(the fixnum
(+ ,i
(the fixnum
(* 3 ,j
))))))
272 (defmacro print-pt
(f)
273 `(print-pt1 ,f $pstream
))
275 (defmacro make-polygon
(a b
)
276 `(list '($polygon
) ,a
,b
)))
278 (defun draw3d (f minx maxx miny maxy nxint nyint
)
279 (let* ((epsx (/ (- maxx minx
) nxint
))
281 (epsy (/ (- maxy miny
) nyint
))
285 (ar (make-array (+ 12 ; 12 for axes
286 (* 3 nx ny
)) :fill-pointer
(* 3 nx ny
)
287 :element-type t
:adjustable t
)))
288 (declare (type flonum x y epsy epsx
)
290 (type (cl:array t
) ar
))
292 initially
(setq y miny
)
298 (setf (z-pt ar l
) (funcall f x y
))
303 (make-polygon ar
(make-grid-vertices nxint nyint
))))
305 ;; The following is 3x2 = 6 rectangles
306 ;; call (make-vertices 3 2)
307 ;; there are 4x3 = 12 points.
308 ;; ordering is x0,y0,z0,x1,y1,z1,....,x11,y11,z11
315 (defun make-grid-vertices (nx ny
)
316 (declare (fixnum nx ny
))
317 (let* ((tem (make-array (+ 15 (* 5 nx ny
)) :fill-pointer
(* 5 nx ny
)
319 :element-type
'(mod 65000)))
324 (declare (fixnum i nxpt m
)
325 (type (cl:array
(mod 65000)) tem
))
326 (loop for k below
(length tem
)
328 (setf (aref tem k
) i
)
329 (setf (aref tem
(incf k
))
331 (setf (aref tem
(incf k
))
333 (setf (aref tem
(incf k
)) i
)
334 (setf (aref tem
(incf k
)) 0) ;place for max
342 (defun $rotation1
(phi th
)
343 (let ((sinph (sin phi
))
348 ((mlist simp
) ,(* cosph costh
)
349 ,(* -
1.0 cosph sinth
)
351 ((mlist simp
) ,sinth
,costh
0.0)
352 ((mlist simp
) ,(- (* sinph costh
))
356 ;; pts is a vector of bts [x0,y0,z0,x1,y1,z1,...] and each tuple xi,yi,zi is rotated
357 #-abcl
(defun $rotate_pts
(pts rotation-matrix
)
358 (or ($matrixp rotation-matrix
) (merror (intl:gettext
"rotate_pts: second argument must be a matrix.")))
361 (x 0.0) (y 0.0) (z 0.0)
363 (declare (type flonum x y z
))
364 (declare (type (cl:array flonum
) rot
))
365 ($copy_pts rotation-matrix
*rot
* 0)
370 (setq x
(aref pts j
))
371 (setq y
(aref pts
(+ j
1)))
372 (setq z
(aref pts
(+ j
2)))
373 (loop for i below
3 with a of-type flonum
= 0.0
375 (setq a
(* x
(aref rot
(+ (* 3 i
) 0))))
376 (setq a
(+ a
(* y
(aref rot
(+ (* 3 i
) 1)))))
377 (setq a
(+ a
(* z
(aref rot
(+ (* 3 i
) 2)))))
378 (setf (aref pts
(+ j i
)) a
))
381 (defun $rotate_list
(x)
382 (cond ((and ($listp x
) (not (mbagp (second x
))))
383 ($list_matrix_entries
(ncmul2 $rot x
)))
384 ((mbagp x
) (cons (car x
) (mapcar '$rotate_list
(cdr x
))))))
386 (defun $get_range
(pts k
&aux
(z 0.0) (max most-negative-flonum
) (min most-positive-flonum
))
387 (declare (type flonum z max min
))
388 (declare (type (vector flonum
) pts
))
389 (loop for i from k below
(length pts
) by
3
390 do
(setq z
(aref pts i
))
391 (cond ((< z min
) (setq min z
)))
392 (cond ((> z max
) (setq max z
))))
393 (list min max
(- max min
)))
395 (defun $polar_to_xy
(pts &aux
(r 0.0) (th 0.0))
396 (declare (type flonum r th
))
397 (declare (type (cl:array t
) pts
))
398 (assert (typep pts
'(vector t
)))
399 (loop for i below
(length pts
) by
3
400 do
(setq r
(aref pts i
))
401 (setq th
(aref pts
(+ i
1)))
402 (setf (aref pts i
) (* r
(cos th
)))
403 (setf (aref pts
(+ i
1)) (* r
(sin th
)))))
405 ;; Transformation from spherical coordinates to rectangular coordinates,
406 ;; to be used in plot3d. Example of its use:
407 ;; plot3d (expr, [th, 0, %pi], [ph, 0, 2*%pi], [transform_xy, spherical_to_xyz])
408 ;; where expr gives the value of r in terms of the inclination (th)
411 (defun $spherical_to_xyz
(pts &aux
(r 0.0) (th 0.0) (ph 0.0))
412 (declare (type flonum r th ph
))
413 (declare (type (cl:array t
) pts
))
414 (assert (typep pts
'(vector t
)))
415 (loop for i below
(length pts
) by
3
416 do
(setq th
(aref pts i
))
417 (setq ph
(aref pts
(+ i
1)))
418 (setq r
(aref pts
(+ i
2)))
419 (setf (aref pts i
) (* r
(sin th
) (cos ph
)))
420 (setf (aref pts
(+ i
1)) (* r
(sin th
) (sin ph
)))
421 (setf (aref pts
(+ i
2)) (* r
(cos th
)))))
424 ;; return a function suitable for the transform function in plot3d.
425 ;; FX, FY, and FZ are functions of three arguments.
426 (defun $make_transform
(lvars fx fy fz
)
427 (setq fx
(coerce-float-fun fx lvars
))
428 (setq fy
(coerce-float-fun fy lvars
))
429 (setq fz
(coerce-float-fun fz lvars
))
430 (let ((sym (gensym "transform")))
431 (setf (symbol-function sym
)
432 #'(lambda (pts &aux
(x1 0.0)(x2 0.0)(x3 0.0))
433 (declare (type flonum x1 x2 x3
))
434 (declare (type (cl:array t
) pts
))
435 (loop for i below
(length pts
) by
3
437 (setq x1
(aref pts i
))
438 (setq x2
(aref pts
(+ i
1)))
439 (setq x3
(aref pts
(+ i
2)))
440 (setf (aref pts i
) (funcall fx x1 x2 x3
))
441 (setf (aref pts
(+ 1 i
)) (funcall fy x1 x2 x3
))
442 (setf (aref pts
(+ 2 i
)) (funcall fz x1 x2 x3
)))))))
444 ;; Return value is a Lisp function which evaluates EXPR to a float.
445 ;; COERCE-FLOAT-FUN always returns a function and never returns a symbol,
446 ;; even if EXPR is a symbol.
448 ;; Following cases are recognized:
450 ;; name of a Lisp function
451 ;; name of a Maxima function
452 ;; name of a DEFMSPEC function
453 ;; name of a Maxima macro
454 ;; a string which is the name of a Maxima operator (e.g., "!")
455 ;; name of a simplifying function
456 ;; EXPR is a Maxima lambda expression
457 ;; EXPR is a general Maxima expression
459 ;; %COERCE-FLOAT-FUN is the main internal routine for this.
460 ;; COERCE-FLOAT-FUN is the user interface for creating a function that
461 ;; returns floats. COERCE-BFLOAT-FUN is the same, except bfloats are
463 (defun %coerce-float-fun
(float-fun expr
&optional lvars
)
464 (cond ((and (consp expr
) (functionp expr
))
465 (let ((args (if lvars
(cdr lvars
) (list (gensym)))))
466 (coerce-lisp-function-or-lisp-lambda args expr
:float-fun float-fun
)))
467 ;; expr is a string which names an operator
468 ;; (e.g. "!" "+" or a user-defined operator)
469 ((and (stringp expr
) (getopr0 expr
))
470 (let ((a (if lvars lvars
`((mlist) ,(gensym)))))
471 (%coerce-float-fun float-fun
`(($apply
) ,(getopr0 expr
) ,a
) a
)))
472 ((and (symbolp expr
) (not (member expr lvars
)) (not ($constantp expr
)))
475 (let ((args (if lvars
(cdr lvars
) (list (gensym)))))
476 (coerce-lisp-function-or-lisp-lambda args expr
:float-fun float-fun
)))
478 ;; expr is name of a Maxima function defined by := or
482 ((mexpr (mget expr
'mexpr
))
483 (args (cdr (second mexpr
))))
484 (coerce-maxima-function-or-maxima-lambda args expr
:float-fun float-fun
)))
487 ;; expr is the name of a function defined by defmspec
489 ;; expr is the name of a Maxima macro defined by ::=
491 ;; expr is the name of a simplifying function, and the
492 ;; simplification property is associated with the noun
494 (get ($nounify expr
) 'operators
)
495 ;; expr is the name of a simplifying function, and the
496 ;; simplification property is associated with the verb
498 (get ($verbify expr
) 'operators
))
499 (let ((a (if lvars lvars
`((mlist) ,(gensym)))))
500 (%coerce-float-fun float-fun
`(($apply
) ,expr
,a
) a
)))
502 (merror (intl:gettext
"COERCE-FLOAT-FUN: no such Lisp or Maxima function: ~M") expr
))))
504 ((and (consp expr
) (eq (caar expr
) 'lambda
))
505 (let ((args (cdr (second expr
))))
506 (coerce-maxima-function-or-maxima-lambda args expr
:float-fun float-fun
)))
509 (let* ((vars (or lvars
($sort
($listofvars expr
))))
510 (subscripted-vars ($sublist vars
'((lambda) ((mlist) $x
) ((mnot) (($atom
) $x
)))))
511 gensym-vars save-list-gensym subscripted-vars-save
512 subscripted-vars-mset subscripted-vars-restore
)
514 ;; VARS and SUBSCRIPTED-VARS are Maxima lists. Other lists are
516 (when (cdr subscripted-vars
)
517 (setq gensym-vars
(mapcar #'(lambda (ign) (declare (ignore ign
)) (gensym))
518 (cdr subscripted-vars
)))
519 (mapcar #'(lambda (a b
) (setq vars
(subst b a vars
:test
'equal
)))
520 (cdr subscripted-vars
) gensym-vars
)
522 ;; This stuff about saving and restoring array variables
523 ;; should go into MBINDING, and the lambda expression
524 ;; constructed below should call MBINDING. (At present
525 ;; MBINDING barfs on array variables.)
526 (setq save-list-gensym
(gensym))
527 (setq subscripted-vars-save
528 (mapcar #'(lambda (a) `(push (meval ',a
) ,save-list-gensym
))
529 (cdr subscripted-vars
)))
530 (setq subscripted-vars-mset
531 (mapcar #'(lambda (a b
) `(mset ',a
,b
))
532 (cdr subscripted-vars
) gensym-vars
))
533 (setq subscripted-vars-restore
534 (mapcar #'(lambda (a) `(mset ',a
(pop ,save-list-gensym
)))
535 (reverse (cdr subscripted-vars
)))))
539 (declare (special ,@(cdr vars
) errorsw
))
541 ;; Nothing interpolated here when there are no subscripted
543 ,@(if save-list-gensym
`((declare (special ,save-list-gensym
))))
545 ;; Nothing interpolated here when there are no subscripted
547 ,@(if (cdr subscripted-vars
)
548 `((progn (setq ,save-list-gensym nil
)
549 ,@(append subscripted-vars-save subscripted-vars-mset
))))
551 (let (($ratprint nil
)
552 ;; We don't want to set $numer to T when coercing
553 ;; to a bigfloat. By doing so, things like
554 ;; log(400)^400 get converted to double-floats,
555 ;; which causes a double-float overflow. But the
556 ;; whole point of coercing to bfloat is to use
557 ;; bfloats, not doubles.
559 ;; Perhaps we don't even need to do this for
560 ;; double-floats? It would be nice to remove
561 ;; this. For backward compatibility, we bind
562 ;; numer to T if we're not trying to bfloat.
563 ($numer
,(not (eq float-fun
'$bfloat
)))
567 (declare (special errcatch
))
568 ;; Catch any errors from evaluating the
569 ;; function. We're assuming that if an error
570 ;; is caught, the result is not a number. We
571 ;; also assume that for such errors, it's
572 ;; because the function is not defined there,
573 ;; not because of some other maxima error.
575 ;; GCL 2.6.2 has handler-case but not quite ANSI yet.
580 (,float-fun
(maybe-realpart (meval* ',expr
))))
581 ;; Should we just catch all errors here? It is
582 ;; rather nice to only catch errors we care
583 ;; about and let other errors fall through so
584 ;; that we don't pretend to do something when
585 ;; it is better to let the error through.
586 (arithmetic-error () t
)
587 (maxima-$error
() t
))
591 (,float-fun
(maybe-realpart (meval* ',expr
))))
595 ;; Nothing interpolated here when there are no
596 ;; subscripted variables.
597 ,@(if (cdr subscripted-vars
) `((progn ,@subscripted-vars-restore
)))
602 (defun coerce-float-fun (expr &optional lvars
)
603 (%coerce-float-fun
'$float expr lvars
))
605 (defun coerce-bfloat-fun (expr &optional lvars
)
606 (%coerce-float-fun
'$bfloat expr lvars
))
608 (defun coerce-maxima-function-or-maxima-lambda (args expr
&key
(float-fun '$float
))
609 (let ((gensym-args (loop for x in args collect
(gensym))))
611 `(lambda ,gensym-args
(declare (special ,@gensym-args
))
612 (let* (($ratprint nil
)
617 (declare (special errcatch
))
618 ;; Just always try to convert the result to a float,
619 ;; which handles things like $%pi. See also BUG
621 ;; http://sourceforge.net/tracker/?func=detail&atid=104933&aid=2880115&group_id=4933
623 ;; Should we use HANDLER-CASE like we do above in
624 ;; %coerce-float-fun? Seems not necessary for what we want
628 (maybe-realpart (mapply ',expr
(list ,@gensym-args
) t
))))))
631 ;; Same as above, but call APPLY instead of MAPPLY.
633 (defun coerce-lisp-function-or-lisp-lambda (args expr
&key
(float-fun '$float
))
634 (let ((gensym-args (loop for x in args collect
(gensym))))
636 `(lambda ,gensym-args
(declare (special ,@gensym-args
))
637 (let* (($ratprint nil
)
640 (result (maybe-realpart (apply ',expr
(list ,@gensym-args
)))))
641 ;; Always use $float. See comment for
642 ;; coerce-maxima-function-ormaxima-lambda above.
643 (,float-fun result
)))
646 (defmacro zval
(points verts i
) `(aref ,points
(+ 2 (* 3 (aref ,verts
,i
)))))
648 ;;sort the edges array so that drawing the edges will happen from the back towards
649 ;; the front. The if n==4 the edges array coming in looks like
650 ;; v1 v2 v3 v4 0 w1 w2 w3 w4 0 ...
651 ;; where vi,wi are indices pointint into the points array specifiying a point
652 ;; in 3 space. After the sorting is done, the 0 is filled in with the vertex
653 ;; which is closer to us (ie highest z component after rotating towards the user)
654 ;; and this is then they are sorted in groups of 5.
655 (defun sort-ngons (points edges n
&aux lis
)
656 (declare (type (cl:array
(flonum)) points
)
657 (type (cl:array
(mod 65000)) edges
)
659 (let ((new (make-array (length edges
) :element-type
(array-element-type edges
)))
665 (leng (length edges
))
667 (declare (type (cl:array
(mod 65000)) new
)
668 (fixnum i leng n1 at
)
670 (declare (type flonum z z1
))
673 (loop for i0 below leng by
(+ n
1)
677 (setq z
(zval points edges i
))
680 do
(if (> (setq z1
(zval points edges i
)) z
)
681 (setq z z1 at
(aref edges i
) ))
684 (setf (aref edges i
) at
)
685 collect
(cons z i0
)))
686 (setq lis
(sort lis
#'alphalessp
:key
#'car
))
690 (loop for j from
(cdr v
)
692 do
(setf (aref new i
) (aref edges j
))
695 (copy-array-portion edges new
0 0 (length edges
))
698 (defun copy-array-portion (ar1 ar2 i1 i2 n1
)
699 (declare (fixnum i1 i2 n1
))
700 (loop while
(>= (setq n1
(- n1
1)) 0)
701 do
(setf (aref ar1 i1
) (aref ar2 i2
))
706 (defun $concat_polygons
(pl1 pl2
&aux tem new
)
710 for l
= (+ (length v
) (length w
))
711 do
(setq tem
(make-array l
712 :element-type
(array-element-type v
)
717 (setq new
(make-polygon (first new
) (second new
)) )
719 (copy-array-portion (polygon-pts pl1
) (polygon-pts new
)
720 0 0 (length (polygon-pts pl1
)))
721 (copy-array-portion (polygon-pts pl2
) (polygon-pts new
)
722 (length (polygon-pts pl1
))
723 0 (length (polygon-pts pl2
)))
724 (copy-array-portion (polygon-edges pl1
) (polygon-edges new
)
725 0 0 (length (polygon-edges pl1
)))
726 (loop for i from
(length (polygon-edges pl1
))
727 for j from
0 below
(length (polygon-edges pl2
))
728 with lpts1
= (length (polygon-pts pl1
))
729 with ar2
= (polygon-edges pl2
)
730 with arnew
= (polygon-edges new
)
731 do
(setf (aref arnew i
) (+ lpts1
(aref ar2 j
)))))
733 (defun $copy_pts
(lis vec start
)
734 (declare (fixnum start
))
736 (declare (type (cl:array flonum
) tem
))
738 (or (typep lis
'flonum
) (setq lis
(float lis
)))
739 (setf (aref tem start
) lis
)
742 ($copy_pts
(cdr lis
) vec
($copy_pts
(car lis
) vec start
)))
743 ((symbolp lis
) start
)
744 (t (merror (intl:gettext
"copy_pts: unrecognized first argument: ~M") lis
)))))
746 ;; parametric ; [parametric,xfun,yfun,[t,tlow,thigh],[nticks ..]]
747 ;; the rest of the parametric list after the list will add to the plot options
749 ;; TODO: This should be removed after some time, once adaptive
750 ;; plotting has received enough testing and debugging.
751 (defmvar $use_adaptive_parametric_plot t
752 "If true, parametric plots use adaptive plotting")
754 (defun draw2d-parametric (param range1 plot-options
&aux range tem
)
755 (cond ((and ($listp
(setq tem
(nth 4 param
)))
757 (eql ($length tem
) 3))
758 ;; sure looks like a range
759 (setq range
(check-range tem
))))
762 (if range1
(cons range1
(cddddr param
)) (cddddr param
))
764 (nticks (getf options
:nticks
))
765 (trange (or (cddr range
) (getf options
:t
)))
766 (tvar (or (cadr range
) '$t
))
767 (xrange (or (getf options
:x
) (getf options
:xbounds
)))
768 (yrange (or (getf options
:y
) (getf options
:ybounds
)))
769 (tmin (coerce-float (first trange
)))
770 (tmax (coerce-float (second trange
)))
771 (xmin (coerce-float (first xrange
)))
772 (xmax (coerce-float (second xrange
)))
773 (ymin (coerce-float (first yrange
)))
774 (ymax (coerce-float (second yrange
)))
775 (x 0.0) ; have to initialize to some floating point..
778 (eps (/ (- tmax tmin
) (- nticks
1)))
779 f1 f2 in-range-y in-range-x in-range last-ok
781 (declare (type flonum x y tt ymin ymax xmin xmax tmin tmax eps
))
782 (setq f1
(coerce-float-fun (third param
) `((mlist), tvar
)))
783 (setq f2
(coerce-float-fun (fourth param
) `((mlist), tvar
)))
787 (setq x
(funcall f1 tt
))
788 (setq y
(funcall f2 tt
))
789 (setq in-range-y
(and (<= y ymax
) (>= y ymin
)))
790 (setq in-range-x
(and (<= x xmax
) (>= x xmin
)))
791 (setq in-range
(and in-range-x in-range-y
))
792 when
(and (not in-range
) (not last-ok
))
793 collect
'moveto and collect
'moveto
795 (setq last-ok in-range
)
796 collect
(if in-range-x x
(if (> x xmax
) xmax xmin
))
797 collect
(if in-range-y y
(if (> y ymax
) ymax ymin
))
798 when
(>= tt tmax
) do
(loop-finish)
799 do
(setq tt
(+ tt eps
))
800 (if (>= tt tmax
) (setq tt tmax
))
804 (defun draw2d-parametric-adaptive (param range1 plot-options
&aux range tem
)
805 (cond ((and ($listp
(setq tem
(nth 4 param
)))
807 (eql ($length tem
) 3))
808 ;; sure looks like a range
809 (setq range
(check-range tem
))))
812 (if range1
(cons range1
(cddddr param
)) (cddddr param
))
814 (nticks (getf options
:nticks
))
815 (trange (or (cddr range
) (getf options
:t
)))
816 (tvar (or (cadr range
) '$t
))
817 (xrange (or (getf options
:x
) (getf options
:xbounds
)))
818 (yrange (or (getf options
:y
) (getf options
:ybounds
)))
819 (tmin (coerce-float (first trange
)))
820 (tmax (coerce-float (second trange
)))
821 (xmin (coerce-float (first xrange
)))
822 (xmax (coerce-float (second xrange
)))
823 (ymin (coerce-float (first yrange
)))
824 (ymax (coerce-float (second yrange
)))
826 (declare (type flonum ymin ymax xmin xmax tmin tmax
))
827 (setq f1
(coerce-float-fun (third param
) `((mlist), tvar
)))
828 (setq f2
(coerce-float-fun (fourth param
) `((mlist), tvar
)))
830 (let ((n-clipped 0) (n-non-numeric 0)
831 (t-step (/ (- tmax tmin
) (coerce-float nticks
) 2))
832 t-samples x-samples y-samples result
)
833 ;; Divide the range into 2*NTICKS regions that we then
834 ;; adaptively plot over.
835 (dotimes (k (1+ (* 2 nticks
)))
836 (let ((tpar (+ tmin
(* k t-step
))))
837 (push tpar t-samples
)
838 (push (funcall f1 tpar
) x-samples
)
839 (push (funcall f2 tpar
) y-samples
)))
840 (setf t-samples
(nreverse t-samples
))
841 (setf x-samples
(nreverse x-samples
))
842 (setf y-samples
(nreverse y-samples
))
844 ;; Adaptively plot over each region
845 (do ((t-start t-samples
(cddr t-start
))
846 (t-mid (cdr t-samples
) (cddr t-mid
))
847 (t-end (cddr t-samples
) (cddr t-end
))
848 (x-start x-samples
(cddr x-start
))
849 (x-mid (cdr x-samples
) (cddr x-mid
))
850 (x-end (cddr x-samples
) (cddr x-end
))
851 (y-start y-samples
(cddr y-start
))
852 (y-mid (cdr y-samples
) (cddr y-mid
))
853 (y-end (cddr y-samples
) (cddr y-end
)))
858 (cddr (adaptive-parametric-plot
860 (car t-start
) (car t-mid
) (car t-end
)
861 (car x-start
) (car x-mid
) (car x-end
)
862 (car y-start
) (car y-mid
) (car y-end
)
863 (getf options
:adapt_depth
)
865 (adaptive-parametric-plot
867 (car t-start
) (car t-mid
) (car t-end
)
868 (car x-start
) (car x-mid
) (car x-end
)
869 (car y-start
) (car y-mid
) (car y-end
)
870 (getf options
:adapt_depth
)
872 ;; Fix up out-of-range values and clobber non-numeric values.
873 (do ((x result
(cddr x
))
874 (y (cdr result
) (cddr y
)))
876 (if (and (numberp (car x
)) (numberp (car y
)))
877 (unless (and (<= ymin
(car y
) ymax
)
878 (<= xmin
(car x
) xmax
))
880 (setf (car x
) 'moveto
)
881 (setf (car y
) 'moveto
))
884 (setf (car x
) 'moveto
)
885 (setf (car y
) 'moveto
))))
886 ;; Filter out any MOVETO's which do not precede a number.
887 ;; Code elsewhere in this file expects MOVETO's to
888 ;; come in pairs, so leave two MOVETO's before a number.
889 (let ((n (length result
)))
894 (eq (nth i result
) 'moveto
)
895 (eq (nth (1+ i
) result
) 'moveto
)
898 (eq (nth (+ i
2) result
) 'moveto
)))
899 (setf (nth i result
) nil
)
900 (setf (nth (1+ i
) result
) nil
))))
902 (let ((result-sans-nil (delete nil result
)))
903 (if (null result-sans-nil
)
906 (mtell (intl:gettext
"plot2d: all values were clipped.~%")))
909 "plot2d: expression evaluates to non-numeric value everywhere in plotting range.~%")))
912 "plot2d: all values are non-numeric, or clipped.~%"))))
914 (if (> n-non-numeric
0)
916 "plot2d: expression evaluates to non-numeric value somewhere in plotting range.~%")))
918 (mtell (intl:gettext
"plot2d: some values were clipped.~%")))))
919 (cons '(mlist) result-sans-nil
)))))
921 ;; draw2d-discrete. Accepts [discrete,[x1,x2,...],[y1,y2,...]]
922 ;; or [discrete,[[x1,y1]...] and returns [x1,y1,...] or nil, if
923 ;; non of the points have real values.
924 ;; Currently any options given are being ignored, because there
925 ;; are no options specific to the generation of the points.
926 (defun draw2d-discrete (f)
927 (let ((x (third f
)) (y (fourth f
)) data gaps
)
929 (($listp x
) ; x is a list
931 (($listp
(cadr x
)) ; x1 is a list
933 ((= (length (cadr x
)) 3) ; x1 is a 2D point
934 (setq data
(parse-points-xy x
)))
935 (t ; x1 is not a 2D point
936 (merror (intl:gettext
"draw2d-discrete: Expecting a point with 2 coordinates; found ~M~%") (cadr x
)))))
937 (t ; x1 is not a list
939 (($listp y
) ; y is a list
941 ((symbolp (coerce-float (cadr y
))); y is an option
942 (setq data
(parse-points-y x
)))
943 (t ; y is not an option
945 (($listp
(cadr y
)) ; y1 is a list
946 (merror (intl:gettext
"draw2d-discrete: Expecting a y coordinate; found ~M~%") (cadr y
)))
949 ((= (length x
) (length y
)) ; case [x][y]
950 (setq data
(parse-points-x-y x y
)))
952 (merror (intl:gettext
"draw2d-discrete: The number of x and y coordinates do not match.~%")))))))))
954 (setq data
(parse-points-y x
)))))))
956 (merror (intl:gettext
"draw2d-discrete: Expecting a list of x coordinates or points; found ~M~%") x
)))
958 ;; checks for non-real values
961 (setq gaps
(count-if #'(lambda (x) (eq x
'moveto
)) data
))
963 ;; some points have non-real values
964 (mtell (intl:gettext
"Warning: excluding ~M points with non-numerical values.~%") (/ gaps
2))))
966 ;; none of the points have real values
967 (mtell (intl:gettext
"Warning: none of the points have numerical values.~%"))
971 ;; Two lists [x1...xn] and [y1...yn] are joined as
972 ;; [x1 y1...xn yn], converting all expressions to real numbers.
973 ;; If either xi or yi are not real, both are replaced by 'moveto
974 (defun parse-points-x-y (x y
)
975 (do ((a (rest x
) (cdr a
))
978 ((null b
) (cons '(mlist) (reverse c
)))
979 (setq af
(coerce-float (car a
)))
980 (setq bf
(coerce-float (car b
)))
982 ((or (not (realp af
)) (not (realp bf
)))
983 (setq c
(cons 'moveto
(cons 'moveto c
))))
985 (setq c
(cons bf
(cons af c
)))))))
987 ;; One list [y1...yn] becomes the list [1 y1...n yn],
988 ;; converting all expressions to real numbers.
989 ;; If yi is not real, both i and yi are replaced by 'moveto
990 (defun parse-points-y (y)
994 ((null b
) (cons '(mlist) (reverse c
)))
995 (setq bf
(coerce-float (car b
)))
998 (setq c
(cons 'moveto
(cons 'moveto c
))))
1000 (setq c
(cons bf
(cons a c
)))))))
1002 ;; List [[x1,y1]...[xn,yn]] is transformed into
1003 ;; [x1 y1...xn yn], converting all expressions to real numbers.
1004 ;; If either xi or yi are not real, both are replaced by 'moveto
1005 (defun parse-points-xy (xy)
1006 (do ((ab (rest xy
) (cdr ab
))
1008 ((null ab
) (cons '(mlist) (reverse c
)))
1009 (setq af
(coerce-float (cadar ab
)))
1010 (setq bf
(coerce-float (caddar ab
)))
1012 ((or (not (realp af
)) (not (realp bf
)))
1013 (setq c
(cons 'moveto
(cons 'moveto c
))))
1015 (setq c
(cons bf
(cons af c
)))))))
1017 ;;; Adaptive plotting, based on the adaptive plotting code from
1018 ;;; YACAS. See http://yacas.sourceforge.net/Algo.html#c3s1 for a
1019 ;;; description of the algorithm. More precise details can be found
1020 ;;; in the file yacas/scripts/plots.rep/plot2d.ys.
1023 ;; Determine if we have a slow oscillation of the function.
1024 ;; Basically, for each 3 consecutive function values, we check to see
1025 ;; if the function is monotonic or not. There are 3 such sets, and
1026 ;; the function is considered slowly oscillating if at most 2 of them
1027 ;; are not monotonic.
1028 (defun slow-oscillation-p (f0 f1 f2 f3 f4
)
1029 (flet ((sign-change (x y z
)
1030 (cond ((not (and (numberp x
) (numberp y
) (numberp z
)))
1031 ;; Something is not a number. Assume the
1032 ;; oscillation is not slow.
1034 ((or (and (> y x
) (> y z
))
1035 (and (< y x
) (< y z
)))
1039 (<= (+ (sign-change f0 f1 f2
)
1040 (sign-change f1 f2 f3
)
1041 (sign-change f2 f3 f4
))
1044 ;; Determine if the function values are smooth enough. This means
1045 ;; that integrals of the functions on the left part and the right part
1046 ;; of the range are approximately the same.
1049 (defun smooth-enough-p (f-a f-a1 f-b f-b1 f-c eps
)
1050 (cond ((every #'numberp
(list f-a f-a1 f-b f-b1 f-c
))
1051 (let ((quad (/ (+ f-a
1057 (quad-b (/ (+ (* 5 f-b
)
1061 ;; According to the Yacas source code, quad is the Simpson
1062 ;; quadrature for the (fb,fb1) subinterval (using points b,b1,c),
1063 ;; subtracted from the 4-point Newton-Cotes quadrature for the
1064 ;; (fb,fb1) subinterval (using points a, a1, b, b1.
1066 ;; quad-b is the Simpson quadrature for the (fb,f1) subinterval.
1068 ;; This used to test for diff <= 0. But in some
1069 ;; situations, like plot2d(0.99,[x,0,5]), roundoff prevents
1070 ;; this from happening. So we do diff < delta instead, for
1071 ;; some value of delta.
1073 ;; XXX: What is the right value for delta? Does this break
1074 ;; other things? Simple tests thus far show that
1075 ;; 100*flonum-epsilon is ok.
1076 (let ((diff (- (abs quad
)
1077 (* eps
(- quad-b
(min f-a f-a1 f-b f-b1 f-c
)))))
1078 (delta (* 150 flonum-epsilon
)))
1081 ;; Something is not a number, so assume it's not smooth enough.
1085 (defun adaptive-plot (fcn a b c f-a f-b f-c depth eps
)
1086 ;; Step 1: Split the interval [a, c] into 5 points
1087 (let* ((a1 (/ (+ a b
) 2))
1089 (f-a1 (funcall fcn a1
))
1090 (f-b1 (funcall fcn b1
))
1092 (cond ((or (not (plusp depth
))
1093 (and (slow-oscillation-p f-a f-a1 f-b f-b1 f-c
)
1094 (smooth-enough-p f-a f-a1 f-b f-b1 f-c eps
)))
1095 ;; Everything is nice and smooth so we're done. Don't
1102 ;; We are not plotting the real part of the function and the
1103 ;; function is undefined at all points - assume it has complex value
1104 ;; on [a,b]. Maybe we should refine it a couple of times just to make sure?
1105 ((and (null *plot-realpart
*)
1106 (null f-a
) (null f-a1
) (null f-b
) (null f-b1
) (null f-c
))
1113 ;; Need to refine. Split the interval in half, and try to plot each half.
1114 (let ((left (adaptive-plot fcn a a1 b f-a f-a1 f-b
(1- depth
) (* 2 eps
)))
1115 (right (adaptive-plot fcn b b1 c f-b f-b1 f-c
(1- depth
) (* 2 eps
))))
1116 (append left
(cddr right
)))))))
1118 (defun adaptive-parametric-plot (x-fcn y-fcn a b c x-a x-b x-c y-a y-b y-c depth eps
)
1119 ;; Step 1: Split the interval [a, c] into 5 points
1120 (let* ((a1 (/ (+ a b
) 2))
1122 (x-a1 (funcall x-fcn a1
))
1123 (x-b1 (funcall x-fcn b1
))
1124 (y-a1 (funcall y-fcn a1
))
1125 (y-b1 (funcall y-fcn b1
)))
1126 (cond ((or (not (plusp depth
))
1127 ;; Should we have a different algorithm to determine
1128 ;; slow oscillation and smooth-enough for parametric
1130 (and (slow-oscillation-p y-a y-a1 y-b y-b1 y-c
)
1131 (slow-oscillation-p x-a x-a1 x-b x-b1 x-c
)
1132 (smooth-enough-p y-a y-a1 y-b y-b1 y-c eps
)
1133 (smooth-enough-p x-a x-a1 x-b x-b1 x-c eps
)))
1134 ;; Everything is nice and smooth so we're done. Don't
1141 ;; We are not plotting the real part of the function and the
1142 ;; function is undefined at all points - assume it has complex value
1143 ;; on [a,b]. Maybe we should refine it a couple of times just to make sure?
1144 ((and (null *plot-realpart
*)
1145 (null y-a
) (null y-a1
) (null y-b
) (null y-b1
) (null y-c
)
1146 (null x-a
) (null x-a1
) (null x-b
) (null x-b1
) (null x-c
))
1153 ;; Need to refine. Split the interval in half, and try to plot each half.
1154 (let ((left (adaptive-parametric-plot x-fcn y-fcn
1158 (1- depth
) (* 2 eps
)))
1159 (right (adaptive-parametric-plot x-fcn y-fcn
1163 (1- depth
) (* 2 eps
))))
1164 ;; (cddr right) to skip over the point that is duplicated
1165 ;; between the right end-point of the left region and the
1166 ;; left end-point of the right
1167 (append left
(cddr right
)))))))
1169 (defun draw2d (fcn range plot-options
)
1170 (if (and ($listp fcn
) (equal '$parametric
(cadr fcn
)))
1172 (if $use_adaptive_parametric_plot
1173 (draw2d-parametric-adaptive fcn range plot-options
)
1174 (draw2d-parametric fcn range plot-options
))))
1175 (if (and ($listp fcn
) (equal '$discrete
(cadr fcn
)))
1176 (return-from draw2d
(draw2d-discrete fcn
)))
1177 (let* ((nticks (getf plot-options
:nticks
))
1178 (yrange (getf plot-options
:ybounds
))
1179 (depth (getf plot-options
:adapt_depth
)))
1181 (setq fcn
(coerce-float-fun fcn
`((mlist), (second range
))))
1183 (let* ((x-start (coerce-float (third range
)))
1184 (xend (coerce-float (fourth range
)))
1185 (x-step (/ (- xend x-start
) (coerce-float nticks
) 2))
1186 (ymin (coerce-float (first yrange
)))
1187 (ymax (coerce-float (second yrange
)))
1188 (n-clipped 0) (n-non-numeric 0)
1189 ;; What is a good EPS value for adaptive plotting?
1191 x-samples y-samples result
1193 (declare (type flonum ymin ymax
))
1194 ;; Divide the region into NTICKS regions. Each region has a
1195 ;; start, mid and endpoint. Then adaptively plot over each of
1196 ;; these regions. So it's probably a good idea not to make
1197 ;; NTICKS too big. Since adaptive plotting splits the sections
1198 ;; in half, it's also probably not a good idea to have NTICKS be
1200 (when (getf plot-options
:logx
)
1201 (setf x-start
(log x-start
))
1202 (setf xend
(log xend
))
1203 (setf x-step
(/ (- xend x-start
) (coerce-float nticks
) 2)))
1206 (let ((y (if (getf plot-options
:logx
)
1207 (funcall fcn
(exp x
))
1209 (if (and (getf plot-options
:logy
)
1211 (if (> y
0) (log y
) 'und
)
1214 (dotimes (k (1+ (* 2 nticks
)))
1215 (let ((x (+ x-start
(* k x-step
))))
1217 (push (fun x
) y-samples
)))
1218 (setf x-samples
(nreverse x-samples
))
1219 (setf y-samples
(nreverse y-samples
))
1221 ;; For each region, adaptively plot it.
1222 (do ((x-start x-samples
(cddr x-start
))
1223 (x-mid (cdr x-samples
) (cddr x-mid
))
1224 (x-end (cddr x-samples
) (cddr x-end
))
1225 (y-start y-samples
(cddr y-start
))
1226 (y-mid (cdr y-samples
) (cddr y-mid
))
1227 (y-end (cddr y-samples
) (cddr y-end
)))
1229 ;; The region is x-start to x-end, with mid-point x-mid.
1231 ;; The cddr is to remove the one extra sample (x and y value)
1232 ;; that adaptive plot returns. But on the first iteration,
1233 ;; result is empty, so we don't want the cddr because we want
1234 ;; all the samples returned from adaptive-plot. On subsequent
1235 ;; iterations, it's a duplicate of the last point of the
1236 ;; previous interval.
1241 (adaptive-plot #'fun
(car x-start
) (car x-mid
) (car x-end
)
1242 (car y-start
) (car y-mid
) (car y-end
)
1244 (adaptive-plot #'fun
(car x-start
) (car x-mid
) (car x-end
)
1245 (car y-start
) (car y-mid
) (car y-end
)
1248 ;; Fix up out-of-range values
1249 ;; and clobber non-numeric values.
1251 (do ((x result
(cddr x
))
1252 (y (cdr result
) (cddr y
)))
1254 (if (numberp (car y
))
1255 (unless (<= ymin
(car y
) ymax
)
1257 (setf (car x
) 'moveto
)
1258 (setf (car y
) 'moveto
))
1260 (incf n-non-numeric
)
1261 (setf (car x
) 'moveto
)
1262 (setf (car y
) 'moveto
)))
1263 (when (and (getf plot-options
:logx
)
1265 (setf (car x
) (exp (car x
))))
1267 (when (and (getf plot-options
:logy
)
1269 (setf (car y
) (exp (car y
)))))
1271 ;; Filter out any MOVETO's which do not precede a number.
1272 ;; Code elsewhere in this file expects MOVETO's to
1273 ;; come in pairs, so leave two MOVETO's before a number.
1274 (let ((n (length result
)))
1279 (eq (nth i result
) 'moveto
)
1280 (eq (nth (1+ i
) result
) 'moveto
)
1283 (eq (nth (+ i
2) result
) 'moveto
)))
1284 (setf (nth i result
) nil
)
1285 (setf (nth (1+ i
) result
) nil
))))
1287 (let ((result-sans-nil (delete nil result
)))
1288 (if (null result-sans-nil
)
1290 ((= n-non-numeric
0)
1291 (mtell (intl:gettext
"plot2d: all values were clipped.~%")))
1293 (mtell (intl:gettext
"plot2d: expression evaluates to non-numeric value everywhere in plotting range.~%")))
1295 (mtell (intl:gettext
"plot2d: all values are non-numeric, or clipped.~%"))))
1297 (if (> n-non-numeric
0)
1298 (mtell (intl:gettext
"plot2d: expression evaluates to non-numeric value somewhere in plotting range.~%")))
1300 (mtell (intl:gettext
"plot2d: some values were clipped.~%")))))
1301 (cons '(mlist) result-sans-nil
))))))
1303 (defun get-range (lis)
1304 (let ((ymin most-positive-flonum
)
1305 (ymax most-negative-flonum
))
1306 (declare (type flonum ymin ymax
))
1307 (do ((l lis
(cddr l
)))
1309 (or (floatp (car l
)) (setf (car l
) (float (car l
))))
1310 (cond ((< (car l
) ymin
)
1311 (setq ymin
(car l
))))
1312 (cond ((< ymax
(car l
))
1313 (setq ymax
(car l
)))))
1314 (list '(mlist) ymin ymax
)))
1316 #+sbcl
(defvar $gnuplot_view_args
"-persist ~a")
1317 #-sbcl
(defvar $gnuplot_view_args
"-persist ~s")
1319 #+(or sbcl openmcl
) (defvar $gnuplot_file_args
"~a")
1320 #-
(or sbcl openmcl
) (defvar $gnuplot_file_args
"~s")
1322 (defvar $mgnuplot_command
"mgnuplot")
1323 (defvar $geomview_command
"geomview")
1325 (defvar $xmaxima_plot_command
"xmaxima")
1327 (defun plot-temp-file (file)
1328 (if *maxima-tempdir
*
1329 (format nil
"~a/~a" *maxima-tempdir
* file
)
1332 ;; If no file path is given, uses temporary directory path
1333 (defun plot-file-path (file)
1334 (if (pathname-directory file
)
1336 (plot-temp-file file
)))
1338 (defun gnuplot-process (plot-options &optional file out-file
)
1339 (let ((gnuplot-term (getf plot-options
:gnuplot_term
))
1340 (run-viewer (getf plot-options
:run_viewer
))
1342 (string-downcase (getf plot-options
:gnuplot_preamble
))))
1344 ;; creates the output file, when there is one to be created
1345 (when (and out-file
(not (eq gnuplot-term
'$default
)))
1346 #+(or (and sbcl win32
) (and ccl windows
))
1347 ($system $gnuplot_command
(format nil $gnuplot_file_args file
))
1348 #-
(or (and sbcl win32
) (and ccl windows
))
1349 ($system
(format nil
"~a ~a" $gnuplot_command
1350 (format nil $gnuplot_file_args file
))))
1352 ;; displays contents of the output file, when gnuplot-term is dumb,
1353 ;; or runs gnuplot when gnuplot-term is default
1357 ;; the options given to gnuplot will be different when the user
1358 ;; redirects the output by using "set output" in the preamble
1359 #+(or (and sbcl win32
) (and ccl windows
))
1360 ($system $gnuplot_command
"-persist" (format nil $gnuplot_file_args file
))
1361 #-
(or (and sbcl win32
) (and ccl windows
))
1363 (format nil
"~a ~a" $gnuplot_command
1364 (format nil
(if (search "set out" gnuplot-preamble
)
1365 $gnuplot_file_args $gnuplot_view_args
)
1369 ($printfile
(car out-file
))
1370 (merror (intl:gettext
"plotting: option 'gnuplot_out_file' not defined."))))))))
1372 ;; plot-options-parser puts the plot options given into a property list.
1373 ;; maxopts: a list (not a Maxima list!) with plot options.
1374 ;; options: a property list, or an empty list.
1376 ;; (plot-options-parser (list #$[x,-2,2]$ #$[nticks,30]$) '(:nticks 4))
1378 ;; (:XLABEL "x" :XMAX 2.0 :XMIN -2.0 :NTICKS 30)
1380 (defun plot-options-parser (maxopts options
&aux name
)
1381 (unless (every #'$listp maxopts
)
1383 (mapcar #'(lambda (x) (if ($listp x
) x
(list '(mlist) x
))) maxopts
)))
1384 (dolist (opt maxopts
)
1385 (unless ($symbolp
(setq name
(second opt
)))
1388 "plot-options-parser: Expecting a symbol for the option name, found: \"~M\"") opt
))
1391 (setf (getf options
:adapt_depth
)
1392 (check-option (cdr opt
) #'naturalp
"a natural number" 1)))
1393 ($axes
(setf (getf options
:axes
)
1394 (check-option-b (cdr opt
) #'axesoptionp
"x, y, solid" 1)))
1395 ($azimuth
(if (caddr opt
)
1396 (setf (caddr opt
) (parse-azimuth (caddr opt
))))
1397 (setf (getf options
:azimuth
)
1398 (check-option (cdr opt
) #'realp
"a real number" 1)))
1399 ($box
(setf (getf options
:box
)
1400 (check-option-boole (cdr opt
))))
1403 (setf (cddr opt
) (mapcar #'coerce-float
(cddr opt
))))
1404 (setf (getf options
:color_bar_tics
)
1405 (check-option-b (cdr opt
) #'realp
"a real number" 3)))
1406 ($color
(setf (getf options
:color
)
1407 (check-option (cdr opt
) #'plotcolorp
"a color")))
1408 ($color_bar
(setf (getf options
:color_bar
)
1409 (check-option-boole (cdr opt
))))
1410 ($elevation
(if (caddr opt
)
1411 (setf (caddr opt
) (parse-elevation (caddr opt
))))
1412 (setf (getf options
:elevation
)
1413 (check-option (cdr opt
) #'realp
"a real number" 1)))
1414 ($grid
(setf (getf options
:grid
)
1415 (check-option (cdr opt
) #'naturalp
"a natural number" 2)))
1416 ($grid2d
(setf (getf options
:grid2d
)
1417 (check-option-boole (cdr opt
))))
1419 (setf (getf options
:iterations
)
1420 (check-option (cdr opt
) #'naturalp
"a natural number" 1)))
1421 ($label
(setf (getf options
:label
)
1422 (check-option-label (cdr opt
))))
1423 ($legend
(setf (getf options
:legend
)
1424 (check-option-b (cdr opt
) #'stringp
"a string")))
1425 ($logx
(setf (getf options
:logx
)
1426 (check-option-boole (cdr opt
))))
1427 ($logy
(setf (getf options
:logy
)
1428 (check-option-boole (cdr opt
))))
1430 (setf (getf options
:mesh_lines_color
)
1431 (check-option-b (cdr opt
) #'plotcolorp
"a color" 1)))
1432 ($nticks
(setf (getf options
:nticks
)
1433 (check-option (cdr opt
) #'naturalp
"a natural number" 1)))
1434 ($palette
(setf (getf options
:palette
)
1435 (check-option-palette (cdr opt
))))
1436 ($plot_format
(setf (getf options
:plot_format
)
1437 (check-option-format (cdr opt
))))
1438 ($plot_realpart
(setf (getf options
:plot_realpart
)
1439 (check-option-boole (cdr opt
))))
1440 ($point_type
(setf (getf options
:point_type
)
1441 (check-option (cdr opt
) #'pointtypep
"a point type")))
1442 ($pdf_file
(setf (getf options
:pdf_file
)
1443 (check-option (cdr opt
) #'stringp
"a string" 1)))
1444 ($png_file
(setf (getf options
:png_file
)
1445 (check-option (cdr opt
) #'stringp
"a string" 1)))
1446 ($ps_file
(setf (getf options
:ps_file
)
1447 (check-option (cdr opt
) #'stringp
"a string" 1)))
1448 ($run_viewer
(setf (getf options
:run_viewer
)
1449 (check-option-boole (cdr opt
))))
1450 ($same_xy
(setf (getf options
:same_xy
)
1451 (check-option-boole (cdr opt
))))
1452 ($same_xyz
(setf (getf options
:same_xyz
)
1453 (check-option-boole (cdr opt
))))
1454 ($style
(setf (getf options
:style
)
1455 (check-option-style (cdr opt
))))
1456 ($svg_file
(setf (getf options
:svg_file
)
1457 (check-option (cdr opt
) #'stringp
"a string" 1)))
1458 ($t
(setf (getf options
:t
) (cddr (check-range opt
))))
1459 ($title
(setf (getf options
:title
)
1460 (check-option (cdr opt
) #'stringp
"a string" 1)))
1461 ($transform_xy
(setf (getf options
:transform_xy
)
1462 (check-option-b (cdr opt
) #'functionp
"a function make_transform" 1)))
1463 ($x
(setf (getf options
:x
) (cddr (check-range opt
))))
1464 ($xbounds
(setf (getf options
:xbounds
) (cddr (check-range opt
))))
1465 ($xlabel
(setf (getf options
:xlabel
)
1466 (check-option (cdr opt
) #'string
"a string" 1)))
1469 (setf (cddr opt
) (mapcar #'coerce-float
(cddr opt
))))
1470 (setf (getf options
:xtics
)
1471 (check-option-b (cdr opt
) #'realp
"a real number" 3)))
1472 ($xvar
(setf (getf options
:xvar
)
1473 (check-option (cdr opt
) #'string
"a string" 1)))
1476 (setf (cddr opt
) (mapcar #'coerce-float
(cddr opt
))))
1477 (setf (getf options
:xy_scale
)
1478 (check-option (cdr opt
) #'realpositivep
1479 "a positive real number" 2)))
1480 ($y
(setf (getf options
:y
) (cddr (check-range opt
))))
1481 ($ybounds
(setf (getf options
:ybounds
) (cddr (check-range opt
))))
1482 ($ylabel
(setf (getf options
:ylabel
)
1483 (check-option (cdr opt
) #'string
"a string" 1)))
1486 (setf (cddr opt
) (mapcar #'coerce-float
(cddr opt
))))
1487 (setf (getf options
:ytics
)
1488 (check-option-b (cdr opt
) #'realp
"a real number" 3)))
1489 ($yvar
(setf (getf options
:yvar
)
1490 (check-option (cdr opt
) #'string
"a string" 1)))
1493 (setf (caddr opt
) (coerce-float (caddr opt
))))
1494 (setf (getf options
:yx_ratio
)
1495 (check-option (cdr opt
) #'realp
"a real number" 1)))
1496 ($z
(setf (getf options
:z
) (cddr (check-range opt
))))
1497 ($zlabel
(setf (getf options
:zlabel
)
1498 (check-option (cdr opt
) #'string
"a string" 1)))
1501 (setf (caddr opt
) (coerce-float (caddr opt
))))
1502 (setf (getf options
:zmin
)
1503 (check-option-b (cdr opt
) #'realp
"a real number" 1)))
1506 (setf (cddr opt
) (mapcar #'coerce-float
(cddr opt
))))
1507 (setf (getf options
:ztics
)
1508 (check-option-b (cdr opt
) #'realp
"a real number" 3)))
1509 ($gnuplot_4_0
(setf (getf options
:gnuplot_4_0
)
1510 (check-option-boole (cdr opt
))))
1511 ($gnuplot_curve_titles
1512 (setf (getf options
:gnuplot_curve_titles
)
1513 (check-option (cdr opt
) #'stringp
"a string")))
1514 ($gnuplot_curve_styles
1515 (setf (getf options
:gnuplot_curve_styles
)
1516 (check-option (cdr opt
) #'stringp
"a string")))
1517 ($gnuplot_default_term_command
1518 (setf (getf options
:gnuplot_default_term_command
)
1519 (check-option (cdr opt
) #'stringp
"a string" 1)))
1520 ($gnuplot_dumb_term_command
1521 (setf (getf options
:gnuplot_dumb_term_command
)
1522 (check-option (cdr opt
) #'stringp
"a string" 1)))
1524 (setf (getf options
:gnuplot_out_file
)
1525 (check-option (cdr opt
) #'stringp
"a string" 1)))
1527 (setf (getf options
:gnuplot_pm3d
)
1528 (check-option-boole (cdr opt
))))
1530 (setf (getf options
:gnuplot_preamble
)
1531 (check-option (cdr opt
) #'stringp
"a string" 1)))
1533 (setf (getf options
:gnuplot_postamble
)
1534 (check-option (cdr opt
) #'stringp
"a string" 1)))
1535 ($gnuplot_pdf_term_command
1536 (setf (getf options
:gnuplot_pdf_term_command
)
1537 (check-option (cdr opt
) #'stringp
"a string" 1)))
1538 ($gnuplot_png_term_command
1539 (setf (getf options
:gnuplot_png_term_command
)
1540 (check-option (cdr opt
) #'stringp
"a string" 1)))
1541 ($gnuplot_ps_term_command
1542 (setf (getf options
:gnuplot_ps_term_command
)
1543 (check-option (cdr opt
) #'stringp
"a string" 1)))
1544 ($gnuplot_svg_term_command
1545 (setf (getf options
:gnuplot_svg_term_command
)
1546 (check-option (cdr opt
) #'stringp
"a string" 1)))
1547 ;; gnuplot_term is a tricky one: when it is just default, dumb or
1548 ;; ps, we want it to be a symbol, but when it is more complicated,
1549 ;; i.e. "ps; size 16cm, 12cm", it must be a string and not a symbol
1551 (let ((s (caddr opt
)))
1553 (cond ((string= s
"default") (setq s
'$default
))
1554 ((string= s
"dumb") (setq s
'$dumb
))
1555 ((string= s
"ps") (setq s
'$ps
))))
1557 (setf (getf options
:gnuplot_term
) s
)
1559 (intl:gettext
"Wrong argument for plot option \"gnuplot_term\". Expecting a string or a symbol but found \"~M\".") s
))))
1562 (intl:gettext
"plot-options-parser: unknown plot option: ~M") opt
))))
1564 ;; plots that create a file work better in gnuplot than gnuplot_pipes
1565 (when (and (eq (getf options
:plot_format
) '$gnuplot_pipes
)
1566 (or (eq (getf options
:gnuplot_term
) '$dumb
)
1567 (getf options
:pdf_file
) (getf options
:png_file
)
1568 (getf options
:ps_file
) (getf options
:svg_file
)))
1569 (setf (getf options
:plot_format
) '$gnuplot
))
1573 ;; natural numbers predicate
1574 (defun naturalp (n) (or (and (integerp n
) (> n
0)) nil
))
1576 ;; positive real numbers predicate
1577 (defun realpositivep (x) (or (and (realp x
) (> x
0)) nil
))
1579 ;; posible values for the axes option
1580 (defun axesoptionp (o) (if (member o
'($x $y $solid
)) t nil
))
1582 ;; the 13 possibilities for the point types
1583 (defun pointtypep (p)
1584 (if (member p
'($bullet $circle $plus $times $asterisk $box $square
1585 $triangle $delta $wedge $nabla $diamond $lozenge
)) t nil
))
1587 ;; Colors can only one of the named colors or a six-digit hexadecimal
1588 ;; number with a # suffix.
1589 (defun plotcolorp (color)
1590 (cond ((and (stringp color
)
1591 (string= (subseq color
0 1) "#")
1592 (= (length color
) 7)
1593 (ignore-errors (parse-integer (subseq color
1 6) :radix
16)))
1595 ((member color
'($red $green $blue $magenta $cyan $yellow
1596 $orange $violet $brown $gray $black $white
))
1600 ;; tries to convert az into a floating-point number between 0 and 360
1601 (defun parse-azimuth (az) (mod ($float
(meval* az
)) 360))
1603 ;; tries to convert el into a floating-poitn number between -180 and 180
1604 (defun parse-elevation (el) (- (mod (+ 180 ($float
(meval* el
))) 360) 180))
1606 ;; The following functions check the value of an option returning an atom
1607 ;; when there is only one argument or a list when there are several arguments
1610 ;; Checks for one or more items of the same type, using the test given
1611 (defun check-option (option test type
&optional count
)
1613 (unless (= (1- (length option
)) count
)
1616 "Wrong number of arguments for plot option \"~M\". Expecting ~M but found ~M.")
1617 (car option
) count
(1- (length option
)))))
1618 (dolist (item (cdr option
))
1619 (when (not (funcall test item
))
1621 (intl:gettext
"Wrong argument for plot option \"~M\". Expecting ~M but found \"~M\".") (car option
) type item
)))
1622 (if (= (length option
) 2)
1626 ;; Accepts one or more items of the same type or true or false.
1627 ;; When given, n is the maximum number of items.
1628 (defun check-option-b (option test type
&optional count
)
1629 (let ((n (- (length option
) 1)))
1631 (unless (< n
(1+ count
))
1634 "Wrong number of arguments for plot option \"~M\". Expecting ~M but found ~M.")
1635 (car option
) count
(1- (length option
)))))
1638 ((= n
1) (if (or (funcall test
(cadr option
)) (null (cadr option
))
1639 (eq (cadr option
) t
))
1641 (merror (intl:gettext
"Wrong argument for plot option \"~M\". Expecting ~M, true or false but found \"~M\".") (car option
) type
(cadr option
))))
1644 (unless (funcall test
(nth (+ i
1) option
))
1646 (intl:gettext
"Wrong argument for plot option \"~M\". Expecting ~M but found \"~M\".") (car option
) type
(nth (+ i
1) option
))))
1649 ;; Boolean options can be [option], [option,true] or [option,false]
1650 (defun check-option-boole (option)
1651 (if (= 1 (length option
))
1653 (if (and (= 2 (length option
))
1654 (or (eq (cadr option
) t
) (null (cadr option
))))
1656 (merror (intl:gettext
"plot option ~M must be either true or false.")
1659 ;; label can be either [label, string, real, real] or
1660 ;; [label, [string_1, real, real],...,[string_n, real, real]]
1661 (defun check-option-label (option &aux opt
)
1662 (if (not ($listp
(cadr option
)))
1663 (setq opt
(list (cons '(mlist) (cdr option
))))
1664 (setq opt
(cdr option
)))
1666 (when (not (and ($listp item
) (= 4 (length item
)) (stringp (second item
))
1667 (realp (setf (third item
) (coerce-float (third item
))))
1668 (realp (setf (fourth item
) (coerce-float (fourth item
))))))
1671 "Wrong argument ~M for option ~M. Must be either [label,\"text\",x,y] or [label, [\"text 1\",x1,y1],...,[\"text n\",xn,yn]]")
1672 item
(car option
))))
1675 ;; one of the possible formats
1676 (defun check-option-format (option &aux formats
)
1677 (if (string= *autoconf-win32
* "true")
1678 (setq formats
'($geomview $gnuplot $mgnuplot $openmath $xmaxima
))
1679 (setq formats
'($geomview $gnuplot $gnuplot_pipes $mgnuplot $openmath $xmaxima
)))
1680 (unless (member (cadr option
) formats
)
1683 "Wrong argument ~M for option ~M. Must one of the following symbols: geomview, gnuplot, mgnuplot, xmaxima (or gnuplot_pipes in Unix)")
1684 (cadr option
) (car option
)))
1685 ; $openmath is just a synonym for $xmaxima
1686 (if (eq (cadr option
) '$openmath
)
1690 ; palette most be one or more Maxima lists starting with the name of one
1691 ;; of the 5 kinds: hue, saturation, value, gray or gradient.
1692 (defun check-option-palette (option)
1693 (if (and (= (length option
) 2) (null (cadr option
)))
1696 (dolist (item (cdr option
))
1697 (when (not (and ($listp item
)
1699 '($hue $saturation $value $gray $gradient
))))
1702 "Wrong argument ~M for option ~M. Not a valid palette.")
1703 item
(car option
))))
1706 ;; style can be one or several of the names of the styles or one or several
1707 ;; Maxima lists starting with the name of one of the styles.
1708 (defun check-option-style (option)
1709 (if (not ($listp
(cadr option
)))
1710 (dolist (item (rest option
))
1711 (when (not (member item
1712 '($lines $points $linespoints $dots $impulses
)))
1715 "Wrong argument ~M for option ~M. Not a valid style")
1716 item
(car option
))))
1717 (dolist (item (rest option
))
1721 '($lines $points $linespoints $dots $impulses
))))
1724 "Wrong argument ~M for option ~M. Not a valid style.")
1725 item
(car option
)))))
1726 (if (= (length option
) 2)
1730 ;; Transform can be false or the name of a function for the transformation.
1731 (defun check-option-transform (option)
1732 (if (and (= (length option
) 2)
1733 (or (atom (cadr option
)) (null (cadr option
))))
1737 "Wrong argument ~M for option ~M. Should be either false or the name of function for the transformation") option
(car option
))))
1742 ;; plot2d (sec(x), [x, -2, 2], [y, -20, 20], [nticks, 200])$
1744 ;; plot2d (exp(3*s), [s, -2, 2], [logy])$
1746 ;; plot2d ([parametric, cos(t), sin(t), [t, -%pi, %pi], [nticks, 80]],
1749 ;; xy:[[10,.6], [20,.9], [30,1.1], [40,1.3], [50,1.4]]$
1750 ;; plot2d ( [ [discrete, xy], 2*%pi*sqrt(l/980) ], [l, 0, 50],
1751 ;; [style, points, lines], [color, red, blue], [point_type, box],
1752 ;; [legend, "experiment", "theory"],
1753 ;; [xlabel, "pendulum's length (cm)"], [ylabel, "period (s)"])$
1755 ;; plot2d ( x^2-1, [x, -3, 3], [y, -2, 10], [box, false], [color, red],
1756 ;; [ylabel, "x^2-1"], [plot_format, xmaxima])$
1758 (defun $plot2d
(fun &optional range
&rest extra-options
)
1759 (let (($display2d nil
) (*plot-realpart
* *plot-realpart
*)
1760 (options (copy-tree *plot-options
*)) (i 0)
1761 output-file gnuplot-term gnuplot-out-file file points-lists
)
1763 ;; 1- Put fun in its most general form: a maxima list with several objects
1764 ;; that can be expressions (simple functions) and maxima lists (parametric
1765 ;; functions or discrete sets of points).
1767 ;; If there is a single parametric function use its range as the range for
1768 ;; the plot and put it inside another maxima list
1769 (setf (getf options
:type
) "plot2d")
1770 (when (and (consp fun
) (eq (second fun
) '$parametric
))
1772 (setq range
(check-range (nth 4 fun
))))
1773 (setq fun
`((mlist) ,fun
)))
1775 ;; If there is a single set of discrete points put it inside a maxima list
1776 (when (and (consp fun
) (eq (second fun
) '$discrete
))
1777 (setq fun
`((mlist) ,fun
)))
1779 ;; If at this point fun is not a maxima list, it is then a single function
1780 (unless ($listp fun
) (setq fun
`((mlist) ,fun
)))
1782 ;; 2- Get names for the two axis and values for xmin and xmax if needed.
1784 ;; If any of the objects in the fun list is a simple function,
1785 ;; the range option is mandatory and will provide the name of
1786 ;; the horizontal axis and the values of xmin and xmax.
1787 (let ((no-range-required t
) small huge
)
1788 #-clisp
(setq small
(- (/ most-positive-flonum
1024)))
1789 #+clisp
(setq small
(- (/ most-positive-double-float
1024.0)))
1790 #-clisp
(setq huge
(/ most-positive-flonum
1024))
1791 #+clisp
(setq huge
(/ most-positive-double-float
1024.0))
1792 (setf (getf options
:ybounds
) (list small huge
))
1793 (dolist (subfun (rest fun
))
1794 (if (not ($listp subfun
))
1795 (setq no-range-required nil
)))
1796 (unless no-range-required
1797 (setq range
(check-range range
))
1798 (setf (getf options
:xlabel
) (ensure-string (second range
)))
1799 (setf (getf options
:x
) (cddr range
)))
1800 (when no-range-required
1801 ;; Make the default ranges on X nd Y large so parametric plots
1802 ;; don't get prematurely clipped. Don't use most-positive-flonum
1803 ;; because draw2d will overflow.
1804 (setf (getf options
:xbounds
) (list small huge
))
1806 ;; second argument was really a plot option, not a range
1807 (setq extra-options
(cons range extra-options
)))))
1809 ;; When only one function is being plotted:
1810 ;; If a simple function use, its name for the vertical axis.
1811 ;; If parametric, give the axes the names of the two parameters.
1812 ;; If discrete points, name the axes x and y.
1813 (when (= (length fun
) 2)
1814 (let ((v (second fun
)) label
)
1816 (setq label
(coerce (mstring v
) 'string
))
1817 (if (< (length label
) 80)
1818 (setf (getf options
:ylabel
) label
)))
1819 ((eq (second v
) '$parametric
)
1820 (setq label
(coerce (mstring (third v
)) 'string
))
1821 (if (< (length label
) 80)
1822 (setf (getf options
:xlabel
) label
))
1823 (setq label
(coerce (mstring (fourth v
)) 'string
))
1824 (if (< (length label
) 80)
1825 (setf (getf options
:ylabel
) label
)))
1826 ((eq (second v
) '$discrete
)
1827 (setf (getf options
:xlabel
) "x")
1828 (setf (getf options
:ylabel
) "y"))
1830 (setq label
(coerce (mstring v
) 'string
))
1831 (if (< (length label
) 80)
1832 (setf (getf options
:ylabel
) label
))))))
1834 ;; Parse the given options into the options list
1835 (setq options
(plot-options-parser extra-options options
))
1836 (when (getf options
:y
) (setf (getf options
:ybounds
) (getf options
:y
)))
1838 ;; Remove axes labels when no box is used in gnuplot
1839 (when (and (member :box options
) (not (getf options
:box
))
1840 (not (eq (getf options
:plot_format
) '$xmaxima
)))
1841 (remf options
:xlabel
)
1842 (remf options
:ylabel
))
1845 (let ((xmin (first (getf options
:x
))) (xmax (second (getf options
:x
))))
1847 (and (getf options
:logx
) xmin xmax
)
1850 (let ((revised-xmin (/ xmax
1000)))
1851 (mtell (intl:gettext
"plot2d: lower bound must be positive when 'logx' in effect.~%plot2d: assuming lower bound = ~M instead of ~M") revised-xmin xmin
)
1852 (setf (getf options
:x
) (list revised-xmin xmax
))
1853 (setq range
`((mlist) ,(second range
) ,revised-xmin
,xmax
))))
1854 (merror (intl:gettext
"plot2d: upper bound must be positive when 'logx' in effect; found: ~M") xmax
))))
1856 (let ((ymin (first (getf options
:y
)))
1857 (ymax (second (getf options
:y
))))
1858 (when (and (getf options
:logy
) ymin ymax
)
1861 (let ((revised-ymin (/ ymax
1000)))
1862 (mtell (intl:gettext
"plot2d: lower bound must be positive when 'logy' in effect.~%plot2d: assuming lower bound = ~M instead of ~M") revised-ymin ymin
)
1863 (setf (getf options
:y
) (list revised-ymin ymax
))))
1864 (merror (intl:gettext
"plot2d: upper bound must be positive when 'logy' in effect; found: ~M") ymax
))))
1866 (setq *plot-realpart
* (getf options
:plot_realpart
))
1868 ;; Compute points to plot for each element of FUN.
1869 ;; If no plottable points are found, return immediately from $PLOT2D.
1872 (mapcar #'(lambda (f) (cdr (draw2d f range options
))) (cdr fun
)))
1873 (when (= (count-if #'(lambda (x) x
) points-lists
) 0)
1874 (mtell (intl:gettext
"plot2d: nothing to plot.~%"))
1875 (return-from $plot2d
))
1877 (setq gnuplot-term
(getf options
:gnuplot_term
))
1878 (setf gnuplot-out-file
(getf options
:gnuplot_out_file
))
1879 (if (and (find (getf options
:plot_format
) '($gnuplot_pipes $gnuplot
))
1880 (eq gnuplot-term
'$default
) gnuplot-out-file
)
1881 (setq file
(plot-file-path gnuplot-out-file
))
1884 (format nil
"maxout.~(~a~)"
1885 (ensure-string (getf options
:plot_format
))))))
1887 ;; old function $plot2dopen incorporated here
1888 (case (getf options
:plot_format
)
1890 (when (getf options
:ps_file
)
1891 (setq output-file
(list (getf options
:ps_file
))))
1893 (with-output-to-string
1895 (xmaxima-print-header st options
)
1896 (let ((legend (getf options
:legend
))
1897 (colors (getf options
:color
))
1898 (styles (getf options
:style
)) style plot-name
)
1899 (unless (listp legend
) (setq legend
(list legend
)))
1900 (unless (listp colors
) (setq colors
(list colors
)))
1901 (unless (listp styles
) (setq styles
(list styles
)))
1902 (loop for f in
(cdr fun
) for points-list in points-lists do
1906 (setq style
(nth (mod i
(length styles
)) styles
))
1907 (setq style
(if ($listp style
) (cdr style
) `(,style
))))
1910 (if (member :legend options
)
1911 ;; a legend has been given in the options
1915 (nth (mod (- i
1) (length legend
)) legend
))
1916 nil
)) ; no legend if option [legend,false]
1917 (if (= 2 (length fun
))
1919 (setq plot-name nil
) ;no legend for single function
1920 (format st
" {nolegend 1}"))
1926 string
(coerce (mstring f
) 'string
)))
1927 ((eq (second f
) '$parametric
)
1932 (coerce (mstring (third f
)) 'string
)
1933 ", " (coerce (mstring (fourth f
)) 'string
))))
1934 ((eq (second f
) '$discrete
)
1936 (format nil
"discrete~a" i
)))
1939 (coerce (mstring f
) 'string
))))
1940 (cond ((< (length string
) 80) string
)
1941 (t (format nil
"fun~a" i
)))))))
1943 (format st
" {label ~s}" plot-name
))
1944 (format st
" ~a~%" (xmaxima-curve-style style colors i
))
1945 (format st
" {xversusy~%")
1946 (let ((lis points-list
))
1949 (loop while
(and lis
(not (eq (car lis
) 'moveto
)))
1950 collecting
(car lis
) into xx
1951 collecting
(cadr lis
) into yy
1952 do
(setq lis
(cddr lis
))
1954 ;; only output if at least two points for line
1956 (tcl-output-list st xx
)
1957 (tcl-output-list st yy
)))
1958 ;; remove the moveto
1959 (setq lis
(cddr lis
))))
1963 (cons '(mlist) (cons file output-file
)))
1965 (with-open-file (st file
:direction
:output
:if-exists
:supersede
)
1966 (case (getf options
:plot_format
)
1968 (setq output-file
(gnuplot-print-header st options
))
1970 (when (getf options
:x
)
1971 (format st
" [~{~,8f~^ : ~}]" (getf options
:x
)))
1972 (when (getf options
:y
)
1973 (unless (getf options
:x
)
1975 (format st
" [~{~,8f~^ : ~}]" (getf options
:y
))))
1977 (check-gnuplot-process)
1979 (setq output-file
(gnuplot-print-header *gnuplot-stream
* options
))
1980 (setq *gnuplot-command
* (format nil
"plot"))
1981 (when (getf options
:x
)
1986 (format nil
" [~{~,8f~^ : ~}]" (getf options
:x
)))))
1987 (when (getf options
:y
)
1988 (unless (getf options
:x
)
1989 (setq *gnuplot-command
*
1990 ($sconcat
*gnuplot-command
* (format nil
" []"))))
1995 (format nil
" [~{~,8f~^ : ~}]" (getf options
:y
)))))))
1996 (let ((legend (getf options
:legend
))
1997 (colors (getf options
:color
))
1998 (types (getf options
:point_type
))
1999 (styles (getf options
:style
))
2001 (unless (listp legend
) (setq legend
(list legend
)))
2002 (unless (listp colors
) (setq colors
(list colors
)))
2003 (unless (listp styles
) (setq styles
(list styles
)))
2004 (loop for v in
(cdr fun
) for points-list in points-lists do
2006 (case (getf options
:plot_format
)
2009 (setq *gnuplot-command
*
2010 ($sconcat
*gnuplot-command
* ", ")))
2011 (setq *gnuplot-command
*
2012 ($sconcat
*gnuplot-command
*
2013 (format nil
"~s index ~a " file i
)))))
2015 (setq style
(nth (mod i
(length styles
)) styles
))
2017 (when ($listp style
) (setq style
(cdr style
)))
2019 (if (member :legend options
)
2020 ;; a legend has been defined in the options
2024 (nth (mod (- i
1) (length legend
)) legend
))
2025 nil
)) ; no legend if option [legend,false]
2026 (if (= 2 (length fun
))
2027 (setq plot-name nil
) ; no legend if just one function
2032 (coerce (mstring v
) 'string
)))
2033 ((eq (second v
) '$parametric
)
2038 (coerce (mstring (third v
)) 'string
)
2040 (coerce (mstring (fourth v
)) 'string
))))
2041 ((eq (second v
) '$discrete
)
2043 string
(format nil
"discrete~a" i
)))
2046 (coerce (mstring v
) 'string
))))
2047 (cond ((< (length string
) 80) string
)
2048 (t (format nil
"fun~a" i
)))))))
2049 (case (getf options
:plot_format
)
2051 (when (> i
1) (format st
","))
2054 (format st
" title ~s" plot-name
)
2055 (format st
" notitle"))
2057 (gnuplot-curve-style style colors types i
)))
2059 (setq *gnuplot-command
*
2064 nil
" title ~s ~a" plot-name
2065 (gnuplot-curve-style style colors types i
))
2068 (gnuplot-curve-style style colors types i
)
2070 (case (getf options
:plot_format
)
2076 (loop for v in
(cdr fun
) for points-list in points-lists do
2079 (case (getf options
:plot_format
)
2085 (format st
"~%~%")))
2087 (format st
"~%~%# \"Fun~a\"~%" i
))
2089 (let (in-discontinuity points
)
2090 (loop for
(v w
) on points-list by
#'cddr
2092 (cond ((eq v
'moveto
)
2094 ((find (getf options
:plot_format
) '($gnuplot_pipes $gnuplot
))
2095 ;; A blank line means a discontinuity
2096 (if (null in-discontinuity
)
2099 (setq in-discontinuity t
))))
2100 ((equal (getf options
:plot_format
) '$mgnuplot
)
2101 ;; A blank line means a discontinuity
2104 (format st
"move "))))
2105 (t (format st
"~g ~g ~%" v w
)
2107 (setq in-discontinuity nil
))))
2108 (if (and (null points
)
2109 (first (getf options
:x
))
2110 (first (getf options
:y
)))
2113 (first (getf options
:x
))
2114 (first (getf options
:y
))))))))))
2116 (case (getf options
:plot_format
)
2118 (gnuplot-process options file output-file
))
2120 (send-gnuplot-command *gnuplot-command
*))
2122 ($system
(concatenate 'string
*maxima-plotdir
* "/" $mgnuplot_command
)
2123 (format nil
" -plot2d ~s -title ~s" file
"Fun1"))))
2124 (cons '(mlist) (cons file output-file
))))
2128 (and (symbolp x
) (char= (char (symbol-value x
) 0) #\$
)))
2131 (defun $tcl_output
(lis i
&optional
(skip 2))
2132 (when (not (typep i
'fixnum
))
2134 (intl:gettext
"tcl_ouput: second argument must be an integer; found ~M")
2136 (when (not ($listp lis
))
2138 (intl:gettext
"tcl_output: first argument must be a list; found ~M") lis
))
2139 (format *standard-output
* "~% {")
2140 (cond (($listp
(second lis
))
2143 (format *standard-output
* "~,10g " (nth i v
))))
2145 (setq lis
(nthcdr i lis
))
2146 (loop with v
= lis while v
2148 (format *standard-output
* "~,10g " (car v
))
2149 (setq v
(nthcdr skip v
)))))
2150 (format *standard-output
* "~% }"))
2151 (defun tcl-output-list ( st lis
)
2157 when
(eql 0 (mod n
5))
2160 (format st
"~,10g " v
))
2162 (t (tcl-output-list st
(car lis
))
2163 (tcl-output-list st
(cdr lis
)))))
2165 (defun check-range (range &aux tem a b
)
2166 (or (and ($listp range
)
2167 (setq tem
(cdr range
))
2168 (or (symbolp (car tem
)) ($subvarp
(car tem
)))
2169 (numberp (setq a
($float
(meval* (second tem
)))))
2170 (numberp (setq b
($float
(meval* (third tem
)))))
2174 (intl:gettext
"plotting: range must be of the form [variable, min, max]; found: ~M")
2177 (intl:gettext
"plotting: no range given; must supply range of the form [variable, min, max]"))))
2178 `((mlist) ,(car tem
) ,(float a
) ,(float b
)))
2180 (defun $zero_fun
(x y
) x y
0.0)
2182 (defun output-points (pl &optional m
)
2183 "If m is supplied print blank line every m lines"
2185 (declare (fixnum j
))
2186 (loop for i below
(length (polygon-pts pl
))
2187 with ar
= (polygon-pts pl
)
2188 do
(print-pt (aref ar i
))
2190 (print-pt (aref ar i
))
2192 (print-pt (aref ar i
))
2196 (cond ((eql j
(the fixnum m
))
2201 (defun output-points-tcl (dest pl m
)
2202 (format dest
" {matrix_mesh ~%")
2203 ;; x y z are done separately:
2204 (loop for off from
0 to
2
2205 with ar
= (polygon-pts pl
)
2206 with i of-type fixnum
= 0
2210 while
(< i
(length ar
))
2211 do
(format dest
"~% {")
2213 do
(print-pt (aref ar i
))
2215 (format dest
"}~%"))
2216 (format dest
"}~%"))
2217 (format dest
"}~%"))
2219 (defun show-open-plot (ans file
)
2220 (cond ($show_openplot
2221 (with-open-file (st1 (plot-temp-file "maxout.xmaxima") :direction
:output
:if-exists
:supersede
)
2223 ($system
(concatenate 'string
*maxima-prefix
*
2224 (if (string= *autoconf-win32
* "true") "\\bin\\" "/bin/")
2225 $xmaxima_plot_command
)
2226 #-
(or (and sbcl win32
) (and ccl windows
))
2227 (format nil
" ~s &" file
)
2228 #+(or (and sbcl win32
) (and ccl windows
))
2230 (t (princ ans
) "")))
2233 ;; contour_plot -- set some parameters for Gnuplot and punt to plot3d
2235 ;; We go to some trouble here to avoid clobbering the Gnuplot preamble
2236 ;; specified by the user, either as a global option (via set_plot_option)
2237 ;; or specified in arguments to contour_plot. Just append or prepend
2238 ;; the parameters for contour plotting to the user-specified preamble.
2239 ;; Assume that arguments take precedence over global options.
2241 ;; contour_plot knows how to set parameters only for Gnuplot.
2242 ;; If the plot_format is not a Gnuplot format, complain.
2246 ;; contour_plot (x^2 + y^2, [x, -4, 4], [y, -4, 4]);
2247 ;; contour_plot (sin(y) * cos(x)^2, [x, -4, 4], [y, -4, 4]);
2248 ;; F(x, y) := x^3 + y^2;
2249 ;; contour_plot (F, [u, -4, 4], [v, -4, 4]);
2250 ;; contour_plot (F, [u, -4, 4], [v, -4, 4], [gnuplot_preamble, "set size ratio -1"]);
2251 ;; set_plot_option ([gnuplot_preamble, "set cntrparam levels 12"]);
2252 ;; contour_plot (F, [u, -4, 4], [v, -4, 4]);
2253 ;; set_plot_option ([plot_format, xmaxima]);
2254 ;; contour_plot (F, [u, -4, 4], [v, -4, 4]); => error: must be gnuplot format
2255 ;; contour_plot (F, [u, -4, 4], [v, -4, 4], [plot_format, gnuplot]);
2257 (defun $contour_plot
(expr &rest optional-args
)
2259 ((plot-format-in-options (getf *plot-options
* :plot_format
))
2260 (plot-format-in-arguments
2261 (caddar (member '$plot_format optional-args
:key
#'cadr
)))
2262 (preamble-in-plot-options (getf *plot-options
* :gnuplot_preamble
))
2263 (preamble-in-arguments
2264 (caddar (member '$gnuplot_preamble optional-args
:key
#'cadr
)))
2265 (contour-preamble "set contour; unset surface; set view map")
2266 (gnuplot-formats '($gnuplot $gnuplot_pipes
))
2268 ;; Ensure that plot_format is some gnuplot format.
2269 ;; Argument takes precedence over global option.
2272 (and plot-format-in-arguments
2273 (not (member plot-format-in-arguments gnuplot-formats
:test
#'eq
)))
2274 (and (not plot-format-in-arguments
)
2275 (not (member plot-format-in-options gnuplot-formats
:test
#'eq
))))
2277 (mtell(intl:gettext
"contour_plot: plot_format = ~a not recognized; must be a gnuplot format.~%")
2278 (ensure-string (or plot-format-in-arguments plot-format-in-options
)))
2279 (return-from $contour_plot
)))
2281 ;; Prepend contour preamble to preamble in arguments (if given)
2282 ;; and pass concatenated preamble as an argument to plot3d.
2283 ;; Otherwise if there is a global option preamble,
2284 ;; append contour preamble to global option preamble.
2285 ;; Otherwise just set global option preamble to the contour preamble.
2287 ;; All this complication is to avoid clobbering the preamble
2288 ;; if one was specified somehow (either global option or argument).
2290 (if preamble-in-arguments
2293 (remove-if #'(lambda (e) (and ($listp e
) (eq ($first e
) '$gnuplot_preamble
))) optional-args
))
2295 ($sconcat contour-preamble
(format nil
"~%")
2296 preamble-in-arguments
)))
2297 (if preamble-in-plot-options
2299 ($sconcat preamble-in-plot-options
(format nil
"~%")
2301 (setq preamble contour-preamble
)))
2305 (list '((mlist) $palette nil
))
2306 (list `((mlist) $gnuplot_preamble
,preamble
))))))
2311 ;; plot3d (2^(-u^2 + v^2), [u, -3, 3], [v, -2, 2], [palette, false]);
2313 ;; plot3d ( log ( x^2*y^2 ), [x, -2, 2], [y, -2, 2], [grid, 29, 29]);
2315 ;; expr_1: cos(y)*(10.0+6*cos(x))$
2316 ;; expr_2: sin(y)*(10.0+6*cos(x))$
2317 ;; expr_3: -6*sin(x)$
2318 ;; plot3d ([expr_1, expr_2, expr_3], [x, 0, 2*%pi], [y, 0, 2*%pi],
2319 ;; ['grid, 40, 40], [z,-8,8]);
2321 ;; plot3d (cos (-x^2 + y^3/4), [x, -4, 4], [y, -4, 4],
2322 ;; [mesh_lines_color, false], [elevation, 0], [azimuth, 0], [colorbox, true],
2323 ;; [grid, 150, 150])$
2325 ;; spherical: make_transform ([th, phi,r], r*sin(phi)*cos(th),
2326 ;; r*sin(phi)*sin(th), r*cos(phi))$
2327 ;; plot3d ( 5, [th, 0, 2*%pi], [phi, 0, %pi], [transform_xy, spherical],
2328 ;; [palette, [value, 0.65, 0.7, 0.1, 0.9]], [plot_format,xmaxima]);
2330 ;; V: 1 / sqrt ( (x+1)^2+y^2 ) - 1 / sqrt( (x-1)^2+y^2 )$
2331 ;; plot3d ( V, [x, -2, 2], [y, -2, 2], [z, -4, 4])$
2335 (fun &rest extra-options
2337 lvars trans xrange yrange
2338 functions exprn domain tem
(options (copy-tree *plot-options
*))
2339 ($in_netmath $in_netmath
)
2340 (*plot-realpart
* *plot-realpart
*)
2341 gnuplot-term gnuplot-out-file file titles output-file
2342 (usage (intl:gettext
2344 To plot a single function f of 2 variables v1 and v2:
2345 plot3d (f, [v1, min, max], [v2, min, max], options)
2346 A parametric representation of a surface with parameters v1 and v2:
2347 plot3d ([f1, f2, f3], [v1, min, max], [v2, min, max], options)
2348 Several functions depending on the two variables v1 and v2:
2349 plot3d ([f1, f2, ..., fn, [v1, min, max], [v2, min, max]], options)")))
2351 (setf (getf options
:type
) "plot3d")
2353 ;; Ensure that fun is a list of expressions and maxima lists, followed
2354 ;; by a domain definition
2356 (if (= 1 (length (check-list-plot3d fun
)))
2357 ;; fun consisted of a single parametric expression
2358 (setq fun
`(,fun
,(pop extra-options
) ,(pop extra-options
)))
2359 ;; fun was a maxima list with several independent surfaces
2361 ;; fun consisted of a single expression
2362 (setq fun
`(,fun
,(pop extra-options
) ,(pop extra-options
))))
2364 ;; go through all the independent surfaces creating the functions stack
2366 (setq exprn
(pop fun
))
2369 (setq domain
(check-list-plot3d exprn
))
2370 (case (length domain
)
2372 ;; exprn is a parametric representation of a surface
2373 (let (vars1 vars2 vars3
)
2374 ;; list fun should have two valid ranges after exprn
2375 (setq xrange
(check-range (pop fun
)))
2376 (setq yrange
(check-range (pop fun
)))
2377 ;; list of the two variables for the parametric equations
2378 (setq lvars
`((mlist),(second xrange
) ,(second yrange
)))
2379 ;; make sure that the 3 parametric equations depend only
2380 ;; on the two variables in lvars
2382 ($listofvars
(mfuncall
2383 (coerce-float-fun (second exprn
) lvars
)
2384 (second lvars
) (third lvars
))))
2386 ($listofvars
(mfuncall
2387 (coerce-float-fun (third exprn
) lvars
)
2388 (second lvars
) (third lvars
))))
2390 ($listofvars
(mfuncall
2391 (coerce-float-fun (fourth exprn
) lvars
)
2392 (second lvars
) (third lvars
))))
2393 (setq lvars
($listofvars
`((mlist) ,vars1
,vars2
,vars3
)))
2394 (if (<= ($length lvars
) 2)
2395 ;; we do have a valid parametric set. Push it into
2396 ;; the functions stack, along with their domain
2398 (push `(,exprn
,xrange
,yrange
) functions
)
2399 ;; add a title to the titles stack
2400 (push "Parametric function" titles
)
2401 ;; unknown variables in the parametric equations
2402 ;; ----- GNUPLOT 4.0 WORK-AROUND -----
2403 (when (and ($constantp
(fourth exprn
))
2404 (getf options
:gnuplot_4_0
))
2405 (setf (getf options
:const_expr
)
2406 ($float
(meval (fourth exprn
))))))
2408 (intl:gettext
"plot3d: there must be at most two variables; found: ~M")
2412 ;; expr is a simple function with its own domain. Push the
2413 ;; function and its domain into the functions stack
2414 (setq xrange
(second domain
))
2415 (setq yrange
(third domain
))
2416 (push `(,(second exprn
) ,xrange
,yrange
) functions
)
2417 ;; push a title for this plot into the titles stack
2418 (if (< (length (ensure-string (second exprn
))) 36)
2419 (push (ensure-string (second exprn
)) titles
)
2420 (push "Function" titles
)))
2423 ;; syntax error. exprn does not have the expected form
2425 (intl:gettext
"plot3d: argument must be a list of three expressions; found: ~M")
2428 ;; exprn is a simple function, defined in the global domain.
2429 (if (and (getf options
:xvar
) (getf options
:yvar
))
2430 ;; the global domain has already been defined; use it.
2432 (setq xrange
`((mlist) ,(getf options
:xvar
)
2433 ,(first (getf options
:x
))
2434 ,(second (getf options
:x
))))
2435 (setq yrange
`((mlist) ,(getf options
:yvar
)
2436 ,(first (getf options
:y
))
2437 ,(second (getf options
:y
)))))
2438 ;; the global domain should be defined by the last two lists
2439 ;; in fun. Extract it and check whether it is valid.
2443 (check-list-plot3d (append `((mlist) ,exprn
) (last fun
2))))
2444 (setq fun
(butlast fun
2))
2445 (if (= 3 (length domain
))
2446 ;; domain is valid. Make it the global one.
2448 (setq xrange
(second domain
))
2449 (setq yrange
(third domain
))
2450 (setf (getf options
:xvar
) (second xrange
))
2451 (setf (getf options
:x
) (cddr xrange
))
2452 (setf (getf options
:yvar
) (second yrange
))
2453 (setf (getf options
:y
) (cddr yrange
)))
2455 ;; ----- GNUPLOT 4.0 WORK-AROUND -----
2456 (when (and ($constantp exprn
) (getf options
:$gnuplot_4_0
))
2457 (setf (getf options
:const_expr
) ($float
(meval exprn
))))
2458 ;; push the function and its domain into the functions stack
2459 (push `(,exprn
,xrange
,yrange
) functions
)
2460 ;; push a title for this plot into the titles stack
2461 (if (< (length (ensure-string exprn
)) 36)
2462 (push (ensure-string exprn
) titles
)
2463 (push "Function" titles
))))
2464 (when (= 0 (length fun
)) (return)))
2466 ;; recover the original ordering for the functions and titles stacks
2467 (setq functions
(reverse functions
))
2468 (setq titles
(reverse titles
))
2470 ;; parse the options given to plot3d
2471 (setq options
(plot-options-parser extra-options options
))
2472 (setq tem
(getf options
:transform_xy
))
2473 (setq *plot-realpart
* (getf options
:plot_realpart
))
2475 ;; set up the labels for the axes, unless no box is being shown
2476 (unless (and (member :box options
) (not (getf options
:box
)))
2477 (if (and (getf options
:xvar
) (getf options
:yvar
) (null tem
))
2479 ;; Don't set xlabel (ylabel) if the user specified one.
2480 (unless (getf options
:xlabel
)
2481 (setf (getf options
:xlabel
) (ensure-string (getf options
:xvar
))))
2482 (unless (getf options
:ylabel
)
2483 (setf (getf options
:ylabel
) (ensure-string (getf options
:yvar
)))))
2485 (setf (getf options
:xlabel
) "x")
2486 (setf (getf options
:ylabel
) "y")))
2487 (unless (getf options
:zlabel
) (setf (getf options
:zlabel
) "z")))
2489 ;; x and y should not be bound, when an xy transformation function is used
2490 (when tem
(remf options
:x
) (remf options
:y
))
2492 ;; Name of the gnuplot commands file and output file
2493 (setf gnuplot-term
(getf options
:gnuplot_term
))
2494 (setf gnuplot-out-file
(getf options
:gnuplot_out_file
))
2495 (if (and (find (getf options
:plot_format
) '($gnuplot_pipes $gnuplot
))
2496 (eq gnuplot-term
'$default
) gnuplot-out-file
)
2497 (setq file
(plot-file-path gnuplot-out-file
))
2500 (format nil
"maxout.~(~a~)"
2501 (ensure-string (getf options
:plot_format
))))))
2504 (setq $in_netmath
(eq (getf options
:plot_format
) '$xmaxima
)))
2506 ;; Set up the output file stream
2508 (cond ($in_netmath
*standard-output
*)
2509 (t (open file
:direction
:output
:if-exists
:supersede
))))
2510 (palette (getf options
:palette
))
2511 (legend (getf options
:legend
)) (n (length functions
)))
2512 ;; titles will be a list. The titles given in the legend option
2513 ;; will have priority over the titles generated by plot3d.
2514 ;; no legend if option [legend,false]
2515 (unless (listp legend
) (setq legend
(list legend
)))
2516 (when (member :legend options
) ;; use titles given by legend option
2517 (if (first legend
) (setq titles legend
)) (setq titles nil
))
2520 (case (getf options
:plot_format
)
2522 (setq output-file
(gnuplot-print-header $pstream options
))
2523 (when (and (member :gnuplot_pm3d options
)
2524 (not (getf options
:gnuplot_pm3d
)))
2528 (gnuplot-plot3d-command "-" palette
2529 (getf options
:gnuplot_curve_styles
)
2530 (getf options
:color
)
2533 (when (and (member :gnuplot_pm3d options
)
2534 (not (getf options
:gnuplot_pm3d
)))
2536 (check-gnuplot-process)
2538 (setq output-file
(gnuplot-print-header *gnuplot-stream
* options
))
2541 (gnuplot-plot3d-command file palette
2542 (getf options
:gnuplot_curve_styles
)
2543 (getf options
:color
)
2546 (when (getf options
:ps_file
)
2547 (setq output-file
(list (getf options
:ps_file
))))
2548 (xmaxima-print-header $pstream options
))
2550 (format $pstream
"LIST~%")))
2552 ;; generate the mesh points for each surface in the functions stack
2554 (dolist (f functions
)
2556 (setq fun
(first f
))
2557 (setq xrange
(second f
))
2558 (setq yrange
(third f
))
2562 ($make_transform
`((mlist) ,(second xrange
)
2563 ,(second yrange
) $z
)
2564 (second fun
) (third fun
) (fourth fun
)))
2565 (setq fun
'$zero_fun
))
2567 (setq lvars
`((mlist) ,(second xrange
) ,(second yrange
)))
2568 (setq fun
(coerce-float-fun fun lvars
))
2574 ($listofvars
(mfuncall fun
(second lvars
) (third lvars
))))))
2575 (mtell (intl:gettext
"plot3d: expected <expr. of v1 and v2>, [v1, min, max], [v2, min, max]~%"))
2576 (mtell (intl:gettext
"plot3d: keep going and hope for the best.~%")))))
2579 fun
(third xrange
) (fourth xrange
) (third yrange
)
2580 (fourth yrange
) (first (getf options
:grid
))
2581 (second (getf options
:grid
))))
2582 (ar (polygon-pts pl
))
2583 (colors (getf options
:color
))
2584 (palettes (getf options
:palette
)))
2585 (declare (type (cl:array t
) ar
))
2587 (if trans
(mfuncall trans ar
))
2588 (if tem
(mfuncall tem ar
))
2590 (case (getf options
:plot_format
)
2592 (when (> i
1) (format $pstream
"e~%"))
2593 (output-points pl
(first (getf options
:grid
))))
2595 (when (> i
1) (format $pstream
"~%~%"))
2596 (output-points pl
(first (getf options
:grid
))))
2598 (when (> i
1) (format $pstream
"~%~%# \"Fun~a\"~%" i
))
2599 (output-points pl
(first (getf options
:grid
))))
2602 (format $pstream
" ~a~%" (xmaxima-palettes palettes i
))
2603 (format $pstream
" {mesh_lines ~a}"
2604 (xmaxima-color colors i
)))
2605 (output-points-tcl $pstream pl
(first (getf options
:grid
))))
2607 (format $pstream
"{ appearance { +smooth }~%MESH ~a ~a ~%"
2608 (+ 1 (first (getf options
:grid
)))
2609 (+ 1 (second (getf options
:grid
))))
2610 (output-points pl nil
)
2611 (format $pstream
"}~%"))))))
2613 ;; close the stream and plot..
2614 (cond ($in_netmath
(format $pstream
"}~%") (return-from $plot3d
""))
2615 ((eql (getf options
:plot_format
) '$xmaxima
)
2616 (format $pstream
"}~%")
2619 (setq $pstream nil
))))
2620 (if (eql (getf options
:plot_format
) '$gnuplot
)
2621 (gnuplot-process options file output-file
)
2622 (cond ((getf options
:run_viewer
)
2623 (case (getf options
:plot_format
)
2627 'string
*maxima-prefix
*
2628 (if (string= *autoconf-win32
* "true") "\\bin\\" "/bin/")
2629 $xmaxima_plot_command
)
2630 #-
(or (and sbcl win32
) (and ccl windows
))
2631 (format nil
" ~s &" file
)
2632 #+(or (and sbcl win32
) (and ccl windows
))
2635 ($system $geomview_command
2636 (format nil
" \"~a\"" file
)))
2638 (send-gnuplot-command *gnuplot-command
*))
2642 'string
*maxima-plotdir
* "/" $mgnuplot_command
)
2643 (format nil
" -parametric3d \"~a\"" file
))))))))
2644 (cons '(mlist) (cons file output-file
)))
2646 ;; Given a Maxima list with 3 elements, checks whether it represents a function
2647 ;; defined in a 2-dimensional domain or a parametric representation of a
2648 ;; 3-dimensional surface, depending on two parameters.
2649 ;; The return value will be a Maxima list if the test is succesfull or nil
2651 ;; In the case of a function and a 2D domain, it returns the domain, validated.
2652 ;; When it is a parametric representation it returns an empty Maxima list.
2654 (defun check-list-plot3d (lis)
2655 (let (xrange yrange
)
2656 ;; Makes sure list has the form ((mlist) $atom item1 item2)
2657 (unless (and ($listp lis
) (= 3 ($length lis
)) (not ($listp
(second lis
))))
2658 (return-from check-list-plot3d nil
))
2659 ;; we might have a function with domain or a parametric representation
2660 (if ($listp
(third lis
))
2661 ;; lis is probably a function with a valid domain
2662 (if ($listp
(fourth lis
))
2663 ;; we do have a function and a domain. Return the domain
2665 (setq xrange
(check-range (third lis
)))
2666 (setq yrange
(check-range (fourth lis
)))
2667 (return-from check-list-plot3d
`((mlist) ,xrange
,yrange
)))
2668 ;; wrong syntax: [expr1, list, expr2]
2669 (return-from check-list-plot3d nil
))
2670 ;; lis is probably a parametric representation
2671 (if ($listp
(fourth lis
))
2672 ;; wrong syntax: [expr1, expr2, list]
2673 (return-from check-list-plot3d nil
)
2674 ;; we do have a parametric representation. Return an empty list
2675 (return-from check-list-plot3d
'((mlist)))))))