1 ;;; org-plot.el --- Support for plotting from Org-mode
3 ;; Copyright (C) 2008, 2009 Free Software Foundation, Inc.
5 ;; Author: Eric Schulte <schulte dot eric at gmail dot com>
6 ;; Keywords: tables, plotting
7 ;; Homepage: http://orgmode.org
10 ;; This file is part of GNU Emacs.
12 ;; GNU Emacs is free software: you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation, either version 3 of the License, or
15 ;; (at your option) any later version.
17 ;; GNU Emacs is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;; GNU General Public License for more details.
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
27 ;; Borrows ideas and a couple of lines of code from org-exp.el.
29 ;; Thanks to the org-mode mailing list for testing and implementation
30 ;; and feature suggestions
39 (declare-function gnuplot-delchar-or-maybe-eof
"ext:gnuplot" (arg))
40 (declare-function gnuplot-mode
"ext:gnuplot" ())
41 (declare-function gnuplot-send-buffer-to-gnuplot
"ext:gnuplot" ())
43 (defvar org-plot
/gnuplot-default-options
47 "Default options to gnuplot used by `org-plot/gnuplot'")
49 (defvar org-plot-timestamp-fmt nil
)
51 (defun org-plot/add-options-to-plist
(p options
)
52 "Parse an OPTIONS line and set values in the property list P.
53 Returns the resulting property list."
56 (let ((op '(("type" .
:plot-type
)
67 ("timeind" .
:timeind
)
68 ("timefmt" .
:timefmt
)))
69 (multiples '("set" "line"))
70 (regexp ":\\([\"][^\"]+?[\"]\\|[(][^)]+?[)]\\|[^ \t\n\r;,.]*\\)")
73 (while (setq o
(pop op
))
74 (if (member (car o
) multiples
) ;; keys with multiple values
76 (concat (regexp-quote (car o
)) regexp
)
78 (setq start
(match-end 0))
79 (setq p
(plist-put p
(cdr o
)
80 (cons (car (read-from-string
81 (match-string 1 options
)))
82 (plist-get p
(cdr o
)))))
84 (if (string-match (concat (regexp-quote (car o
)) regexp
)
86 (setq p
(plist-put p
(cdr o
)
87 (car (read-from-string
88 (match-string 1 options
)))))))))))
91 (defun org-plot/goto-nearest-table
()
92 "Move the point forward to the beginning of nearest table.
93 Return value is the point at the beginning of the table."
94 (interactive) (move-beginning-of-line 1)
95 (while (not (or (org-at-table-p) (< 0 (forward-line 1)))))
96 (goto-char (org-table-begin)))
98 (defun org-plot/collect-options
(&optional params
)
99 "Collect options from an org-plot '#+Plot:' line.
100 Accepts an optional property list PARAMS, to which the options
101 will be added. Returns the resulting property list."
103 (let ((line (thing-at-point 'line
)))
104 (if (string-match "#\\+PLOT: +\\(.*\\)$" line
)
105 (org-plot/add-options-to-plist params
(match-string 1 line
))
108 (defun org-plot-quote-timestamp-field (s)
109 "Convert field S from timestamp to Unix time and export to gnuplot."
110 (format-time-string org-plot-timestamp-fmt
(org-time-string-to-time s
)))
112 (defun org-plot-quote-tsv-field (s)
113 "Quote field S for export to gnuplot."
114 (if (string-match org-table-number-regexp s
) s
115 (if (string-match org-ts-regexp3 s
)
116 (org-plot-quote-timestamp-field s
)
117 (concat "\"" (mapconcat 'identity
(split-string s
"\"") "\"\"") "\""))))
119 (defun org-plot/gnuplot-to-data
(table data-file params
)
120 "Export TABLE to DATA-FILE in a format readable by gnuplot.
121 Pass PARAMS through to `orgtbl-to-generic' when exporting TABLE."
124 (make-local-variable 'org-plot-timestamp-fmt
)
125 (setq org-plot-timestamp-fmt
(or
126 (plist-get params
:timefmt
)
127 "%Y-%m-%d-%H:%M:%S"))
128 (insert (orgtbl-to-generic
131 '(:sep
"\t" :fmt org-plot-quote-tsv-field
)
135 (defun org-plot/gnuplot-to-grid-data
(table data-file params
)
136 "Export the data in TABLE to DATA-FILE for gnuplot.
137 This means, in a format appropriate for grid plotting by gnuplot.
138 PARAMS specifies which columns of TABLE should be plotted as independent
139 and dependant variables."
141 (let* ((ind (- (plist-get params
:ind
) 1))
142 (deps (if (plist-member params
:deps
)
143 (mapcar (lambda (val) (- val
1)) (plist-get params
:deps
))
145 (dotimes (col (length (first table
)))
146 (setf collector
(cons col collector
)))
148 row-vals
(counter 0))
149 (when (>= ind
0) ;; collect values of ind col
150 (setf row-vals
(mapcar (lambda (row) (setf counter
(+ 1 counter
))
151 (cons counter
(nth ind row
))) table
)))
152 (when (or deps
(>= ind
0)) ;; remove non-plotting columns
153 (setf deps
(delq ind deps
))
154 (setf table
(mapcar (lambda (row)
155 (dotimes (col (length row
))
156 (unless (memq col deps
)
157 (setf (nth col row
) nil
)))
160 ;; write table to gnuplot grid datafile format
161 (with-temp-file data-file
162 (let ((num-rows (length table
)) (num-cols (length (first table
)))
163 front-edge back-edge
)
164 (flet ((gnuplot-row (col row value
)
165 (setf col
(+ 1 col
)) (setf row
(+ 1 row
))
166 (format "%f %f %f\n%f %f %f\n"
167 col
(- row
0.5) value
;; lower edge
168 col
(+ row
0.5) value
))) ;; upper edge
169 (dotimes (col num-cols
)
170 (dotimes (row num-rows
)
173 (gnuplot-row (- col
1) row
(string-to-number
174 (nth col
(nth row table
))))))
177 (gnuplot-row col row
(string-to-number
178 (nth col
(nth row table
)))))))
179 ;; only insert once per row
180 (insert back-edge
) (insert "\n") ;; back edge
181 (insert front-edge
) (insert "\n") ;; front edge
182 (setf back-edge
"") (setf front-edge
"")))))
185 (defun org-plot/gnuplot-script
(data-file num-cols params
&optional preface
)
186 "Write a gnuplot script to DATA-FILE respecting the options set in PARAMS.
187 NUM-COLS controls the number of columns plotted in a 2-d plot.
188 Optional argument PREFACE returns only option parameters in a
189 manner suitable for prepending to a user-specified script."
190 (let* ((type (plist-get params
:plot-type
))
191 (with (if (equal type
'grid
)
193 (plist-get params
:with
)))
194 (sets (plist-get params
:set
))
195 (lines (plist-get params
:line
))
196 (map (plist-get params
:map
))
197 (title (plist-get params
:title
))
198 (file (plist-get params
:file
))
199 (ind (plist-get params
:ind
))
200 (time-ind (plist-get params
:timeind
))
201 (timefmt (plist-get params
:timefmt
))
202 (text-ind (plist-get params
:textind
))
203 (deps (if (plist-member params
:deps
) (plist-get params
:deps
)))
204 (col-labels (plist-get params
:labels
))
205 (x-labels (plist-get params
:xlabels
))
206 (y-labels (plist-get params
:ylabels
))
207 (plot-str "'%s' using %s%d%s with %s title '%s'")
212 (script "reset") plot-lines
)
213 (flet ((add-to-script (line) (setf script
(format "%s\n%s" script line
))))
214 (when file
;; output file
215 (add-to-script (format "set term %s" (file-name-extension file
)))
216 (add-to-script (format "set output '%s'" file
)))
219 ('3d
(if map
(add-to-script "set map")))
221 (add-to-script "set pm3d map")
222 (add-to-script "set pm3d"))))
223 (when title
(add-to-script (format "set title '%s'" title
))) ;; title
224 (when lines
(mapc (lambda (el) (add-to-script el
)) lines
)) ;; line
226 (mapc (lambda (el) (add-to-script (format "set %s" el
))) sets
))
227 (when x-labels
;; x labels (xtics)
229 (format "set xtics (%s)"
230 (mapconcat (lambda (pair)
231 (format "\"%s\" %d" (cdr pair
) (car pair
)))
233 (when y-labels
;; y labels (ytics)
235 (format "set ytics (%s)"
236 (mapconcat (lambda (pair)
237 (format "\"%s\" %d" (cdr pair
) (car pair
)))
239 (when time-ind
;; timestamp index
240 (add-to-script "set xdata time")
241 (add-to-script (concat "set timefmt \""
242 (or timefmt
;; timefmt passed to gnuplot
243 "%Y-%m-%d-%H:%M:%S") "\"")))
245 (case type
;; plot command
246 ('2d
(dotimes (col num-cols
)
247 (unless (and (equal type
'2d
)
248 (or (and ind
(equal (+ 1 col
) ind
))
249 (and deps
(not (member (+ 1 col
) deps
)))))
252 (format plot-str data-file
253 (or (and (not text-ind
) ind
254 (> ind
0) (format "%d:" ind
)) "")
256 (if text-ind
(format ":xticlabel(%d)" ind
) "")
258 (or (nth col col-labels
) (format "%d" (+ 1 col
))))
261 (setq plot-lines
(list (format "'%s' matrix with %s title ''"
264 (setq plot-lines
(list (format "'%s' with %s title ''"
267 (concat plot-cmd
" " (mapconcat 'identity
(reverse plot-lines
) ",\\\n "))))
270 ;;-----------------------------------------------------------------------------
273 (defun org-plot/gnuplot
(&optional params
)
274 "Plot table using gnuplot. Gnuplot options can be specified with PARAMS.
275 If not given options will be taken from the +PLOT
276 line directly before or after the table."
279 (save-window-excursion
280 (delete-other-windows)
281 (when (get-buffer "*gnuplot*") ;; reset *gnuplot* if it already running
282 (with-current-buffer "*gnuplot*"
283 (goto-char (point-max))
284 (gnuplot-delchar-or-maybe-eof nil
)))
285 (org-plot/goto-nearest-table
)
286 ;; set default options
289 (unless (plist-member params
(car pair
))
290 (setf params
(plist-put params
(car pair
) (cdr pair
)))))
291 org-plot
/gnuplot-default-options
)
292 ;; collect table and table information
293 (let* ((data-file (make-temp-file "org-plot"))
294 (table (org-table-to-lisp))
295 (num-cols (length (if (eq (first table
) 'hline
) (second table
)
297 (while (equal 'hline
(first table
)) (setf table
(cdr table
)))
298 (when (equal (second table
) 'hline
)
299 (setf params
(plist-put params
:labels
(first table
))) ;; headers to labels
300 (setf table
(delq 'hline
(cdr table
)))) ;; clean non-data from table
302 (save-excursion (while (and (equal 0 (forward-line -
1))
304 (setf params
(org-plot/collect-options params
))))
305 ;; dump table to datafile (very different for grid)
306 (case (plist-get params
:plot-type
)
307 ('2d
(org-plot/gnuplot-to-data table data-file params
))
308 ('3d
(org-plot/gnuplot-to-data table data-file params
))
309 ('grid
(let ((y-labels (org-plot/gnuplot-to-grid-data
310 table data-file params
)))
311 (when y-labels
(plist-put params
:ylabels y-labels
)))))
312 ;; check for timestamp ind column
313 (let ((ind (- (plist-get params
:ind
) 1)))
314 (when (and (>= ind
0) (equal '2d
(plist-get params
:plot-type
)))
318 (if (string-match org-ts-regexp3 el
)
320 (mapcar (lambda (row) (nth ind row
)) table
)))) 0)
321 (plist-put params
:timeind t
)
322 ;; check for text ind column
324 (if (or (string= (plist-get params
:with
) "hist")
328 (if (string-match org-table-number-regexp el
)
330 (mapcar (lambda (row) (nth ind row
)) table
)))) 0))
331 (plist-put params
:textind t
)))))
334 (if (plist-get params
:script
) ;; user script
336 (org-plot/gnuplot-script data-file num-cols params t
))
338 (insert-file-contents (plist-get params
:script
))
339 (goto-char (point-min))
340 (while (re-search-forward "$datafile" nil t
)
341 (replace-match data-file nil nil
)))
343 (org-plot/gnuplot-script data-file num-cols params
)))
346 (gnuplot-send-buffer-to-gnuplot))
348 (bury-buffer (get-buffer "*gnuplot*"))
349 (run-with-idle-timer 0.1 nil
(lambda () (delete-file data-file
))))))
353 ;; arch-tag: 5763f7c6-0c75-416d-b070-398ee4ec0eca
354 ;;; org-plot.el ends here