From bcd45c562966a893449d2412b363f31368ee5983 Mon Sep 17 00:00:00 2001 From: TEC Date: Thu, 30 Jul 2020 18:36:11 +0800 Subject: [PATCH 10/15] org-plot.el: complete transition to softcoded type * lisp/org-plot.el (org-plot/preset-plot-types): Adapt structure to cover all type-specific logic within org-plot. (org-plot/gnuplot-script, org-plot/gnuplot): Replace type-specific logic with references to properties of the type from `org-plot/preset-plot-types'. --- lisp/org-plot.el | 242 ++++++++++++++++++++++++++++------------------- 1 file changed, 143 insertions(+), 99 deletions(-) diff --git a/lisp/org-plot.el b/lisp/org-plot.el index 1b227d698..53186bb75 100644 --- a/lisp/org-plot.el +++ b/lisp/org-plot.el @@ -290,7 +290,10 @@ If a function, it is called with the plot type as the argument." :type '(choice string function)) (defcustom org-plot/preset-plot-types - '((2d (lambda (data-file num-cols params plot-str) + '((2d :plot-cmd "plot" + :check-ind-type t + :plot-func + (lambda (_table data-file num-cols params plot-str) (let* ((type (plist-get params :plot-type)) (with (if (eq type 'grid) 'pm3d (plist-get params :with))) (ind (plist-get params :ind)) @@ -314,23 +317,60 @@ If a function, it is called with the plot type as the argument." (or (nth col col-labels) (format "%d" (1+ col)))) res))))))) - (3d (lambda (data-file num-cols params plot-str) + (3d :plot-cmd "splot" + :plot-pre (lambda (_table _data-file _num-cols params _plot-str) + (if (plist-get params :map) "set map")) + :plot-func + (lambda (_table data-file _num-cols params _plot-str) (let* ((type (plist-get params :plot-type)) (with (if (eq type 'grid) 'pm3d (plist-get params :with)))) (list (format "'%s' matrix with %s title ''" data-file with))))) - (grid (lambda (data-file num-cols params plot-str) + (grid :plot-cmd "splot" + :plot-pre (lambda (_table _data-file _num-cols params _plot-str) + (if (plist-get params :map) "set pm3d map" "set map")) + :data-dump (lambda (table data-file params _num-cols) + (let ((y-labels (org-plot/gnuplot-to-grid-data + table data-file params))) + (when y-labels (plist-put params :ylabels y-labels)))) + :plot-func + (lambda (table data-file _num-cols params _plot-str) (let* ((type (plist-get params :plot-type)) (with (if (eq type 'grid) 'pm3d (plist-get params :with)))) - (list (format "'%s' with %s title ''" - data-file with))))) - (radar (lambda (data-file num-cols params plot-str) + (list (format "'%s' with %s title ''" + data-file with))))) + (radar :plot-func + (lambda (table _data-file _num-cols params plot-str) (list (org--plot/radar table params))))) - "List of plot presets with the type name as the car, and a function -which yeilds plot-lines (a list of strings) as the cdr. -The parameters of `org-plot/gnuplot-script' and PLOT-STR are passed to -that function. i.e. it is called with the following arguments: - DATA-FILE NUM-COLS PARAMS PLOT-STR" + "List of plists describing the avalible plot types. +The car is the type name, and the property :plot-func must be set. +The value of :plot-func is a lambda which yields plot-lines +(a list of strings) as the cdr. + +All lambda functions have the parameters of `org-plot/gnuplot-script' and PLOT-STR passed to them. +i.e. they are called with the following signature: (TABLE DATA-FILE NUM-COLS PARAMS PLOT-STR) + +Potentially useful parameters in PARAMS include: + :set :line :map :title :file :ind :timeind :timefmt :textind + :deps :labels :xlabels :ylabels :xmin :xmax :ymin :ymax :ticks + +In addition to :plot-func, the following optional properties may be set. + +- :plot-cmd - A gnuplot command appended to each plot-line. + Accepts string or nil. Default value: nil. + +- :check-ind-type - Whether the types of ind values should be checked. + Accepts boolean. + +- :plot-str - the formula string passed to :plot-func as PLOT-STR + Accepts string. Default value: \"'%s' using %s%d%s with %s title '%s'\" + +- :data-dump - Function to dump the table to a datafile for ease of use. + Accepts lambda function. Default lambda body: (org-plot/gnuplot-to-data table data-file params) + +- :plot-pre - Gnuplot code to be inserted early into the script, just after term and output have been set. + Accepts string, nil, or lambda function which returns string or nil. Defaults to nil. +" :group 'org-plot :type '(alist :value-type (symbol group))) @@ -476,89 +516,90 @@ If a function, it is called with the plot type as the argument." :group 'org-plot :type '(choice string function)) -(defun org-plot/gnuplot-script (data-file num-cols params &optional preface) +(defun org-plot/gnuplot-script (table data-file num-cols params &optional preface) "Write a gnuplot script to DATA-FILE respecting the options set in PARAMS. NUM-COLS controls the number of columns plotted in a 2-d plot. Optional argument PREFACE returns only option parameters in a manner suitable for prepending to a user-specified script." - (let* ((type (plist-get params :plot-type)) - (with (if (eq type 'grid) 'pm3d (plist-get params :with))) - (sets (plist-get params :set)) - (lines (plist-get params :line)) - (map (plist-get params :map)) - (title (plist-get params :title)) - (file (plist-get params :file)) - (ind (plist-get params :ind)) - (time-ind (plist-get params :timeind)) - (timefmt (plist-get params :timefmt)) - (text-ind (plist-get params :textind)) - (deps (if (plist-member params :deps) (plist-get params :deps))) - (col-labels (plist-get params :labels)) - (x-labels (plist-get params :xlabels)) - (y-labels (plist-get params :ylabels)) - (plot-str "'%s' using %s%d%s with %s title '%s'") - (plot-cmd (pcase type - (`2d "plot") - (`3d "splot") - (`grid "splot"))) - (script "reset") - ;; ats = add-to-script - (ats (lambda (line) (setf script (concat script "\n" line)))) - plot-lines) - - - ;; handle output file, background, and size - (funcall ats (format "set term %s %s" - (if file (file-name-extension file) "GNUTERM") - (if (stringp org-plot/gnuplot-term-extra) - org-plot/gnuplot-term-extra - (org-plot/gnuplot-term-extra type)))) - (when file ; output file - (funcall ats (format "set output '%s'" file))) - - (funcall ats - (if (stringp org-plot/gnuplot-script-preamble) - org-plot/gnuplot-script-preamble - (org-plot/gnuplot-script-preamble type))) - - (pcase type ; type - (`2d ()) - (`3d (when map (funcall ats "set map"))) - (`grid (funcall ats (if map "set pm3d map" "set pm3d")))) - (when title (funcall ats (format "set title '%s'" title))) ; title - (mapc ats lines) ; line - (dolist (el sets) (funcall ats (format "set %s" el))) ; set - ;; Unless specified otherwise, values are TAB separated. - (unless (string-match-p "^set datafile separator" script) - (funcall ats "set datafile separator \"\\t\"")) - (when x-labels ; x labels (xtics) - (funcall ats - (format "set xtics (%s)" - (mapconcat (lambda (pair) - (format "\"%s\" %d" (cdr pair) (car pair))) - x-labels ", ")))) - (when y-labels ; y labels (ytics) - (funcall ats - (format "set ytics (%s)" - (mapconcat (lambda (pair) - (format "\"%s\" %d" (cdr pair) (car pair))) - y-labels ", ")))) - (when time-ind ; timestamp index - (funcall ats "set xdata time") - (funcall ats (concat "set timefmt \"" - (or timefmt ; timefmt passed to gnuplot - "%Y-%m-%d-%H:%M:%S") "\""))) - (unless preface - (let ((type-func (cadr (assoc type org-plot/preset-plot-types)))) - (when type-func - (setq plot-lines - (funcall type-func data-file num-cols params plot-str)))) + (let* ((type-name (plist-get params :plot-type)) + (type (cdr (assoc type-name org-plot/preset-plot-types)))) + (unless type + (user-error "Org-plot type `%s' is undefined." type-name)) + (let* ((sets (plist-get params :set)) + (lines (plist-get params :line)) + (map (plist-get params :map)) + (title (plist-get params :title)) + (file (plist-get params :file)) + (ind (plist-get params :ind)) + (time-ind (plist-get params :timeind)) + (timefmt (plist-get params :timefmt)) + (text-ind (plist-get params :textind)) + (deps (if (plist-member params :deps) (plist-get params :deps))) + (col-labels (plist-get params :labels)) + (x-labels (plist-get params :xlabels)) + (y-labels (plist-get params :ylabels)) + (plot-str (or (plist-get type :plot-str) + "'%s' using %s%d%s with %s title '%s'")) + (plot-cmd (plist-get type :plot-cmd)) + (plot-pre (plist-get type :plot-pre)) + (script "reset") + ;; ats = add-to-script + (ats (lambda (line) (when line (setf script (concat script "\n" line))))) + plot-lines) + + + ;; handle output file, background, and size + (funcall ats (format "set term %s %s" + (if file (file-name-extension file) "GNUTERM") + (if (stringp org-plot/gnuplot-term-extra) + org-plot/gnuplot-term-extra + (funcall org-plot/gnuplot-term-extra type)))) + (when file ; output file + (funcall ats (format "set output '%s'" file))) + + (when plot-pre + (funcall ats (funcall plot-pre table data-file num-cols params plot-str))) (funcall ats - (concat plot-cmd " " (mapconcat #'identity - (reverse plot-lines) - ",\\\n ")))) - script)) + (if (stringp org-plot/gnuplot-script-preamble) + org-plot/gnuplot-script-preamble + (funcall org-plot/gnuplot-script-preamble type))) + + (when title (funcall ats (format "set title '%s'" title))) ; title + (mapc ats lines) ; line + (dolist (el sets) (funcall ats (format "set %s" el))) ; set + ;; Unless specified otherwise, values are TAB separated. + (unless (string-match-p "^set datafile separator" script) + (funcall ats "set datafile separator \"\\t\"")) + (when x-labels ; x labels (xtics) + (funcall ats + (format "set xtics (%s)" + (mapconcat (lambda (pair) + (format "\"%s\" %d" (cdr pair) (car pair))) + x-labels ", ")))) + (when y-labels ; y labels (ytics) + (funcall ats + (format "set ytics (%s)" + (mapconcat (lambda (pair) + (format "\"%s\" %d" (cdr pair) (car pair))) + y-labels ", ")))) + (when time-ind ; timestamp index + (funcall ats "set xdata time") + (funcall ats (concat "set timefmt \"" + (or timefmt ; timefmt passed to gnuplot + "%Y-%m-%d-%H:%M:%S") "\""))) + (unless preface + (let ((type-func (plist-get type :plot-func))) + (when type-func + (setq plot-lines + (funcall type-func table data-file num-cols params plot-str)))) + (funcall ats + (concat plot-cmd + (when plot-cmd " ") + (mapconcat #'identity + (reverse plot-lines) + ",\\\n ")))) + script))) ;;----------------------------------------------------------------------------- ;; facade functions @@ -598,7 +639,13 @@ line directly before or after the table." (push 'hline (cdr tbl)))) tbl)) (num-cols (length (if (eq (nth 0 table) 'hline) (nth 1 table) - (nth 0 table))))) + (nth 0 table)))) + (type (assoc (plist-get params :plot-type) + org-plot/preset-plot-types))) + + (unless type + (user-error "Org-plot type `%s' is undefined." type-name)) + (run-with-idle-timer 0.1 nil #'delete-file data-file) (when (eq (cadr table) 'hline) (setf params @@ -608,15 +655,12 @@ line directly before or after the table." (save-excursion (while (and (equal 0 (forward-line -1)) (looking-at "[[:space:]]*#\\+")) (setf params (org-plot/collect-options params)))) - ;; Dump table to datafile (very different for grid). - (pcase (plist-get params :plot-type) - (`2d (org-plot/gnuplot-to-data table data-file params)) - (`3d (org-plot/gnuplot-to-data table data-file params)) - (`grid (let ((y-labels (org-plot/gnuplot-to-grid-data - table data-file params))) - (when y-labels (plist-put params :ylabels y-labels))))) + ;; Dump table to datafile + (if-let ((dump-func (plist-get type :data-dump))) + (funcall dump-func table data-file num-cols params) + (org-plot/gnuplot-to-data table data-file params)) ;; Check type of ind column (timestamp? text?) - (when (eq `2d (plist-get params :plot-type)) + (when (plist-get params :check-ind-type) (let* ((ind (1- (plist-get params :ind))) (ind-column (mapcar (lambda (row) (nth ind row)) table))) (cond ((< ind 0) nil) ; ind is implicit @@ -633,13 +677,13 @@ line directly before or after the table." (with-temp-buffer (if (plist-get params :script) ; user script (progn (insert - (org-plot/gnuplot-script data-file num-cols params t)) + (org-plot/gnuplot-script table data-file num-cols params t)) (insert "\n") (insert-file-contents (plist-get params :script)) (goto-char (point-min)) (while (re-search-forward "\\$datafile" nil t) (replace-match data-file nil nil))) - (insert (org-plot/gnuplot-script data-file num-cols params))) + (insert (org-plot/gnuplot-script table data-file num-cols params))) ;; Graph table. (gnuplot-mode) (gnuplot-send-buffer-to-gnuplot)) -- 2.28.0