463 lines
14 KiB
Text
463 lines
14 KiB
Text
;; @module plot.lsp
|
|
;; @description Routines for creating data plots.
|
|
;; @version 1.1 initial release
|
|
;; @version 1.2 allow different length data vectors
|
|
;; @version 2.0 added plotXY for plotting data points -> (x, y)
|
|
;; @version 2.1 added plot:reset for resetting optional labels and settings
|
|
;; @version 2.2 option plot:data-min, plot:data-max did not work
|
|
;; @author Lutz Mueller, September 2011, April 2012
|
|
;;
|
|
;; In its initial release the <tt>plot.lsp</tt> module can draw
|
|
;; simple line plots from one to five data sets. In its simplest
|
|
;; form only the <tt>plot</tt> command is necessary. A group of
|
|
;; parameters can be set to further customize the plot. Plots can
|
|
;; be save to a graphics file.
|
|
;;
|
|
;; Load the module using:
|
|
;; <pre>(load "plot.lsp")</pre>
|
|
;; This module runs the newLISP Java based Guiserver. The file
|
|
;; <tt>guiserver.jar</tt> is installed by one of the newLISP binary
|
|
;; installers in a standard location. Executing <tt>(test-plot)</tt>
|
|
;; will generate a test plots which can be seen here:
|
|
;; @link http://newlisp.org/code/example-line-plot.png line-plot
|
|
;; and
|
|
;; @link http://newlisp.org/code/example-xy-plot.png XY-plot .
|
|
;;
|
|
;; Several variables can be set optionally to change the size,
|
|
;; and positioning of the plot area in the image.
|
|
;; Other variables can be set to control the partioning of the grid,
|
|
;; labeling of the horizontal axis and legend.
|
|
;; The following list shows all parameters with their default
|
|
;; values:
|
|
;; <pre>
|
|
;; ; mandatory and preset with default values
|
|
;; plot:wwidth 640 ; window width in pixels
|
|
;; plot:wheight 420 ; window height in pixels
|
|
;; plot:origin-x 64 ; top left x of plot area
|
|
;; plot:origin-y 64 ; top left y of plot area
|
|
;; plot:pwidth 520 ; width of plot area
|
|
;; plot:pheight 280 ; height of plot area<br/>
|
|
;; ; optional
|
|
;; plot:data-min nil ; minimum value on the Y axis
|
|
;; plot:data-max nil ; maximum value on the Y axis
|
|
;; plot:title nil ; top centered main title
|
|
;; plot:sub-title nil ; sub title under main title
|
|
;; plot:unit-y nil ; a unit label centered left to the Y axis
|
|
;; plot:labels nil ; a list of string labels for vertical grid
|
|
;; plot:legend nil ; a list of string labels<br/>
|
|
;; ; optional for plotXY only
|
|
;; plot:data-x-min nil ; minimum value on the X axis
|
|
;; plot:data-x-max nil ; maximum value on the X axis
|
|
;; plot:data-y-min nil ; minimum value on the Y axis
|
|
;; plot:data-y-max nil ; maximum value on the Y axis
|
|
;; plot:unit-x nil ; a unit label centered under the X axis
|
|
;; </pre>
|
|
;;
|
|
;; Only the the first group of variables is mandatory and preset to
|
|
;; the values shown above. Options in the second group will be either
|
|
;; suppressed or set automatically.
|
|
|
|
;; @syntax (plot <list-data> [<list-data> . . . ])
|
|
;; @param <list-data> One to five lists of data points.
|
|
;; <br>
|
|
;; The function draws one or more horizontal data lines from up
|
|
;; to five data sets in <list-data>. Colors are chosen in the sequence
|
|
;; red, green, blue, yellow and purple.
|
|
;; <br><br>
|
|
;; The following example doesn't set any extra options and plots to random
|
|
;; data sets. Scale and labels on the vertical and horizontal axis
|
|
;; will be set automatically. No title, sub-title and legends will
|
|
;; be printed.
|
|
;;
|
|
;; @example
|
|
;; (plot (random 10 5 50) (normal 10 0.5 50))
|
|
|
|
;; The following example sets several options then plots and
|
|
;; exports the image to a PNG graphics file:
|
|
;;
|
|
;; @example
|
|
;; (set 'plot:title "The Example Plot")
|
|
;; (set 'plot:sub-title "several random data sets")
|
|
;; (set 'plot:labels '("1" "" "20" "" "40" "" "60" "" "80" "" "100"))
|
|
;; (set 'plot:legend '("first" "second" "third" "fourth"))
|
|
;;
|
|
;; ; display plot image
|
|
;; (plot (random 10 5 100)
|
|
;; (normal 10 0.5 100)
|
|
;; (normal 8 2 100)
|
|
;; (normal 5 1 100) )
|
|
;;
|
|
;; ; save the displayed image to a file
|
|
;; (plot:export "example-plot.png")
|
|
;;
|
|
|
|
;; @syntax (plot:XY <list-data-X> <list-data-Y>)
|
|
;; @param <list-data-X> List of X coordinates of data points.
|
|
;; @param <list-data-X> List of Y coordinates of data points.
|
|
;; <br>
|
|
;; Draws data points of data in <list-data-X> and <list-data-Y>.
|
|
;;
|
|
|
|
;; @syntax (plot:export <str-file-name>)
|
|
;; @param <str-file-name> The name of the file.
|
|
;;
|
|
;; Exports the current plot shown to a file in PNG format.
|
|
;;
|
|
;; @example
|
|
;; (plot:export "example-plot.png")
|
|
|
|
;; See the example plot
|
|
;; @link http://newlisp.org/code/example-plot.png here .
|
|
|
|
;; @syntax (plot:reset)
|
|
;; <br>
|
|
;; Resets all optional labels and sizes to 'nil'.
|
|
|
|
(define (plot:export file-name)
|
|
(gs:export file-name))
|
|
|
|
|
|
(set-locale "C")
|
|
(load (append (env "NEWLISPDIR") "/guiserver.lsp"))
|
|
|
|
(context 'plot)
|
|
|
|
(set 'wwidth 640)
|
|
(set 'wheight 420)
|
|
|
|
(set 'origin-x 64)
|
|
(set 'origin-y 64)
|
|
|
|
(set 'pwidth 520)
|
|
(set 'pheight 280)
|
|
|
|
(set 'title nil)
|
|
(set 'sub-title nil)
|
|
|
|
(set 'unit-x nil)
|
|
(set 'unit-y nil)
|
|
(set 'data-min nil)
|
|
(set 'data-max nil)
|
|
(set 'labels nil)
|
|
(set 'legend nil)
|
|
|
|
; colors
|
|
(set 'background-color '(0.3 0.3 0.3))
|
|
(set 'text-color '(0.9 0.9 0.9))
|
|
(set 'grid-color '(0.5 0.5 0.5))
|
|
(set 'frame-color '(0.7 0.7 0.7))
|
|
|
|
(set 'line-color (array 5 '(
|
|
(1.0 0.35 0.35) ; 0 red
|
|
(0.0 0.9 0.0) ; 1 green
|
|
(0.6 0.6 1.0) ; 2 blue
|
|
(0.9 0.7 0.0) ; 3 orange
|
|
(0.9 0.3 0.9) ; 4 purple
|
|
)))
|
|
|
|
|
|
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; auxiliary functions ;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
; round to 3 digits precision
|
|
; calculate rounding digits from (sub max min)
|
|
; then apply to all numbers
|
|
(define (rnd num)
|
|
(round num (- (log num 10) 2)))
|
|
|
|
; setup the main window and plot area
|
|
(define (setup-window)
|
|
(gs:init)
|
|
;; describe the GUI
|
|
(gs:frame 'PlotWindow 100 100 wwidth wheight "Plot")
|
|
(gs:set-resizable 'PlotWindow nil)
|
|
(gs:canvas 'PlotCanvas)
|
|
(gs:set-background 'PlotCanvas background-color)
|
|
(gs:add-to 'PlotWindow 'PlotCanvas)
|
|
(gs:set-translation origin-x origin-y)
|
|
|
|
(gs:set-visible 'PlotWindow true)
|
|
; draw title
|
|
(when title
|
|
(gs:set-font 'PlotCanvas "Lucida Sans Typewriter Regular" 18 "bold")
|
|
(gs:draw-text 'title title
|
|
(/ (- pwidth (* (length title) 11)) 2) -34 text-color)
|
|
)
|
|
; draw sub title
|
|
(when sub-title
|
|
(gs:set-font 'PlotCanvas "Lucida Sans Typewriter Regular" 12 "plain")
|
|
(gs:draw-text 'title sub-title
|
|
(/ (- pwidth (* (length sub-title) 7)) 2) -18 text-color)
|
|
)
|
|
)
|
|
|
|
(define (draw-grid L)
|
|
; draw horizontal grid lines
|
|
(gs:set-stroke 1.0)
|
|
(for (i 1 4)
|
|
(let (y (/ (* i pheight) 5) )
|
|
(gs:draw-line 'Grid 0 y pwidth y grid-color))
|
|
)
|
|
|
|
(if labels
|
|
(set 'L (- (length labels) 1))
|
|
(unless L
|
|
(set 'L (if (< N 50) (- N 1) 10)))
|
|
)
|
|
|
|
; draw vertical grid lines
|
|
(gs:set-stroke 1.0)
|
|
(for (i 1 (- L 1))
|
|
(let (x (/ (* i pwidth) L) )
|
|
(gs:draw-line 'Grid x 0 x pheight grid-color)
|
|
)
|
|
)
|
|
|
|
)
|
|
|
|
(define (draw-grid-frame)
|
|
(gs:set-stroke 1.0)
|
|
(gs:draw-rect 'GridFrame 0 0 pwidth pheight)
|
|
(gs:color-tag 'GridFrame frame-color)
|
|
(gs:set-stroke 5.0)
|
|
(gs:draw-rect 'GridMask -3 -3 (+ pwidth 6) (+ pheight 6))
|
|
(gs:color-tag 'GridMask background-color)
|
|
)
|
|
|
|
|
|
(define (draw-labels) ; only used for line plot
|
|
; draw labels for x-range under vertical grid lines
|
|
(gs:set-font 'PlotCanvas "Monospaced" 12 "plain")
|
|
(if labels
|
|
(let (step (div pwidth (- (length labels) 1))
|
|
cnt 0 )
|
|
(dolist (t labels)
|
|
(unless (empty? t)
|
|
(gs:draw-text 'Grid t (int (sub (mul step cnt) (mul (length t) 3.5)))
|
|
(+ pheight 14) text-color))
|
|
(inc cnt))
|
|
)
|
|
; else if no labels
|
|
(begin
|
|
(gs:draw-text 'Grid (format "%5d" 1) -32 (+ pheight 15) text-color)
|
|
(gs:draw-text 'Grid (format "%5d" N) (- pwidth 26) (+ pheight 15) text-color)
|
|
)
|
|
)
|
|
)
|
|
|
|
(define (draw-y-numbers range, y-format step)
|
|
; draw y labels as of y-range left to horizontal grid lines
|
|
(gs:set-font 'PlotCanvas "Monospaced" 12 "plain")
|
|
(set 'y-format (if
|
|
(< range 1) "%8.3f"
|
|
(< range 10) "%8.2f"
|
|
(< range 100) "%8.1f"
|
|
true "%8.0f"))
|
|
|
|
(set 'step (rnd (div range 5)))
|
|
(dotimes (i 6)
|
|
(let (y (- pheight -4 (* i (/ pheight 5))))
|
|
(gs:draw-text 'Grid
|
|
(format y-format (add ymin (mul i step)))
|
|
-64 y text-color))
|
|
)
|
|
|
|
; draw unit label to left center of grid
|
|
(when unit-y
|
|
(gs:draw-text 'Grid unit-y -50 (+ (/ pheight 2) 4) text-color))
|
|
)
|
|
|
|
(define (draw-x-numbers n range xmin, x-format x-offset L step)
|
|
; draw numbers for x-range under vertical grid lines
|
|
(gs:set-font 'PlotCanvas "Monospaced" 12 "plain")
|
|
|
|
(set 'x-format (if
|
|
(< range 1) "%8.3f"
|
|
(< range 10) "%8.2f"
|
|
(< range 100) "%8.1f"
|
|
true "%8.0f"))
|
|
|
|
(set 'x-offset (if
|
|
(< range 1) 20
|
|
(< range 10) 14
|
|
(< range 100) 8
|
|
true 2 ))
|
|
|
|
|
|
(set 'L 10)
|
|
(set 'step (rnd (div range L)))
|
|
|
|
(for (i 0 (- L 1))
|
|
(let ( x (+ (* i (/ pwidth L)) x-offset) )
|
|
(gs:draw-text 'Grid
|
|
(format x-format (add xmin (mul (+ i 1) step)))
|
|
x (+ pheight 15) text-color)
|
|
)
|
|
)
|
|
; unit label
|
|
(when unit-x
|
|
(gs:draw-text 'Grid unit-x (- (/ pwidth 2) 30) (+ pheight 30) text-color))
|
|
)
|
|
|
|
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; user functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
; take one or more data vectors (lists) as arguments
|
|
(define (plot:plot)
|
|
(setup-window)
|
|
; set data min, max and range over all plot vectors
|
|
(set 'M (length (args)))
|
|
(set 'N 0)
|
|
(dotimes (m M)
|
|
(set 'data (args m) )
|
|
(set 'N (max N (length data)))
|
|
(unless (and data-min data-max)
|
|
(if (= 0 m)
|
|
(begin
|
|
(set 'ymin (apply min data))
|
|
(set 'ymax (apply max data)) )
|
|
(begin
|
|
(set 'ymin (min ymin (apply min data)))
|
|
(set 'ymax (max ymax (apply max data))))
|
|
)
|
|
)
|
|
)
|
|
|
|
; overwrite ymin and ymax if data-min/mx are defined
|
|
(when (and data-min data-max)
|
|
(set 'ymin data-min)
|
|
(set 'ymax data-max))
|
|
|
|
(set 'y-range (sub ymax ymin))
|
|
(draw-grid)
|
|
(draw-labels)
|
|
(draw-y-numbers y-range)
|
|
|
|
; for each data vector draw data
|
|
(gs:set-stroke 1.5)
|
|
(dotimes (m M)
|
|
(set 'data (args m))
|
|
; draw data
|
|
(set 'last-x nil 'last-y nil)
|
|
(dotimes (i (length data))
|
|
(let (x (div (mul i pwidth) (- N 1))
|
|
y (div (mul (sub (pop data) ymin) pheight) y-range))
|
|
(when last-x
|
|
(gs:draw-line 'seg last-x (- pheight last-y)
|
|
(int x) (int (- pheight y)) (line-color m)))
|
|
(set 'last-x (int x) 'last-y (int y))
|
|
)
|
|
)
|
|
)
|
|
|
|
; draw legend depending on the number of plot lines
|
|
; style below
|
|
(dotimes (m M)
|
|
(let (x (* m (/ pwidth 5)))
|
|
(when (and (list? legend) (= M (length legend)))
|
|
(gs:draw-line 'legend x (+ pheight 30) (+ x 25) (+ pheight 30) (line-color m))
|
|
(gs:draw-text 'legend (legend m) (+ x 1 30) (+ pheight 33) text-color)
|
|
)
|
|
)
|
|
)
|
|
|
|
(draw-grid-frame)
|
|
(gs:set-visible 'PlotWindow true)
|
|
)
|
|
|
|
(define (plot:XY data-x data-y (idx-color 3) , N, xmin, xmax, ymin, ymax)
|
|
; set data min, max and range over the two data vectors
|
|
(set 'N (length data-x))
|
|
(when (!= N (length data-y))
|
|
(throw-error "X vecor and Y vector must be of equal length"))
|
|
|
|
(setup-window)
|
|
|
|
(set 'xmin (apply min data-x))
|
|
(set 'xmax (apply max data-x))
|
|
(set 'ymin (apply min data-y))
|
|
(set 'ymax (apply max data-y))
|
|
|
|
(when (and data-x-min data-x-max)
|
|
(set 'xmin data-x-min)
|
|
(set 'xmax data-x-max))
|
|
|
|
(when (and data-y-min data-y-max)
|
|
(set 'ymin data-y-min)
|
|
(set 'ymax data-y-max))
|
|
|
|
(set 'x-range (sub xmax xmin))
|
|
(set 'y-range (sub ymax ymin))
|
|
|
|
(draw-grid 10)
|
|
(draw-y-numbers y-range)
|
|
(draw-x-numbers N x-range xmin)
|
|
|
|
; draw XY data points in (line-color idx-color)
|
|
(gs:set-stroke 1.5)
|
|
(dotimes (i N)
|
|
(let (x (div (mul (sub (pop data-x) xmin) pwidth) x-range)
|
|
y (div (mul (sub (pop data-y) ymin) pheight) y-range))
|
|
(if (and (< x pwidth) (< y pheight) (>= x 0) (>= y 0))
|
|
(gs:draw-circle 'xy-points (int x) (int (- pheight y)) 3 (line-color idx-color)))
|
|
)
|
|
)
|
|
|
|
(draw-grid-frame)
|
|
(gs:set-visible 'PlotWindow true)
|
|
)
|
|
|
|
(define (plot:reset)
|
|
(setq
|
|
data-min nil ; minimum value on the Y axis
|
|
data-max nil ; maximum value on the Y axis
|
|
title nil ; top centered main title
|
|
sub-title nil ; sub title under main title<br>
|
|
unit-y nil ; a unit label centered left to the Y axis
|
|
labels nil ; a list of string labels for vertical grid
|
|
legend nil ; a list of string labels
|
|
data-x-min nil ; minimum value on the X axis
|
|
data-x-max nil ; maximum value on the X axis
|
|
data-y-min nil ; minimum value on the Y axis
|
|
data-y-max nil ; maximum value on the Y axis
|
|
unit-x nil ; a unit label centered under the X axis
|
|
)
|
|
)
|
|
;; </pre>
|
|
|
|
(context MAIN)
|
|
|
|
; test
|
|
(define (test-plot)
|
|
; optional title, sub-title, labels and legend, data min/max for Y
|
|
(set 'plot:title "The Example Line Plot")
|
|
(set 'plot:sub-title "several random data sets")
|
|
(set 'plot:labels '("1" "" "20" "" "40" "" "60" "" "80" "" "100"))
|
|
(set 'plot:legend '("first" "second" "third" "fourth"))
|
|
;(set 'plot:data-min 0 'plot:data-max 20)
|
|
|
|
(set 'plot:unit-y "unit y")
|
|
; display plot - only required statement
|
|
(plot (random 10 5 100)
|
|
(normal 10 0.5 100)
|
|
(normal 8 2 100)
|
|
(normal 5 1 100) )
|
|
|
|
; optionally save the display to a file
|
|
(plot:export "example-line-plot.png")
|
|
|
|
; test XY plot
|
|
(plot:reset)
|
|
(set 'plot:title "The Example XY Plot")
|
|
(set 'plot:sub-title "1000 normal distributed data points")
|
|
(set 'plot:unit-x "unit x")
|
|
|
|
(plot:XY (normal 10 3 1000) (normal 0 1 1000) )
|
|
(plot:export "example-xy-plot.png")
|
|
|
|
)
|
|
|
|
|
|
;(plot (random 10 5 50) (normal 10 0.5 50))
|
|
|
|
;(test-plot)
|
|
;(exit)
|
|
|