changelog shortlog graph tags branches changeset file revisions annotate raw help

Mercurial > infra > home / .emacs.d/lib/eplot.el

revision 97: f61dc77440df
     1.1--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2+++ b/.emacs.d/lib/eplot.el	Sun Sep 08 20:46:10 2024 -0400
     1.3@@ -0,0 +1,3424 @@
     1.4+;;; eplot.el --- Manage and Edit Wordpress Posts -*- lexical-binding: t -*-
     1.5+
     1.6+;; Copyright (C) 2024 Free Software Foundation, Inc.
     1.7+
     1.8+;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
     1.9+;; Keywords: charts
    1.10+;; Package: eplot
    1.11+;; Version: 1.0
    1.12+;; Package-Requires: ((emacs "29.0.59") (pcsv "0.0"))
    1.13+
    1.14+;; eplot is free software; you can redistribute it and/or modify it
    1.15+;; under the terms of the GNU General Public License as published by
    1.16+;; the Free Software Foundation; either version 2, or (at your option)
    1.17+;; any later version.
    1.18+
    1.19+;;; Commentary:
    1.20+
    1.21+;; The main entry point is `M-x eplot' in a buffer with time series
    1.22+;; data.
    1.23+;;
    1.24+;; If installing manually, put something like the following in your
    1.25+;; Emacs init file (but adjust the path to where you've put eplot):
    1.26+;; 
    1.27+;; (push "~/src/eplot/" load-path)
    1.28+;; (autoload 'eplot "eplot" nil t)
    1.29+;; (autoload 'eplot-mode "eplot" nil t)
    1.30+;; (unless (assoc "\\.plt" auto-mode-alist)
    1.31+;;   (setq auto-mode-alist (cons '("\\.plt" . eplot-mode) auto-mode-alist)))
    1.32+
    1.33+;; This requires the pcsv package to parse CSV files.
    1.34+
    1.35+;;; Code:
    1.36+
    1.37+(require 'svg)
    1.38+(require 'cl-lib)
    1.39+(require 'face-remap)
    1.40+(require 'eieio)
    1.41+(require 'iso8601)
    1.42+(require 'transient)
    1.43+
    1.44+(defvar eplot--user-defaults nil)
    1.45+(defvar eplot--chart-headers nil)
    1.46+(defvar eplot--plot-headers nil)
    1.47+(defvar eplot--transient-settings nil)
    1.48+
    1.49+
    1.50+(defvar eplot--colors
    1.51+  '("aliceblue" "antiquewhite" "aqua" "aquamarine" "azure" "beige" "bisque"
    1.52+    "black" "blanchedalmond" "blue" "blueviolet" "brown" "burlywood"
    1.53+    "cadetblue" "chartreuse" "chocolate" "coral" "cornflowerblue" "cornsilk"
    1.54+    "crimson" "cyan" "darkblue" "darkcyan" "darkgoldenrod" "darkgray"
    1.55+    "darkgreen" "darkgrey" "darkkhaki" "darkmagenta" "darkolivegreen"
    1.56+    "darkorange" "darkorchid" "darkred" "darksalmon" "darkseagreen"
    1.57+    "darkslateblue" "darkslategray" "darkslategrey" "darkturquoise"
    1.58+    "darkviolet" "deeppink" "deepskyblue" "dimgray" "dimgrey" "dodgerblue"
    1.59+    "firebrick" "floralwhite" "forestgreen" "fuchsia" "gainsboro" "ghostwhite"
    1.60+    "gold" "goldenrod" "gray" "green" "greenyellow" "grey" "honeydew" "hotpink"
    1.61+    "indianred" "indigo" "ivory" "khaki" "lavender" "lavenderblush" "lawngreen"
    1.62+    "lemonchiffon" "lightblue" "lightcoral" "lightcyan" "lightgoldenrodyellow"
    1.63+    "lightgray" "lightgreen" "lightgrey" "lightpink" "lightsalmon"
    1.64+    "lightseagreen" "lightskyblue" "lightslategray" "lightslategrey"
    1.65+    "lightsteelblue" "lightyellow" "lime" "limegreen" "linen" "magenta"
    1.66+    "maroon" "mediumaquamarine" "mediumblue" "mediumorchid" "mediumpurple"
    1.67+    "mediumseagreen" "mediumslateblue" "mediumspringgreen" "mediumturquoise"
    1.68+    "mediumvioletred" "midnightblue" "mintcream" "mistyrose" "moccasin"
    1.69+    "navajowhite" "navy" "oldlace" "olive" "olivedrab" "orange" "orangered"
    1.70+    "orchid" "palegoldenrod" "palegreen" "paleturquoise" "palevioletred"
    1.71+    "papayawhip" "peachpuff" "peru" "pink" "plum" "powderblue" "purple" "red"
    1.72+    "rosybrown" "royalblue" "saddlebrown" "salmon" "sandybrown" "seagreen"
    1.73+    "seashell" "sienna" "silver" "skyblue" "slateblue" "slategray" "slategrey"
    1.74+    "snow" "springgreen" "steelblue" "tan" "teal" "thistle" "tomato"
    1.75+    "turquoise" "violet" "wheat" "white" "whitesmoke" "yellow" "yellowgreen"))
    1.76+
    1.77+(defun eplot-set (header value)
    1.78+  "Set the default value of HEADER to VALUE.
    1.79+To get a list of all possible HEADERs, use the `M-x
    1.80+eplot-list-chart-headers' command.
    1.81+
    1.82+Also see `eplot-reset'."
    1.83+  (let ((elem (or (assq header eplot--chart-headers)
    1.84+		  (assq header eplot--plot-headers))))
    1.85+    (unless elem
    1.86+      (error "No such header type: %s" header))
    1.87+    (eplot--add-default header value)))
    1.88+
    1.89+(defun eplot--add-default (header value)
    1.90+  ;; We want to preserve the order defaults have been added, so that
    1.91+  ;; we can apply them in the same order.  This makes a difference
    1.92+  ;; when we're dealing with specs that have inheritence.
    1.93+  (setq eplot--user-defaults (delq (assq header eplot--user-defaults)
    1.94+				   eplot--user-defaults))
    1.95+  (setq eplot--user-defaults (list (cons header value))))
    1.96+
    1.97+(defun eplot-reset (&optional header)
    1.98+  "Reset HEADER to defaults.
    1.99+If HEADER is nil or not present, reset everything to defaults."
   1.100+  (if header
   1.101+      (setq eplot--user-defaults (delq (assq header eplot--user-defaults)
   1.102+				       eplot--user-defaults))
   1.103+    (setq eplot--user-defaults nil)))
   1.104+
   1.105+(unless (assoc "\\.plt" auto-mode-alist)
   1.106+  (setq auto-mode-alist (cons '("\\.plt" . eplot-mode) auto-mode-alist)))
   1.107+
   1.108+;;; eplot modes.
   1.109+
   1.110+(defvar-keymap eplot-mode-map
   1.111+  "C-c C-c" #'eplot-update-view-buffer
   1.112+  "C-c C-p" #'eplot-switch-view-buffer
   1.113+  "C-c C-e" #'eplot-list-chart-headers
   1.114+  "C-c C-v" #'eplot-customize
   1.115+  "C-c C-l" #'eplot-create-controls
   1.116+  "TAB" #'eplot-complete)
   1.117+
   1.118+;; # is working overtime in the syntax here:
   1.119+;;  It can be a color like Color: #e0e0e0, and
   1.120+;;  it can be a setting like 33 # Label: Apples,
   1.121+;;  when it starts a line it's a comment.
   1.122+(defvar eplot-font-lock-keywords
   1.123+  `(("^[ \t\n]*#.*" . font-lock-comment-face)
   1.124+    ("^[^ :\n]+:" . font-lock-keyword-face)
   1.125+    ("#[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]\\([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]\\)?" . font-lock-variable-name-face)
   1.126+    ("#.*" . font-lock-builtin-face)))
   1.127+
   1.128+(define-derived-mode eplot-mode text-mode "eplot"
   1.129+  "Major mode for editing charts.
   1.130+Use the \\[eplot-list-chart-headers] command to get a list of all
   1.131+possible chart headers."
   1.132+  (setq-local completion-at-point-functions
   1.133+	      (cons 'eplot--complete-header completion-at-point-functions))
   1.134+  (setq-local font-lock-defaults
   1.135+	      '(eplot-font-lock-keywords nil nil nil)))
   1.136+
   1.137+(defun eplot-complete ()
   1.138+  "Complete headers."
   1.139+  (interactive)
   1.140+  (cond
   1.141+   ((let ((completion-fail-discreetly t))
   1.142+      (completion-at-point))
   1.143+    ;; Completion was performed; nothing else to do.
   1.144+    nil)
   1.145+   (t (indent-relative))))
   1.146+
   1.147+(defun eplot--complete-header ()
   1.148+  (or
   1.149+   ;; Complete headers names.
   1.150+   (and (or (looking-at ".*:")
   1.151+	    (and (looking-at "[ \t]*$")
   1.152+		 (save-excursion
   1.153+		   (beginning-of-line)
   1.154+		   (not (looking-at "\\(.+\\):")))))
   1.155+	(lambda ()
   1.156+	  (let ((headers (mapcar
   1.157+			  (lambda (h)
   1.158+			    (if (looking-at ".*:")
   1.159+				(capitalize (symbol-name (car h)))
   1.160+			      (concat (capitalize (symbol-name (car h))) ": ")))
   1.161+			  (save-excursion
   1.162+			    ;; If we're after the headers, then we want
   1.163+			    ;; to complete over the plot headers.  Otherwise,
   1.164+			    ;; complete over the chart headers.
   1.165+			    (if (and (not (bobp))
   1.166+				     (progn
   1.167+				       (forward-line -1)
   1.168+				       (re-search-backward "^[ \t]*$" nil t)))
   1.169+				eplot--plot-headers
   1.170+			      eplot--chart-headers))))
   1.171+		(completion-ignore-case t))
   1.172+	    (completion-in-region (pos-bol) (line-end-position) headers)
   1.173+	    'completion-attempted)))
   1.174+   ;; Complete header values.
   1.175+   (let ((hname nil))
   1.176+     (and (save-excursion
   1.177+	    (and (looking-at "[ \t]*$")
   1.178+		 (progn
   1.179+		   (beginning-of-line)
   1.180+		   (and (looking-at "\\(.+\\):")
   1.181+			(setq hname (intern (downcase (match-string 1)))))))
   1.182+	    (lambda ()
   1.183+	      (let ((valid (plist-get
   1.184+			    (cdr (assq hname (append eplot--plot-headers
   1.185+						     eplot--chart-headers)))
   1.186+			    :valid))
   1.187+		    (completion-ignore-case t))
   1.188+		(completion-in-region
   1.189+		 (save-excursion
   1.190+		   (search-backward ":" (pos-bol) t)
   1.191+		   (skip-chars-forward ": \t")
   1.192+		   (point))
   1.193+		 (line-end-position)
   1.194+		 (mapcar #'symbol-name valid))
   1.195+		'completion-attempted)))))))
   1.196+
   1.197+(define-minor-mode eplot-minor-mode
   1.198+  "Minor mode to issue commands from an eplot data buffer."
   1.199+  :lighter " eplot")
   1.200+
   1.201+(defvar-keymap eplot-minor-mode-map
   1.202+  "H-l" #'eplot-eval-and-update)
   1.203+
   1.204+(defvar-keymap eplot-view-mode-map
   1.205+  "s" #'eplot-view-write-file
   1.206+  "w" #'eplot-view-write-scaled-file
   1.207+  "c" #'eplot-view-customize
   1.208+  "l" #'eplot-create-controls)
   1.209+
   1.210+(define-derived-mode eplot-view-mode special-mode "eplot view"
   1.211+  "Major mode for displaying eplots."
   1.212+  (setq-local revert-buffer-function #'eplot-update
   1.213+	      cursor-type nil))
   1.214+
   1.215+(defun eplot-view-write-file (file &optional width)
   1.216+  "Write the current chart to a file.
   1.217+If you type in a file name that ends with something else than \"svg\",
   1.218+ImageMagick \"convert\" will be used to convert the image first.
   1.219+
   1.220+If writing to a PNG file, \"rsvg-conver\" will be used instead if
   1.221+it exists as this usually gives better results."
   1.222+  (interactive "FWrite to file name: ")
   1.223+  (when (and (file-exists-p file)
   1.224+	     (not (yes-or-no-p "File exists, overwrite? ")))
   1.225+    (error "Not overwriting the file"))
   1.226+  (save-excursion
   1.227+    (goto-char (point-min))
   1.228+    (let ((match
   1.229+	   (text-property-search-forward 'display nil
   1.230+					 (lambda (_ e)
   1.231+					   (and (consp e)
   1.232+						(eq (car e) 'image))))))
   1.233+      (unless match
   1.234+	(error "Can't find an image in the current buffer"))
   1.235+      (let ((svg (plist-get (cdr (prop-match-value match)) :data))
   1.236+	    (tmp " *eplot convert*")
   1.237+	    (executable (if width "rsvg-convert" "convert"))
   1.238+	    sfile ofile)
   1.239+	(unless svg
   1.240+	  (error "Invalid image in the current buffer"))
   1.241+	(with-temp-buffer
   1.242+	  (set-buffer-multibyte nil)
   1.243+	  (svg-print svg)
   1.244+	  (if (string-match-p "\\.svg\\'" file)
   1.245+	      (write-region (point-min) (point-max) file)
   1.246+	    (if (and (string-match-p "\\.png\\'" file)
   1.247+		     (executable-find "rsvg-convert"))
   1.248+		(setq executable "rsvg-convert")
   1.249+	      (unless (executable-find executable)
   1.250+		(error "%s isn't installed; can only save svg files"
   1.251+		       executable)))
   1.252+	    (when (and (equal executable "rsvg-convert")
   1.253+		       (not (string-match-p "\\.png\\'" file))
   1.254+		       (not (executable-find "convert")))
   1.255+	      (error "Can only write PNG files when scaling because \"convert\" isn't installed"))
   1.256+	    (unwind-protect
   1.257+		(progn
   1.258+		  (setq sfile (make-temp-file "eplot" nil ".svg")
   1.259+			ofile (make-temp-file "eplot" nil ".png"))
   1.260+		  (write-region (point-min) (point-max) sfile nil 'silent)
   1.261+		  ;; We don't use `call-process-region', because
   1.262+		  ;; convert doesn't seem to like that?
   1.263+		  (let ((code (if (equal executable "rsvg-convert")
   1.264+				  (apply
   1.265+				   #'call-process
   1.266+				   executable nil (get-buffer-create tmp) nil
   1.267+				   `(,(format "--output=%s"
   1.268+					      (expand-file-name ofile))
   1.269+				     ,@(and width
   1.270+					    `(,(format "--width=%d" width)
   1.271+					      "--keep-aspect-ratio"))
   1.272+				     ,sfile))
   1.273+				(call-process
   1.274+				 executable nil (get-buffer-create tmp) nil
   1.275+				 sfile file))))
   1.276+		    (eplot--view-error code tmp)
   1.277+		    (when (file-exists-p ofile)
   1.278+		      (if (string-match-p "\\.png\\'" file)
   1.279+			  (rename-file ofile file)
   1.280+			(let ((code (call-process "convert" nil tmp nil
   1.281+						  ofile file)))
   1.282+			  (eplot--view-error code tmp))))
   1.283+		    (message "Wrote %s" file)))
   1.284+	      ;; Clean-up.
   1.285+	      (when (get-buffer tmp)
   1.286+		(kill-buffer tmp))
   1.287+	      (when (file-exists-p sfile)
   1.288+		(delete-file sfile))
   1.289+	      (when (file-exists-p ofile)
   1.290+		(delete-file sfile)))))))))
   1.291+
   1.292+(defun eplot--view-error (code tmp)
   1.293+  (unless (zerop code)
   1.294+    (error "Error code %d: %s"
   1.295+	   code
   1.296+	   (with-current-buffer tmp
   1.297+	     (while (search-forward "[ \t\n]+" nil t)
   1.298+	       (replace-match " "))
   1.299+	     (string-trim (buffer-string))))))
   1.300+
   1.301+(defun eplot-view-write-scaled-file (width file)
   1.302+  "Write the current chart to a rescaled to a file.
   1.303+The rescaling is done by \"rsvg-convert\", which has to be
   1.304+installed.  Rescaling is done when rendering, so this should give
   1.305+you a clear, non-blurry version of the chart at any size."
   1.306+  (interactive "nWidth: \nFWrite to file: ")
   1.307+  (eplot-view-write-file file width))
   1.308+
   1.309+(defun eplot-view-customize ()
   1.310+  "Customize the settings for the chart in the current buffer."
   1.311+  (interactive)
   1.312+  (with-suppressed-warnings ((interactive-only eplot-customize))
   1.313+    (eplot-customize)))
   1.314+
   1.315+(defvar eplot--data-buffer nil)
   1.316+(defvar eplot--current-chart nil)
   1.317+
   1.318+(defun eplot ()
   1.319+  "Plot the data in the current buffer."
   1.320+  (interactive)
   1.321+  (eplot-update-view-buffer))
   1.322+
   1.323+(defun eplot-with-headers (header-file)
   1.324+  "Plot the data in the current buffer using headers from a file."
   1.325+  (interactive "fHeader file: ")
   1.326+  (eplot-update-view-buffer
   1.327+   (with-temp-buffer
   1.328+     (insert-file-contents header-file)
   1.329+     (eplot--parse-headers))))
   1.330+
   1.331+(defun eplot-switch-view-buffer ()
   1.332+  "Switch to the eplot view buffer and render the chart."
   1.333+  (interactive)
   1.334+  (eplot-update-view-buffer nil t))
   1.335+
   1.336+(defun eplot-update-view-buffer (&optional headers switch)
   1.337+  "Update the eplot view buffer based on the current data buffer."
   1.338+  (interactive)
   1.339+  ;; This is mainly useful during implementation.
   1.340+  (if (and (eq major-mode 'emacs-lisp-mode)
   1.341+	   (get-buffer-window "*eplot*" t))
   1.342+      (with-current-buffer "*eplot*"
   1.343+	(eplot-update)
   1.344+	(when-let ((win (get-buffer-window "*eplot*" t)))
   1.345+	  (set-window-point win (point-min))))
   1.346+    ;; Normal case.
   1.347+    (let* ((eplot--user-defaults (eplot--settings-table))
   1.348+	   (data (eplot--parse-buffer))
   1.349+	   (data-buffer (current-buffer))
   1.350+	   (window (selected-window)))
   1.351+      (unless data
   1.352+	(user-error "No data in the current buffer"))
   1.353+      (setq data (eplot--inject-headers data headers))
   1.354+      (if (get-buffer-window "*eplot*" t)
   1.355+	  (set-buffer "*eplot*")
   1.356+	(if switch
   1.357+	    (pop-to-buffer-same-window "*eplot*")
   1.358+	  (pop-to-buffer "*eplot*")))
   1.359+      (let ((inhibit-read-only t))
   1.360+	(erase-buffer)
   1.361+	(unless (eq major-mode 'eplot-view-mode)
   1.362+	  (eplot-view-mode))
   1.363+	(setq-local eplot--data-buffer data-buffer)
   1.364+	(let ((chart (eplot--render data)))
   1.365+	  (with-current-buffer data-buffer
   1.366+	    (setq-local eplot--current-chart chart)))
   1.367+	(insert "\n")
   1.368+	(when-let ((win (get-buffer-window "*eplot*" t)))
   1.369+	  (set-window-point win (point-min))))
   1.370+      (select-window window))))
   1.371+
   1.372+(defun eplot--settings-table ()
   1.373+  (if (not eplot--transient-settings)
   1.374+      eplot--user-defaults
   1.375+    (append eplot--user-defaults eplot--transient-settings)))
   1.376+
   1.377+(defun eplot--inject-headers (data headers)
   1.378+  ;; It's OK not to separate the plot headers from the chart
   1.379+  ;; headers.  Collect them here, if any.
   1.380+  (when-let ((plot-headers
   1.381+	      (cl-loop for elem in (mapcar #'car eplot--plot-headers)
   1.382+		       for value = (eplot--vs elem headers)
   1.383+		       when value
   1.384+		       collect (progn
   1.385+				 ;; Remove these headers from the data
   1.386+				 ;; headers so that we don't get errors
   1.387+				 ;; on undefined headers.
   1.388+				 (setq headers (delq (assq elem headers)
   1.389+						     headers))
   1.390+				 (cons elem value)))))
   1.391+    (dolist (plot (cdr (assq :plots data)))
   1.392+      (let ((headers (assq :headers plot)))
   1.393+	(if headers
   1.394+	    (nconc headers plot-headers)
   1.395+	  (nconc plot (list (list :headers plot-headers)))))))
   1.396+  (append data headers))
   1.397+
   1.398+(defun eplot-eval-and-update ()
   1.399+  "Helper command when developing."
   1.400+  (interactive nil emacs-lisp-mode)
   1.401+  (save-some-buffers t)
   1.402+  (elisp-eval-region-or-buffer)
   1.403+  (eval-defun nil)
   1.404+  (eplot-update-view-buffer))
   1.405+
   1.406+;;; Parsing buffers.
   1.407+
   1.408+(defun eplot-update (&rest _ignore)
   1.409+  "Update the plot in the current buffer."
   1.410+  (interactive)
   1.411+  (unless eplot--data-buffer
   1.412+    (user-error "No data buffer associated with this eplot view buffer"))
   1.413+  (let ((data (with-current-buffer eplot--data-buffer
   1.414+		(eplot--parse-buffer)))
   1.415+	(eplot--user-defaults (with-current-buffer eplot--data-buffer
   1.416+				(eplot--settings-table)))
   1.417+	(inhibit-read-only t))
   1.418+    (erase-buffer)
   1.419+    (let ((chart (eplot--render data)))
   1.420+      (with-current-buffer eplot--data-buffer
   1.421+	(setq-local eplot--current-chart chart)))
   1.422+    (insert "\n\n")))
   1.423+
   1.424+(defun eplot--parse-buffer ()
   1.425+  (if (eq major-mode 'org-mode)
   1.426+      (eplot--parse-org-buffer)
   1.427+    (eplot--parse-eplot-buffer)))
   1.428+
   1.429+(defun eplot--parse-eplot-buffer ()
   1.430+  (if (eplot--csv-buffer-p)
   1.431+      (eplot--parse-csv-buffer)
   1.432+    (let ((buf (current-buffer)))
   1.433+      (with-temp-buffer
   1.434+	(insert-buffer-substring buf)
   1.435+	;; Remove comments first.
   1.436+	(goto-char (point-min))
   1.437+	(while (re-search-forward "^[ \t]*#" nil t)
   1.438+	  (delete-line))
   1.439+	(goto-char (point-min))
   1.440+	;; First headers.
   1.441+	(let* ((data (eplot--parse-headers))
   1.442+	       (plot-headers
   1.443+		;; It's OK not to separate the plot headers from the chart
   1.444+		;; headers.  Collect them here, if any.
   1.445+		(cl-loop for elem in (mapcar #'car eplot--plot-headers)
   1.446+			 for value = (eplot--vs elem data)
   1.447+			 when value
   1.448+			 collect (progn
   1.449+				   ;; Remove these headers from the data
   1.450+				   ;; headers so that we don't get errors
   1.451+				   ;; on undefined headers.
   1.452+				   (setq data (delq (assq elem data) data))
   1.453+				   (cons elem value))))
   1.454+	       plots)
   1.455+	  ;; Then the values.
   1.456+	  (while-let ((plot (eplot--parse-values nil plot-headers)))
   1.457+	    (setq plot-headers nil)
   1.458+	    (push plot plots))
   1.459+	  (when plots
   1.460+	    (push (cons :plots (nreverse plots)) data))
   1.461+	  data)))))
   1.462+
   1.463+(defun eplot--parse-headers ()
   1.464+  (let ((data nil)
   1.465+	type value)
   1.466+    (while (looking-at "\\([^\n\t :]+\\):\\(.*\\)")
   1.467+      (setq type (intern (downcase (match-string 1)))
   1.468+	    value (substring-no-properties (string-trim (match-string 2))))
   1.469+      (forward-line 1)
   1.470+      ;; Get continuation lines.
   1.471+      (while (looking-at "[ \t]+\\(.*\\)")
   1.472+	(setq value (concat value " " (string-trim (match-string 1))))
   1.473+	(forward-line 1))
   1.474+      (if (eq type 'header-file)
   1.475+	  (setq data (nconc data
   1.476+			    (with-temp-buffer
   1.477+			      (insert-file-contents value)
   1.478+			      (eplot--parse-headers))))
   1.479+	;; We don't use `push' here because we want to preserve order
   1.480+	;; also when inserting headers from other files.
   1.481+	(setq data (nconc data (list (cons type value))))))
   1.482+    data))
   1.483+
   1.484+(defun eplot--parse-values (&optional in-headers data-headers)
   1.485+  ;; Skip past separator lines.
   1.486+  (while (looking-at "[ \t]*\n")
   1.487+    (forward-line 1))
   1.488+  (let* ((values nil)
   1.489+	 ;; We may have plot-specific headers.
   1.490+	 (headers (nconc (eplot--parse-headers) data-headers))
   1.491+	 (data-format (or (eplot--vyl 'data-format headers)
   1.492+			  (eplot--vyl 'data-format in-headers)))
   1.493+	 (two-values (memq 'two-values data-format))
   1.494+	 (xy (or (memq 'year data-format)
   1.495+		 (memq 'date data-format)
   1.496+		 (memq 'time data-format)
   1.497+		 (memq 'xy data-format)))
   1.498+	 (data-column (or (eplot--vn 'data-column headers)
   1.499+			  (eplot--vn 'data-column in-headers))))
   1.500+    (if-let ((data-file (eplot--vs 'data-file headers)))
   1.501+	(with-temp-buffer
   1.502+	  (insert-file-contents data-file)
   1.503+	  (setq values (cdr (assq :values (eplot--parse-values headers)))
   1.504+		headers (delq (assq 'data headers) headers)))
   1.505+      ;; Now we come to the data.  The data is typically either just a
   1.506+      ;; number, or two numbers (in which case the first number is a
   1.507+      ;; date or a time).  Labels ans settings can be introduced with
   1.508+      ;; a # char.
   1.509+      (while (looking-at "\\([-0-9. \t]+\\)\\([ \t]*#\\(.*\\)\\)?")
   1.510+	(let ((numbers (match-string 1))
   1.511+	      (settings (eplot--parse-settings (match-string 3)))
   1.512+	      this)
   1.513+	  (setq numbers (mapcar #'string-to-number
   1.514+				(split-string (string-trim numbers))))
   1.515+	  ;; If we're reading two dimensionalish data, the first
   1.516+	  ;; number is the date/time/x.
   1.517+	  (when xy
   1.518+	    (setq this (list :x (pop numbers))))
   1.519+	  ;; Chop off all the numbers until we read the column(s)
   1.520+	  ;; we're using.
   1.521+	  (when data-column
   1.522+	    (setq numbers (nthcdr (1- data-column) numbers)))
   1.523+	  (when numbers
   1.524+	    (setq this (nconc this (list :value (pop numbers)))))
   1.525+	  (when two-values
   1.526+	    (setq this (nconc this (list :extra-value (pop numbers)))))
   1.527+	  (when settings
   1.528+	    (setq this (nconc this (list :settings settings))))
   1.529+	  (when (plist-get this :value)
   1.530+	    (push this values)))
   1.531+	(forward-line 1))
   1.532+      (setq values (nreverse values)))
   1.533+    (and values
   1.534+	 `((:headers . ,headers) (:values . ,values)))))
   1.535+
   1.536+(defun eplot--parse-settings (string)
   1.537+  (when string
   1.538+    (with-temp-buffer
   1.539+      (insert (string-trim string) "\n")
   1.540+      (goto-char (point-min))
   1.541+      (while (re-search-forward "\\(.\\)," nil t)
   1.542+	(if (equal (match-string 1) "\\")
   1.543+	    (replace-match "," t t)
   1.544+	  (delete-char -1)
   1.545+	  (insert "\n")
   1.546+	  (when (looking-at "[ \t]+")
   1.547+	    (replace-match ""))))
   1.548+      (goto-char (point-min))
   1.549+      (eplot--parse-headers))))
   1.550+
   1.551+;;; Accessing data.
   1.552+
   1.553+(defun eplot--vn (type data &optional default)
   1.554+  (if-let ((value (cdr (assq type data))))
   1.555+      (string-to-number value)
   1.556+    default))
   1.557+
   1.558+(defun eplot--vs (type data &optional default)
   1.559+  (or (cdr (assq type data)) default))
   1.560+
   1.561+(defun eplot--vy (type data &optional default)
   1.562+  (if-let ((value (cdr (assq type data))))
   1.563+      (intern (downcase value))
   1.564+    default))
   1.565+
   1.566+(defun eplot--vyl (type data &optional default)
   1.567+  (if-let ((value (cdr (assq type data))))
   1.568+      (mapcar #'intern (split-string (downcase value)))
   1.569+    default))
   1.570+
   1.571+(defmacro eplot-def (args doc-string)
   1.572+  (declare (indent defun))
   1.573+  `(eplot--def ',(nth 0 args) ',(nth 1 args) ',(nth 2 args) ',(nth 3 args)
   1.574+	       ,doc-string))
   1.575+
   1.576+(defun eplot--def (name type default valid doc)
   1.577+  (setq eplot--chart-headers (delq (assq name eplot--chart-headers)
   1.578+				   eplot--chart-headers))
   1.579+  (push (list name
   1.580+	      :type type
   1.581+	      :default default
   1.582+	      :doc doc
   1.583+	      :valid valid)
   1.584+	eplot--chart-headers))
   1.585+
   1.586+(eplot-def (width number)
   1.587+  "The width of the entire chart.")
   1.588+
   1.589+(eplot-def (height number)
   1.590+  "The height of the entire chart.")
   1.591+
   1.592+(eplot-def (format symbol normal (normal bar-chart horizontal-bar-chart))
   1.593+  "The overall format of the chart.")
   1.594+
   1.595+(eplot-def (layout symbol nil (normal compact))
   1.596+  "The general layout of the chart.")
   1.597+
   1.598+(eplot-def (mode symbol light (dark light))
   1.599+  "Dark/light mode.")
   1.600+
   1.601+(eplot-def (margin-left number 70)
   1.602+  "The left margin.")
   1.603+
   1.604+(eplot-def (margin-right number 20)
   1.605+  "The right margin.")
   1.606+
   1.607+(eplot-def (margin-top number 40)
   1.608+  "The top margin.")
   1.609+
   1.610+(eplot-def (margin-bottom number 60)
   1.611+  "The bottom margin.")
   1.612+
   1.613+(eplot-def (x-axis-title-space number 5)
   1.614+  "The space between the X axis and the label.")
   1.615+
   1.616+(eplot-def (font string "sans-serif")
   1.617+  "The font to use in titles, labels and legends.")
   1.618+
   1.619+(eplot-def (font-size number 12)
   1.620+  "The font size.")
   1.621+
   1.622+(eplot-def (font-weight symbol bold (bold normal))
   1.623+  "The font weight.")
   1.624+
   1.625+(eplot-def (label-font string (spec font))
   1.626+  "The font to use for axes labels.")
   1.627+
   1.628+(eplot-def (label-font-size number (spec font-size))
   1.629+  "The font size to use for axes labels.")
   1.630+
   1.631+(eplot-def (bar-font string (spec font))
   1.632+  "The font to use for bar chart labels.")
   1.633+
   1.634+(eplot-def (bar-font-size number (spec font-size))
   1.635+  "The font size to use for bar chart labels.")
   1.636+
   1.637+(eplot-def (bar-font-weight symbol (spec font-weight) (bold normal))
   1.638+  "The font weight to use for bar chart labels.")
   1.639+
   1.640+(eplot-def (chart-color string "black")
   1.641+  "The foreground color to use in plots, axes, legends, etc.
   1.642+This is used as the default, but can be overridden per thing.")
   1.643+
   1.644+(eplot-def (background-color string "white")
   1.645+  "The background color.
   1.646+If you want a chart with a transparent background, use the color
   1.647+\"none\".")
   1.648+
   1.649+(eplot-def (background-gradient string)
   1.650+  "Use this to get a gradient color in the background.")
   1.651+
   1.652+(eplot-def (axes-color string (spec chart-color))
   1.653+  "The color of the axes.")
   1.654+
   1.655+(eplot-def (grid-color string "#e0e0e0")
   1.656+  "The color of the grid.")
   1.657+
   1.658+(eplot-def (grid symbol xy (xy x y off))
   1.659+  "What grid axes to do.")
   1.660+
   1.661+(eplot-def (grid-opacity number)
   1.662+  "The opacity of the grid.
   1.663+This should either be nil or a value between 0 and 1, where 0 is
   1.664+fully transparent.")
   1.665+
   1.666+(eplot-def (grid-position symbol bottom (bottom top))
   1.667+  "Whether to put the grid on top or under the plot.")
   1.668+
   1.669+(eplot-def (legend symbol nil (true nil))
   1.670+  "Whether to do a legend.")
   1.671+
   1.672+(eplot-def (legend-color string (spec chart-color))
   1.673+  "The color of legends (if any).")
   1.674+
   1.675+(eplot-def (legend-border-color string (spec chart-color))
   1.676+  "The border color of legends (if any).")
   1.677+
   1.678+(eplot-def (legend-background-color string (spec background-color))
   1.679+  "The background color of legends (if any).")
   1.680+
   1.681+(eplot-def (label-color string (spec axes-color))
   1.682+  "The color of labels on the axes.")
   1.683+
   1.684+(eplot-def (surround-color string)
   1.685+  "The color between the plot area and the edges of the chart.")
   1.686+
   1.687+(eplot-def (border-color string)
   1.688+  "The color of the border of the chart, if any.")
   1.689+
   1.690+(eplot-def (border-width number)
   1.691+  "The width of the border of the chart, if any.")
   1.692+
   1.693+(eplot-def (frame-color string)
   1.694+  "The color of the frame of the plot, if any.")
   1.695+
   1.696+(eplot-def (frame-width number)
   1.697+  "The width of the frame of the plot, if any.")
   1.698+
   1.699+(eplot-def (min number)
   1.700+  "The minimum value in the chart.
   1.701+This is normally computed automatically, but can be overridden
   1.702+ with this spec.")
   1.703+
   1.704+(eplot-def (max number)
   1.705+  "The maximum value in the chart.
   1.706+This is normally computed automatically, but can be overridden
   1.707+ with this spec.")
   1.708+
   1.709+(eplot-def (title string)
   1.710+  "The title of the chart, if any.")
   1.711+
   1.712+(eplot-def (title-color string (spec chart-color))
   1.713+  "The color of the title.")
   1.714+
   1.715+(eplot-def (x-title string)
   1.716+  "The title of the X axis, if any.")
   1.717+
   1.718+(eplot-def (y-title string)
   1.719+  "The title of the X axis, if any.")
   1.720+
   1.721+(eplot-def (x-label-format string)
   1.722+  "Format string for the X labels.
   1.723+This is a `format' string.")
   1.724+
   1.725+(eplot-def (y-label-format string)
   1.726+  "Format string for the Y labels.
   1.727+This is a `format' string.")
   1.728+
   1.729+(eplot-def (x-label-orientation symbol horizontal (horizontal vertical))
   1.730+  "Orientation of the X labels.")
   1.731+
   1.732+(eplot-def (background-image-file string)
   1.733+  "Use an image as the background.")
   1.734+
   1.735+(eplot-def (background-image-opacity number 1)
   1.736+  "The opacity of the background image.")
   1.737+
   1.738+(eplot-def (background-image-cover symbol all (all plot frame))
   1.739+  "Position of the background image.
   1.740+Valid values are `all' (the entire image), `plot' (the plot area)
   1.741+and `frame' (the surrounding area).")
   1.742+
   1.743+(eplot-def (header-file string)
   1.744+  "File where the headers are.")
   1.745+
   1.746+(defvar eplot-compact-defaults
   1.747+  '((margin-left 30)
   1.748+    (margin-right 10)
   1.749+    (margin-top 20)
   1.750+    (margin-bottom 21)
   1.751+    (font-size 12)
   1.752+    (x-axis-title-space 3)))
   1.753+
   1.754+(defvar eplot-dark-defaults
   1.755+  '((chart-color "#c0c0c0")
   1.756+    (axes-color "#c0c0c0")
   1.757+    (grid-color "#404040")
   1.758+    (background-color "#101010")
   1.759+    (label-color "#c0c0c0")
   1.760+    (legend-color "#c0c0c0")
   1.761+    (title-color "#c0c0c0")))
   1.762+
   1.763+(defvar eplot-bar-chart-defaults
   1.764+  '((grid-position top)
   1.765+    (grid y)
   1.766+    (grid-opacity 0.2)
   1.767+    (min 0)))
   1.768+
   1.769+(defvar eplot-horizontal-bar-chart-defaults
   1.770+  '((grid-position top)
   1.771+    (grid-opacity 0.2)
   1.772+    (min 0)))
   1.773+
   1.774+(defclass eplot-chart ()
   1.775+  (
   1.776+   (plots :initarg :plots)
   1.777+   (data :initarg :data)
   1.778+   (xs)
   1.779+   (ys)
   1.780+   (x-values :initform nil)
   1.781+   (x-type :initform nil)
   1.782+   (x-min)
   1.783+   (x-max)
   1.784+   (x-ticks)
   1.785+   (y-ticks)
   1.786+   (y-labels)
   1.787+   (x-labels)
   1.788+   (print-format)
   1.789+   (x-tick-step)
   1.790+   (x-label-step)
   1.791+   (x-step-map :initform nil)
   1.792+   (y-tick-step)
   1.793+   (y-label-step)
   1.794+   (inhibit-compute-x-step :initform nil)
   1.795+   ;; ---- CUT HERE ----
   1.796+   (axes-color :initarg :axes-color :initform nil)
   1.797+   (background-color :initarg :background-color :initform nil)
   1.798+   (background-gradient :initarg :background-gradient :initform nil)
   1.799+   (background-image-cover :initarg :background-image-cover :initform nil)
   1.800+   (background-image-file :initarg :background-image-file :initform nil)
   1.801+   (background-image-opacity :initarg :background-image-opacity :initform nil)
   1.802+   (bar-font :initarg :bar-font :initform nil)
   1.803+   (bar-font-size :initarg :bar-font-size :initform nil)
   1.804+   (bar-font-weight :initarg :bar-font-weight :initform nil)
   1.805+   (border-color :initarg :border-color :initform nil)
   1.806+   (border-width :initarg :border-width :initform nil)
   1.807+   (chart-color :initarg :chart-color :initform nil)
   1.808+   (font :initarg :font :initform nil)
   1.809+   (font-size :initarg :font-size :initform nil)
   1.810+   (font-weight :initarg :font-weight :initform nil)
   1.811+   (format :initarg :format :initform nil)
   1.812+   (frame-color :initarg :frame-color :initform nil)
   1.813+   (frame-width :initarg :frame-width :initform nil)
   1.814+   (grid :initarg :grid :initform nil)
   1.815+   (grid-color :initarg :grid-color :initform nil)
   1.816+   (grid-opacity :initarg :grid-opacity :initform nil)
   1.817+   (grid-position :initarg :grid-position :initform nil)
   1.818+   (header-file :initarg :header-file :initform nil)
   1.819+   (height :initarg :height :initform nil)
   1.820+   (label-color :initarg :label-color :initform nil)
   1.821+   (label-font :initarg :label-font :initform nil)
   1.822+   (label-font-size :initarg :label-font-size :initform nil)
   1.823+   (layout :initarg :layout :initform nil)
   1.824+   (legend :initarg :legend :initform nil)
   1.825+   (legend-background-color :initarg :legend-background-color :initform nil)
   1.826+   (legend-border-color :initarg :legend-border-color :initform nil)
   1.827+   (legend-color :initarg :legend-color :initform nil)
   1.828+   (margin-bottom :initarg :margin-bottom :initform nil)
   1.829+   (margin-left :initarg :margin-left :initform nil)
   1.830+   (margin-right :initarg :margin-right :initform nil)
   1.831+   (margin-top :initarg :margin-top :initform nil)
   1.832+   (max :initarg :max :initform nil)
   1.833+   (min :initarg :min :initform nil)
   1.834+   (mode :initarg :mode :initform nil)
   1.835+   (surround-color :initarg :surround-color :initform nil)
   1.836+   (title :initarg :title :initform nil)
   1.837+   (title-color :initarg :title-color :initform nil)
   1.838+   (width :initarg :width :initform nil)
   1.839+   (x-axis-title-space :initarg :x-axis-title-space :initform nil)
   1.840+   (x-title :initarg :x-title :initform nil)
   1.841+   (y-title :initarg :y-title :initform nil)
   1.842+   (x-label-format :initarg :x-label-format :initform nil)
   1.843+   (x-label-orientation :initarg :x-label-orientation :initform nil)
   1.844+   (y-label-format :initarg :y-label-format :initform nil)
   1.845+   ;; ---- CUT HERE ----
   1.846+   ))
   1.847+
   1.848+;;; Parameters that are plot specific.
   1.849+
   1.850+(defmacro eplot-pdef (args doc-string)
   1.851+  (declare (indent defun))
   1.852+  `(eplot--pdef ',(nth 0 args) ',(nth 1 args) ',(nth 2 args) ',(nth 3 args)
   1.853+		,doc-string))
   1.854+
   1.855+(defun eplot--pdef (name type default valid doc)
   1.856+  (setq eplot--plot-headers (delq (assq name eplot--plot-headers)
   1.857+				   eplot--plot-headers))
   1.858+  (push (list name
   1.859+	      :type type
   1.860+	      :default default
   1.861+	      :valid valid
   1.862+	      :doc doc)
   1.863+	eplot--plot-headers))
   1.864+
   1.865+(eplot-pdef (smoothing symbol nil (moving-average nil))
   1.866+  "Smoothing algorithm to apply to the data, if any.
   1.867+Valid values are `moving-average' and, er, probably more to come.")
   1.868+
   1.869+(eplot-pdef (gradient string)
   1.870+  "Gradient to apply to the plot.
   1.871+The syntax is:
   1.872+
   1.873+  from-color to-color direction position
   1.874+
   1.875+The last two parameters are optional.
   1.876+
   1.877+direction is either `top-down' (the default), `bottom-up',
   1.878+`left-right' or `right-left').
   1.879+
   1.880+position is either `below' or `above'.
   1.881+
   1.882+to-color can be either a color name, or a string that defines
   1.883+stops and colors:
   1.884+
   1.885+   Gradient: black 25-purple-50-white-75-purple-black
   1.886+
   1.887+In that case, the second element specifies the percentage points
   1.888+of where each color ends, so the above starts with black, then at
   1.889+25% it's purple, then at 50% it's white, then it's back to purple
   1.890+again at 75%, before ending up at black at a 100% (but you don't
   1.891+have to include the 100% here -- it's understood).")
   1.892+
   1.893+(eplot-pdef (style symbol line ( line impulse point square circle cross
   1.894+				 triangle rectangle curve))
   1.895+  "Style the plot should be drawn in.
   1.896+Valid values are listed below.  Some styles take additional
   1.897+optional parameters.
   1.898+
   1.899+line
   1.900+  Straight lines between values.
   1.901+
   1.902+curve
   1.903+  Curved lines between values.
   1.904+
   1.905+impulse
   1.906+  size: width of the impulse
   1.907+
   1.908+point
   1.909+
   1.910+square
   1.911+
   1.912+circle
   1.913+  size: diameter of the circle
   1.914+  fill-color: color to fill the center
   1.915+
   1.916+cross
   1.917+  size: length of the lines in the cross
   1.918+
   1.919+triangle
   1.920+  size: length of the sides of the triangle
   1.921+  fill-color: color to fill the center
   1.922+
   1.923+rectangle
   1.924+  size: length of the sides of the rectangle
   1.925+  fill-color: color to fill the center")
   1.926+
   1.927+(eplot-pdef (fill-color string)
   1.928+  "Color to use to fill the plot styles that are closed shapes.
   1.929+I.e., circle, triangle and rectangle.")
   1.930+
   1.931+(eplot-pdef (color string (spec chart-color))
   1.932+  "Color to draw the plot.")
   1.933+
   1.934+(eplot-pdef (data-format symbol single (single date time xy))
   1.935+  "Format of the data.
   1.936+By default, eplot assumes that each line has a single data point.
   1.937+This can also be `date', `time' and `xy'.
   1.938+
   1.939+date: The first column is a date on ISO8601 format (i.e., YYYYMMDD).
   1.940+
   1.941+time: The first column is a clock (i.e., HHMMSS).
   1.942+
   1.943+xy: The first column is the X position.")
   1.944+
   1.945+(eplot-pdef (data-column number 1)
   1.946+  "Column where the data is.")
   1.947+
   1.948+(eplot-pdef (fill-border-color string)
   1.949+  "Border around the fill area when using a fill/gradient style.")
   1.950+
   1.951+(eplot-pdef (size number)
   1.952+  "Size of elements in styles that have meaningful sizes.")
   1.953+
   1.954+(eplot-pdef (size-factor number)
   1.955+  "Multiply the size of the elements by the value.")
   1.956+
   1.957+(eplot-pdef (data-file string)
   1.958+  "File where the data is.")
   1.959+
   1.960+(eplot-pdef (data-format symbol-list nil (nil two-values date time))
   1.961+  "List of symbols to describe the data format.
   1.962+Elements allowed are `two-values', `date' and `time'.")
   1.963+
   1.964+(eplot-pdef (name string)
   1.965+  "Name of the plot, which will be displayed if legends are switched on.")
   1.966+
   1.967+(eplot-pdef (legend-color string (spec chart-color))
   1.968+  "Color for the name to be displayed in the legend.")
   1.969+
   1.970+(eplot-pdef (bezier-factor number 0.1)
   1.971+  "The Bezier factor to apply to curve plots.")
   1.972+
   1.973+(defclass eplot-plot ()
   1.974+  (
   1.975+   (values :initarg :values)
   1.976+   ;; ---- CUT HERE ----
   1.977+   (bezier-factor :initarg :bezier-factor :initform nil)
   1.978+   (color :initarg :color :initform nil)
   1.979+   (data-column :initarg :data-column :initform nil)
   1.980+   (data-file :initarg :data-file :initform nil)
   1.981+   (data-format :initarg :data-format :initform nil)
   1.982+   (fill-border-color :initarg :fill-border-color :initform nil)
   1.983+   (fill-color :initarg :fill-color :initform nil)
   1.984+   (gradient :initarg :gradient :initform nil)
   1.985+   (legend-color :initarg :legend-color :initform nil)
   1.986+   (name :initarg :name :initform nil)
   1.987+   (size :initarg :size :initform nil)
   1.988+   (size-factor :initarg :size-factor :initform nil)
   1.989+   (smoothing :initarg :smoothing :initform nil)
   1.990+   (style :initarg :style :initform nil)
   1.991+   ;; ---- CUT HERE ----
   1.992+   ))
   1.993+
   1.994+(defun eplot--make-plot (data)
   1.995+  "Make an `eplot-plot' object and initialize based on DATA."
   1.996+  (let ((plot (make-instance 'eplot-plot
   1.997+			     :values (cdr (assq :values data)))))
   1.998+    ;; Get the program-defined defaults.
   1.999+    (eplot--object-defaults plot eplot--plot-headers)
  1.1000+    ;; One special case.  I don't think this hack is quite right...
  1.1001+    (when (or (eq (eplot--vs 'mode data) 'dark)
  1.1002+	      (eq (cdr (assq 'mode eplot--user-defaults)) 'dark))
  1.1003+      (setf (slot-value plot 'color) "#c0c0c0"))
  1.1004+    ;; Use the headers.
  1.1005+    (eplot--object-values plot (cdr (assq :headers data)) eplot--plot-headers)
  1.1006+    plot))
  1.1007+
  1.1008+(defun eplot--make-chart (data)
  1.1009+  "Make an `eplot-chart' object and initialize based on DATA."
  1.1010+  (let ((chart (make-instance 'eplot-chart
  1.1011+			      :plots (mapcar #'eplot--make-plot
  1.1012+					     (eplot--vs :plots data))
  1.1013+			      :data data)))
  1.1014+    ;; First get the program-defined defaults.
  1.1015+    (eplot--object-defaults chart eplot--chart-headers)
  1.1016+    ;; Then do the "meta" variables.
  1.1017+    (eplot--meta chart data 'mode 'dark eplot-dark-defaults)
  1.1018+    (eplot--meta chart data 'layout 'compact eplot-compact-defaults)
  1.1019+    (eplot--meta chart data 'format 'bar-chart eplot-bar-chart-defaults)
  1.1020+    (eplot--meta chart data 'format 'horizontal-bar-chart
  1.1021+		 eplot-horizontal-bar-chart-defaults)
  1.1022+    ;; Set defaults from user settings/transients.
  1.1023+    (cl-loop for (name . value) in eplot--user-defaults
  1.1024+	     when (assq name eplot--chart-headers)
  1.1025+	     do
  1.1026+	     (setf (slot-value chart name) value)
  1.1027+	     (eplot--set-dependent-values chart name value))
  1.1028+    ;; Finally, use the data from the chart.
  1.1029+    (eplot--object-values chart data eplot--chart-headers)
  1.1030+    ;; If not set, recompute the margins based on the font sizes (if
  1.1031+    ;; the font size has been changed from defaults).
  1.1032+    (when (or (assq 'font-size eplot--user-defaults)
  1.1033+	      (assq 'font-size data))
  1.1034+      (with-slots ( title x-title y-title
  1.1035+		    margin-top margin-bottom margin-left
  1.1036+		    font-size font font-weight)
  1.1037+	  chart
  1.1038+	(when (or title x-title y-title)
  1.1039+	  (let ((text-height
  1.1040+		 (eplot--text-height (concat title x-title y-title)
  1.1041+				     font font-size font-weight)))
  1.1042+	    (when (and title
  1.1043+		       (and (not (assq 'margin-top eplot--user-defaults))
  1.1044+			    (not (assq 'margin-top data))))
  1.1045+	      (cl-incf margin-top (* text-height 1.4)))
  1.1046+	    (when (and x-title
  1.1047+		       (and (not (assq 'margin-bottom eplot--user-defaults))
  1.1048+			    (not (assq 'margin-bottom data))))
  1.1049+	      (cl-incf margin-bottom (* text-height 1.4)))
  1.1050+	    (when (and y-title
  1.1051+		       (and (not (assq 'margin-left eplot--user-defaults))
  1.1052+			    (not (assq 'margin-left data))))
  1.1053+	      (cl-incf margin-left (* text-height 1.4)))))))
  1.1054+    chart))
  1.1055+
  1.1056+(defun eplot--meta (chart data slot value defaults)
  1.1057+  (when (or (eq (cdr (assq slot eplot--user-defaults)) value)
  1.1058+	    (eq (eplot--vy slot data) value))
  1.1059+    (eplot--set-theme chart defaults)))
  1.1060+
  1.1061+(defun eplot--object-defaults (object headers)
  1.1062+  (dolist (header headers)
  1.1063+    (when-let ((default (plist-get (cdr header) :default)))
  1.1064+      (setf (slot-value object (car header))
  1.1065+	    ;; Allow overrides via `eplot-set'.
  1.1066+	    (or (cdr (assq (car header) eplot--user-defaults))
  1.1067+		(if (and (consp default)
  1.1068+			 (eq (car default) 'spec))
  1.1069+		    ;; Chase dependencies.
  1.1070+		    (eplot--default (cadr default))
  1.1071+		  default))))))
  1.1072+
  1.1073+(defun eplot--object-values (object data headers)
  1.1074+  (cl-loop for (name . value) in data
  1.1075+	   do (unless (eq name :plots)
  1.1076+		(let ((spec (cdr (assq name headers))))
  1.1077+		  (if (not spec)
  1.1078+		      (error "%s is not a valid spec" name)
  1.1079+		    (let ((value 
  1.1080+			   (cl-case (plist-get spec :type)
  1.1081+			     (number
  1.1082+			      (string-to-number value))
  1.1083+			     (symbol
  1.1084+			      (intern (downcase value)))
  1.1085+			     (symbol-list
  1.1086+			      (mapcar #'intern (split-string (downcase value))))
  1.1087+			     (t
  1.1088+			      value))))
  1.1089+		      (setf (slot-value object name) value)
  1.1090+		      (eplot--set-dependent-values object name value)))))))
  1.1091+
  1.1092+(defun eplot--set-dependent-values (object name value)
  1.1093+  (dolist (slot (gethash name (eplot--dependecy-graph)))
  1.1094+    (setf (slot-value object slot) value)
  1.1095+    (eplot--set-dependent-values object slot value)))
  1.1096+
  1.1097+(defun eplot--set-theme (chart map)
  1.1098+  (cl-loop for (slot value) in map
  1.1099+	   do (setf (slot-value chart slot) value)))
  1.1100+
  1.1101+(defun eplot--default (slot)
  1.1102+  "Find the default value for SLOT, chasing dependencies."
  1.1103+  (let ((spec (cdr (assq slot eplot--chart-headers))))
  1.1104+    (unless spec
  1.1105+      (error "Invalid slot %s" slot))
  1.1106+    (let ((default (plist-get spec :default)))
  1.1107+      (if (and (consp default)
  1.1108+	       (eq (car default) 'spec))
  1.1109+	  (eplot--default (cadr default))
  1.1110+	(or (cdr (assq slot eplot--user-defaults)) default)))))
  1.1111+
  1.1112+(defun eplot--dependecy-graph ()
  1.1113+  (let ((table (make-hash-table)))
  1.1114+    (dolist (elem eplot--chart-headers)
  1.1115+      (let ((default (plist-get (cdr elem) :default)))
  1.1116+	(when (and (consp default)
  1.1117+		   (eq (car default) 'spec))
  1.1118+	  (push (car elem) (gethash (cadr default) table)))))
  1.1119+    table))
  1.1120+
  1.1121+(defun eplot--render (data &optional return-image)
  1.1122+  "Create the chart and display it.
  1.1123+If RETURN-IMAGE is non-nil, return it instead of displaying it."
  1.1124+  (let* ((chart (eplot--make-chart data))
  1.1125+	 svg)
  1.1126+    (with-slots ( width height xs ys
  1.1127+		  margin-left margin-right margin-top margin-bottom
  1.1128+		  grid-position plots x-min format
  1.1129+		  x-label-orientation)
  1.1130+	chart
  1.1131+      ;; Set the size of the chart based on the window it's going to
  1.1132+      ;; be displayed in.  It uses the *eplot* window by default, or
  1.1133+      ;; the current one if that isn't displayed.
  1.1134+      (let ((factor (image-compute-scaling-factor image-scaling-factor)))
  1.1135+	(unless width
  1.1136+	  (setq width (truncate
  1.1137+		       (/ (* (window-pixel-width
  1.1138+ 			      (get-buffer-window "*eplot*" t))
  1.1139+			     0.9)
  1.1140+			  factor))))
  1.1141+	(unless height
  1.1142+	  (setq height (truncate
  1.1143+			(/ (* (window-pixel-height
  1.1144+			       (get-buffer-window "*eplot*" t))
  1.1145+			      0.9)
  1.1146+			   factor)))))
  1.1147+      (setq svg (svg-create width height)
  1.1148+	    xs (- width margin-left margin-right)
  1.1149+	    ys (- height margin-top margin-bottom))
  1.1150+      ;; Protect against being called in an empty buffer.
  1.1151+      (if (not (and plots
  1.1152+		    ;; Sanity check against the user choosing dimensions
  1.1153+		    ;; that leave no space for the plot.
  1.1154+		    (> ys 0) (> xs 0)))
  1.1155+	  ;; Just draw the basics.
  1.1156+	  (eplot--draw-basics svg chart)
  1.1157+
  1.1158+	;; Horizontal bar charts are special.
  1.1159+	(when (eq format 'horizontal-bar-chart)
  1.1160+	  (eplot--adjust-horizontal-bar-chart chart data))
  1.1161+	;; Compute min/max based on all plots, and also compute x-ticks
  1.1162+	;; etc.
  1.1163+	(eplot--compute-chart-dimensions chart)
  1.1164+	(when (and (eq x-label-orientation 'vertical)
  1.1165+		   (eplot--default-p 'margin-bottom (slot-value chart 'data)))
  1.1166+	  (eplot--adjust-vertical-x-labels chart))
  1.1167+	;; Analyze values and adjust values accordingly.
  1.1168+	(eplot--adjust-chart chart)
  1.1169+	;; Compute the Y labels -- this may adjust `margin-left'.
  1.1170+	(eplot--compute-y-labels chart)
  1.1171+	;; Compute the X labels -- this may adjust `margin-bottom'.
  1.1172+	(eplot--compute-x-labels chart)
  1.1173+	;; Draw background/borders/titles/etc.
  1.1174+	(eplot--draw-basics svg chart)
  1.1175+
  1.1176+	(when (eq grid-position 'top)
  1.1177+	  (eplot--draw-plots svg chart))
  1.1178+
  1.1179+	(eplot--draw-x-ticks svg chart)
  1.1180+	(unless (eq format 'horizontal-bar-chart)
  1.1181+	  (eplot--draw-y-ticks svg chart))
  1.1182+      
  1.1183+	;; Draw axes.
  1.1184+	(with-slots ( margin-left margin-right margin-margin-top
  1.1185+		      margin-bottom axes-color)
  1.1186+	    chart
  1.1187+	  (svg-line svg margin-left margin-top margin-left
  1.1188+		    (+ (- height margin-bottom) 5)
  1.1189+		    :stroke axes-color)
  1.1190+	  (svg-line svg (- margin-left 5) (- height margin-bottom)
  1.1191+		    (- width margin-right) (- height margin-bottom)
  1.1192+		    :stroke axes-color))
  1.1193+    
  1.1194+	(when (eq grid-position 'bottom)
  1.1195+	  (eplot--draw-plots svg chart)))
  1.1196+
  1.1197+      (with-slots (frame-color frame-width) chart
  1.1198+	(when (or frame-color frame-width)
  1.1199+	  (svg-rectangle svg margin-left margin-top xs ys
  1.1200+			 :stroke-width frame-width
  1.1201+			 :fill "none"
  1.1202+			 :stroke-color frame-color)))
  1.1203+      (eplot--draw-legend svg chart))
  1.1204+
  1.1205+    (if return-image
  1.1206+	svg
  1.1207+      (svg-insert-image svg)
  1.1208+      chart)))
  1.1209+
  1.1210+(defun eplot--adjust-horizontal-bar-chart (chart data)
  1.1211+  (with-slots ( plots bar-font bar-font-size bar-font-weight margin-left
  1.1212+		width margin-right xs)
  1.1213+      chart
  1.1214+    (with-slots ( data-format values) (car plots)
  1.1215+      (push 'xy data-format)
  1.1216+      ;; Flip the values -- we want the values to be on the X
  1.1217+      ;; axis instead.
  1.1218+      (setf values
  1.1219+	    (cl-loop for value in values
  1.1220+		     for i from 1
  1.1221+		     collect (list :value i
  1.1222+				   :x (plist-get value :value)
  1.1223+				   :settings
  1.1224+				   (plist-get value :settings))))
  1.1225+      (when (eplot--default-p 'margin-left data)
  1.1226+	(setf margin-left
  1.1227+	      (+ (cl-loop for value in values
  1.1228+			  maximize
  1.1229+			  (eplot--text-width
  1.1230+			   (eplot--vs 'label (plist-get value :settings))
  1.1231+			   bar-font bar-font-size bar-font-weight))
  1.1232+		 20)
  1.1233+	      xs (- width margin-left margin-right))))))
  1.1234+
  1.1235+(defun eplot--draw-basics (svg chart)
  1.1236+  (with-slots ( width height 
  1.1237+		chart-color font font-size font-weight
  1.1238+		margin-left margin-right margin-top margin-bottom
  1.1239+		background-color label-color
  1.1240+		xs ys)
  1.1241+      chart
  1.1242+    ;; Add background.
  1.1243+    (eplot--draw-background chart svg 0 0 width height)
  1.1244+    (with-slots ( background-image-file background-image-opacity
  1.1245+		  background-image-cover)
  1.1246+	chart
  1.1247+      (when (and background-image-file
  1.1248+		 ;; Sanity checks to avoid erroring out later.
  1.1249+		 (file-exists-p background-image-file)
  1.1250+		 (file-regular-p background-image-file))
  1.1251+	(apply #'svg-embed svg background-image-file "image/jpeg" nil
  1.1252+	       :opacity background-image-opacity
  1.1253+	       :preserveAspectRatio "xMidYMid slice"
  1.1254+	       (if (memq background-image-cover '(all frame))
  1.1255+		   `(:x 0 :y 0 :width ,width :height ,height)
  1.1256+		 `(:x ,margin-left :y ,margin-top :width ,xs :height ,ys)))
  1.1257+	(when (eq background-image-cover 'frame)
  1.1258+	  (eplot--draw-background chart svg margin-left margin-right xs ys))))
  1.1259+    ;; Area between plot and edges.
  1.1260+    (with-slots (surround-color) chart
  1.1261+      (when surround-color
  1.1262+	(svg-rectangle svg 0 0 width height
  1.1263+		       :fill surround-color)
  1.1264+	(svg-rectangle svg margin-left margin-top
  1.1265+		       xs ys
  1.1266+		       :fill background-color)))
  1.1267+    ;; Border around the entire chart.
  1.1268+    (with-slots (border-width border-color) chart
  1.1269+      (when (or border-width border-color)
  1.1270+	(svg-rectangle svg 0 0 width height
  1.1271+		       :stroke-width (or border-width 1)
  1.1272+		       :fill "none"
  1.1273+		       :stroke-color (or border-color chart-color))))
  1.1274+    ;; Frame around the plot.
  1.1275+    (with-slots (frame-width frame-color) chart
  1.1276+      (when (or frame-width frame-color)
  1.1277+	(svg-rectangle svg margin-left margin-top xs ys
  1.1278+		       :stroke-width (or frame-width 1)
  1.1279+		       :fill "none"
  1.1280+		       :stroke-color (or frame-color chart-color))))
  1.1281+    ;; Title and legends.
  1.1282+    (with-slots (title title-color) chart
  1.1283+      (when title
  1.1284+	(svg-text svg title
  1.1285+		  :font-family font
  1.1286+		  :text-anchor "middle"
  1.1287+		  :font-weight font-weight
  1.1288+		  :font-size font-size
  1.1289+		  :fill title-color
  1.1290+		  :x (+ margin-left (/ (- width margin-left margin-right) 2))
  1.1291+		  :y (+ 3 (/ margin-top 2)))))
  1.1292+    (with-slots (x-title) chart
  1.1293+      (when x-title
  1.1294+	(svg-text svg x-title
  1.1295+		  :font-family font
  1.1296+		  :text-anchor "middle"
  1.1297+		  :font-weight font-weight
  1.1298+		  :font-size font-size
  1.1299+		  :fill label-color
  1.1300+		  :x (+ margin-left (/ (- width margin-left margin-right) 2))
  1.1301+		  :y (- height (/ margin-bottom 4)))))
  1.1302+    (with-slots (y-title) chart
  1.1303+      (when y-title
  1.1304+	(let ((text-height
  1.1305+	       (eplot--text-height y-title font font-size font-weight)))
  1.1306+	  (svg-text svg y-title
  1.1307+		    :font-family font
  1.1308+		    :text-anchor "middle"
  1.1309+		    :font-weight font-weight
  1.1310+		    :font-size font-size
  1.1311+		    :fill label-color
  1.1312+		    :transform
  1.1313+		    (format "translate(%s,%s) rotate(-90)"
  1.1314+			    (- (/ margin-left 2) (/ text-height 2) 4)
  1.1315+			    (+ margin-top
  1.1316+			       (/ (- height margin-bottom margin-top) 2)))))))))
  1.1317+
  1.1318+(defun eplot--draw-background (chart svg left top width height)
  1.1319+  (with-slots (background-gradient background-color) chart
  1.1320+    (let ((gradient (eplot--parse-gradient background-gradient))
  1.1321+	  id)
  1.1322+      (when gradient
  1.1323+	(setq id (format "gradient-%s" (make-temp-name "grad")))
  1.1324+	(eplot--gradient svg id 'linear
  1.1325+			 (eplot--stops (eplot--vs 'from gradient)
  1.1326+				       (eplot--vs 'to gradient))
  1.1327+			 (eplot--vs 'direction gradient)))
  1.1328+      (apply #'svg-rectangle svg left top width height
  1.1329+	     (if gradient
  1.1330+		 `(:gradient ,id)
  1.1331+	       `(:fill ,background-color))))))
  1.1332+
  1.1333+(defun eplot--compute-chart-dimensions (chart)
  1.1334+  (with-slots ( min max plots x-values x-min x-max x-ticks
  1.1335+		print-format font-size
  1.1336+		xs
  1.1337+		inhibit-compute-x-step x-type x-step-map format
  1.1338+		x-tick-step x-label-step
  1.1339+		label-font label-font-size x-label-format)
  1.1340+      chart
  1.1341+    (let ((set-min min)
  1.1342+	  (set-max max))
  1.1343+      (dolist (plot plots)
  1.1344+	(with-slots (values data-format) plot
  1.1345+	  (let* ((vals (nconc (seq-map (lambda (v) (plist-get v :value)) values)
  1.1346+			      (and (memq 'two-values data-format)
  1.1347+				   (seq-map
  1.1348+				    (lambda (v) (plist-get v :extra-value))
  1.1349+				    values)))))
  1.1350+	    ;; Set the x-values based on the first plot.
  1.1351+	    (unless x-values
  1.1352+	      (setq print-format (cond
  1.1353+				  ((memq 'year data-format) 'year)
  1.1354+				  ((memq 'date data-format) 'date)
  1.1355+				  ((memq 'time data-format) 'time)
  1.1356+				  (t 'number)))
  1.1357+	      (cond
  1.1358+	       ((or (memq 'xy data-format)
  1.1359+		    (memq 'year data-format))
  1.1360+		(setq x-values (cl-loop for val in values
  1.1361+					collect (plist-get val :x))
  1.1362+		      x-min (if (eq format 'horizontal-bar-chart)
  1.1363+				0
  1.1364+			      (seq-min x-values))
  1.1365+		      x-max (seq-max x-values)
  1.1366+		      x-ticks (eplot--get-ticks x-min x-max xs))
  1.1367+		(when (memq 'year data-format)
  1.1368+		  (setq print-format 'literal-year)))
  1.1369+	       ((memq 'date data-format)
  1.1370+		(setq x-values
  1.1371+		      (cl-loop for val in values
  1.1372+			       collect
  1.1373+			       (time-to-days
  1.1374+				(encode-time
  1.1375+				 (decoded-time-set-defaults
  1.1376+				  (iso8601-parse-date
  1.1377+				   (format "%d" (plist-get val :x)))))))
  1.1378+		      x-min (seq-min x-values)
  1.1379+		      x-max (seq-max x-values)
  1.1380+		      inhibit-compute-x-step t)
  1.1381+		(let ((xs (eplot--get-date-ticks
  1.1382+			   x-min x-max xs
  1.1383+			   label-font label-font-size x-label-format)))
  1.1384+		  (setq x-ticks (car xs)
  1.1385+			print-format (cadr xs)
  1.1386+			x-tick-step 1
  1.1387+			x-label-step 1
  1.1388+			x-step-map (nth 2 xs))))
  1.1389+	       ((memq 'time data-format)
  1.1390+		(setq x-values
  1.1391+		      (cl-loop for val in values
  1.1392+			       collect
  1.1393+			       (time-convert
  1.1394+				(encode-time
  1.1395+				 (decoded-time-set-defaults
  1.1396+				  (iso8601-parse-time
  1.1397+				   (format "%06d" (plist-get val :x)))))
  1.1398+				'integer))
  1.1399+		      x-min (car x-values)
  1.1400+		      x-max (car (last x-values))
  1.1401+		      inhibit-compute-x-step t)
  1.1402+		(let ((xs (eplot--get-time-ticks
  1.1403+			   x-min x-max xs label-font label-font-size
  1.1404+			   x-label-format)))
  1.1405+		  (setq x-ticks (car xs)
  1.1406+			print-format (cadr xs)
  1.1407+			x-tick-step 1
  1.1408+			x-label-step 1
  1.1409+			x-step-map (nth 2 xs))))
  1.1410+	       (t
  1.1411+		;; This is a one-dimensional plot -- we don't have X
  1.1412+		;; values, really, so we just do zero to (1- (length
  1.1413+		;; values)).
  1.1414+		(setq x-type 'one-dimensional
  1.1415+		      x-values (cl-loop for i from 0
  1.1416+					repeat (length values)
  1.1417+					collect i)
  1.1418+		      x-min (car x-values)
  1.1419+		      x-max (car (last x-values))
  1.1420+		      x-ticks x-values))))
  1.1421+	    (unless set-min
  1.1422+	      (setq min (min (or min 1.0e+INF) (seq-min vals))))
  1.1423+	    (unless set-max
  1.1424+	      (setq max (max (or max -1.0e+INF) (seq-max vals))))))))))
  1.1425+
  1.1426+(defun eplot--adjust-chart (chart)
  1.1427+  (with-slots ( x-tick-step x-label-step y-tick-step y-label-step
  1.1428+		min max ys format inhibit-compute-x-step
  1.1429+		y-ticks xs x-values print-format
  1.1430+		x-label-format label-font label-font-size data
  1.1431+		x-ticks)
  1.1432+      chart
  1.1433+    (setq y-ticks (and max
  1.1434+		       (eplot--get-ticks
  1.1435+			min
  1.1436+			;; We get 5% more ticks to check whether we
  1.1437+			;; should extend max.
  1.1438+			(if (eplot--default-p 'max data)
  1.1439+			    (* max 1.02)
  1.1440+			  max)
  1.1441+			ys)))
  1.1442+    (when (eplot--default-p 'max data)
  1.1443+      (setq max (max max (car (last y-ticks)))))
  1.1444+    (if (eq format 'bar-chart)
  1.1445+	(setq x-tick-step 1
  1.1446+	      x-label-step 1)
  1.1447+      (unless inhibit-compute-x-step
  1.1448+	(let ((xt (eplot--compute-x-ticks
  1.1449+		   xs x-ticks print-format
  1.1450+		   x-label-format label-font label-font-size)))
  1.1451+	  (setq x-tick-step (car xt)
  1.1452+		x-label-step (cadr xt)))))
  1.1453+    (when max
  1.1454+      (let ((yt (eplot--compute-y-ticks
  1.1455+		 ys y-ticks
  1.1456+		 (eplot--text-height "100" label-font label-font-size))))
  1.1457+	(setq y-tick-step (car yt)
  1.1458+	      y-label-step (cadr yt))))
  1.1459+    ;; If max is less than 2% off from a pleasant number, then
  1.1460+    ;; increase max.
  1.1461+    (when (eplot--default-p 'max data)
  1.1462+      (cl-loop for tick in (reverse y-ticks)
  1.1463+	       when (and (< max tick)
  1.1464+			 (< (e/ (- tick max) (- max min)) 0.02))
  1.1465+	       return (progn
  1.1466+			(setq max tick)
  1.1467+			;; Chop off any further ticks.
  1.1468+			(setcdr (member tick y-ticks) nil))))
  1.1469+
  1.1470+    (when y-ticks
  1.1471+      (if (and (eplot--default-p 'min data)
  1.1472+	       (< (car y-ticks) min))
  1.1473+	  (setq min (car y-ticks))
  1.1474+	;; We may be extending the bottom of the chart to get pleasing
  1.1475+	;; numbers.  We don't want to be drawing the chart on top of the
  1.1476+	;; X axis, because the chart won't be visible there.
  1.1477+	(when (and nil
  1.1478+		   (<= min (car y-ticks))
  1.1479+		   ;; But not if we start at origo, because that just
  1.1480+		   ;; looks confusing.
  1.1481+		   (not (zerop min)))
  1.1482+	  (setq min (- (car y-ticks)
  1.1483+		       ;; 2% of the value range.
  1.1484+		       (* 0.02 (- (car (last y-ticks)) (car y-ticks))))))))))
  1.1485+
  1.1486+(defun eplot--adjust-vertical-x-labels (chart)
  1.1487+  (with-slots ( x-step-map x-ticks format plots
  1.1488+		print-format x-label-format label-font
  1.1489+		label-font-size margin-bottom
  1.1490+		bar-font bar-font-size bar-font-weight)
  1.1491+      chart
  1.1492+    ;; Make X ticks.
  1.1493+    (let ((width
  1.1494+	   (cl-loop
  1.1495+	    for xv in (or x-step-map x-ticks)
  1.1496+	    for x = (if (consp xv) (car xv) xv)
  1.1497+	    for i from 0
  1.1498+	    for value = (and (equal format 'bar-chart)
  1.1499+			     (elt (slot-value (car plots) 'values) i))
  1.1500+	    for label = (if (equal format 'bar-chart)
  1.1501+			    (eplot--vs 'label
  1.1502+				       (plist-get value :settings)
  1.1503+				       ;; When we're doing bar charts, we
  1.1504+				       ;; want default labeling to start with
  1.1505+				       ;; 1 and not zero.
  1.1506+				       (format "%s" (1+ x)))
  1.1507+			  (eplot--format-value x print-format x-label-format))
  1.1508+	    maximize (if (equal format 'bar-chart)
  1.1509+			 (eplot--text-width
  1.1510+			  label bar-font bar-font-size bar-font-weight)
  1.1511+		       (eplot--text-width
  1.1512+			label label-font label-font-size)))))
  1.1513+      ;; Ensure that we have enough room to display the X labels
  1.1514+      ;; (unless overridden).
  1.1515+      (with-slots ( height margin-top ys
  1.1516+		    y-ticks y-tick-step y-label-step min max)
  1.1517+	  chart
  1.1518+	(setq margin-bottom (max margin-bottom (+ width 40))
  1.1519+	      ys (- height margin-top margin-bottom))))))
  1.1520+
  1.1521+(defun eplot--compute-x-labels (chart)
  1.1522+  (with-slots ( x-step-map x-ticks
  1.1523+		format plots print-format x-label-format x-labels
  1.1524+		x-tick-step x-label-step
  1.1525+		x-label-orientation margin-bottom)
  1.1526+      chart
  1.1527+    ;; Make X ticks.
  1.1528+    (setf x-labels
  1.1529+	  (cl-loop
  1.1530+	   for xv in (or x-step-map x-ticks)
  1.1531+	   for x = (if (consp xv) (car xv) xv)
  1.1532+	   for do-tick = (if (consp xv)
  1.1533+			     (nth 1 xv)
  1.1534+			   (zerop (e% x x-tick-step)))
  1.1535+	   for do-label = (if (consp xv)
  1.1536+			      (nth 2 xv)
  1.1537+			    (zerop (e% x x-label-step)))
  1.1538+	   for i from 0
  1.1539+	   for value = (and (equal format 'bar-chart)
  1.1540+			    (elt (slot-value (car plots) 'values) i))
  1.1541+	   collect (list
  1.1542+		    (if (equal format 'bar-chart)
  1.1543+			(eplot--vs 'label
  1.1544+				   (plist-get value :settings)
  1.1545+				   ;; When we're doing bar charts, we
  1.1546+				   ;; want default labeling to start with
  1.1547+				   ;; 1 and not zero.
  1.1548+				   (format "%s" (1+ x)))
  1.1549+		      (eplot--format-value x print-format x-label-format))
  1.1550+		    do-tick
  1.1551+		    do-label)))))
  1.1552+
  1.1553+(defun eplot--draw-x-ticks (svg chart)
  1.1554+  (with-slots ( x-step-map x-ticks format layout print-format
  1.1555+		margin-left margin-right margin-top margin-bottom
  1.1556+		x-min x-max xs
  1.1557+		width height
  1.1558+		axes-color label-color
  1.1559+		grid grid-opacity grid-color
  1.1560+		font x-tick-step x-label-step x-label-format x-label-orientation
  1.1561+		label-font label-font-size
  1.1562+		plots x-labels
  1.1563+		bar-font bar-font-size bar-font-weight)
  1.1564+      chart
  1.1565+    (let ((font label-font)
  1.1566+	  (font-size label-font-size)
  1.1567+	  (font-weight 'normal))
  1.1568+      (when (equal format 'bar-chart)
  1.1569+	(setq font bar-font
  1.1570+	      font-size bar-font-size
  1.1571+	      font-weight bar-font-weight))
  1.1572+      ;; Make X ticks.
  1.1573+      (cl-loop with label-height
  1.1574+	       for xv in (or x-step-map x-ticks)
  1.1575+	       for x = (if (consp xv) (car xv) xv)
  1.1576+	       for i from 0
  1.1577+	       for (label do-tick do-label) in x-labels
  1.1578+	       for stride = (eplot--stride chart x-ticks)
  1.1579+	       for px = (if (equal format 'bar-chart)
  1.1580+			    (+ margin-left (* x stride) (/ stride 2)
  1.1581+			       (/ (* stride 0.1) 2))
  1.1582+			  (+ margin-left
  1.1583+			     (* (/ (- (* 1.0 x) x-min) (- x-max x-min))
  1.1584+				xs)))
  1.1585+	       ;; We might have one extra stride outside the area -- don't
  1.1586+	       ;; draw it.
  1.1587+	       when (<= px (- width margin-right))
  1.1588+	       do
  1.1589+	       (when do-tick
  1.1590+		 ;; Draw little tick.
  1.1591+		 (unless (equal format 'bar-chart)
  1.1592+		   (svg-line svg
  1.1593+			     px (- height margin-bottom)
  1.1594+			     px (+ (- height margin-bottom)
  1.1595+				   (if do-label
  1.1596+				       4
  1.1597+				     2))
  1.1598+			     :stroke axes-color))
  1.1599+		 (when (or (eq grid 'xy) (eq grid 'x))
  1.1600+		   (svg-line svg px margin-top
  1.1601+			     px (- height margin-bottom)
  1.1602+			     :opacity grid-opacity
  1.1603+			     :stroke grid-color)))
  1.1604+	       (when (and do-label
  1.1605+			  ;; We want to skip marking the first X value
  1.1606+			  ;; unless we're a bar chart or we're a one
  1.1607+			  ;; dimensional chart.
  1.1608+			  (or (equal format 'bar-chart)
  1.1609+			      t
  1.1610+			      (not (= x-min (car x-values)))
  1.1611+			      (eq x-type 'one-dimensional)
  1.1612+			      (and (not (zerop x)) (not (zerop i)))))
  1.1613+		 (if (eq x-label-orientation 'vertical)
  1.1614+		     (progn
  1.1615+		       (unless label-height
  1.1616+			 ;; The X position we're putting the label at is
  1.1617+			 ;; based on the bottom of the lower-case
  1.1618+			 ;; characters.  So we want to ignore descenders
  1.1619+			 ;; etc, so we use "xx" to determine the height
  1.1620+			 ;; to be able to center the text.
  1.1621+			 (setq label-height
  1.1622+			       (eplot--text-height
  1.1623+				;; If the labels are numerical, we need
  1.1624+				;; to center them using the height of
  1.1625+				;; numbers.
  1.1626+				(if (string-match "^[0-9]+$" label)
  1.1627+				    "10"
  1.1628+				  ;; Otherwise center them on the baseline.
  1.1629+				  "xx")
  1.1630+				font font-size font-weight)))
  1.1631+		       (svg-text svg label
  1.1632+				 :font-family font
  1.1633+				 :text-anchor "end"
  1.1634+				 :font-size font-size
  1.1635+				 :font-weight font-weight
  1.1636+				 :fill label-color
  1.1637+				 :transform
  1.1638+				 (format "translate(%s,%s) rotate(-90)"
  1.1639+					 (+ px (/ label-height 2))
  1.1640+					 (- height margin-bottom -10))))
  1.1641+		   (svg-text svg label
  1.1642+			     :font-family font
  1.1643+			     :text-anchor "middle"
  1.1644+			     :font-size font-size
  1.1645+			     :font-weight font-weight
  1.1646+			     :fill label-color
  1.1647+			     :x px
  1.1648+			     :y (+ (- height margin-bottom)
  1.1649+				   font-size
  1.1650+				   (if (equal format 'bar-chart)
  1.1651+				       (if (equal layout 'compact) 3 5)
  1.1652+				     2)))))))))
  1.1653+
  1.1654+(defun eplot--stride (chart values)
  1.1655+  (with-slots (xs x-type format) chart
  1.1656+    (if (eq x-type 'one-dimensional)
  1.1657+	(e/ xs
  1.1658+	    ;; Fenceposting bar-chart vs everything else.
  1.1659+	    (if (eq format 'bar-chart)
  1.1660+		(length values)
  1.1661+	      (1- (length values))))
  1.1662+      (e/ xs (length values)))))
  1.1663+
  1.1664+(defun eplot--default-p (slot data)
  1.1665+  "Return non-nil if SLOT is at the default value."
  1.1666+  (and (not (assq slot eplot--user-defaults))
  1.1667+       (not (assq slot data))))
  1.1668+
  1.1669+(defun eplot--compute-y-labels (chart)
  1.1670+  (with-slots ( y-ticks y-labels
  1.1671+		width height min max xs ys
  1.1672+		margin-top margin-bottom margin-left margin-right
  1.1673+		y-tick-step y-label-step y-label-format)
  1.1674+      chart
  1.1675+    ;; First collect all the labels we're thinking about outputting.
  1.1676+    (setq y-labels
  1.1677+	  (cl-loop for y in y-ticks
  1.1678+		   for py = (- (- height margin-bottom)
  1.1679+			       (* (/ (- (* 1.0 y) min) (- max min))
  1.1680+				  ys))
  1.1681+		   when (and (<= margin-top py (- height margin-bottom))
  1.1682+			     (zerop (e% y y-tick-step))
  1.1683+			     (zerop (e% y y-label-step)))
  1.1684+		   collect (eplot--format-y
  1.1685+			    y (- (cadr y-ticks) (car y-ticks)) nil
  1.1686+			    y-label-format)))
  1.1687+    ;; Check the labels to see whether we have too many digits for
  1.1688+    ;; what we're actually going to display.  Man, this is a lot of
  1.1689+    ;; back-and-forth and should be rewritten to be less insanely
  1.1690+    ;; inefficient.
  1.1691+    (when (= (seq-count (lambda (label)
  1.1692+			  (string-match "\\." label))
  1.1693+			y-labels)
  1.1694+	     (length y-labels))
  1.1695+      (setq y-labels
  1.1696+	    (cl-loop with max = (cl-loop for label in y-labels
  1.1697+					 maximize (eplot--decimal-digits
  1.1698+						   (string-to-number label)))
  1.1699+		     for label in y-labels
  1.1700+		     collect (format (if (zerop max)
  1.1701+					 "%d"
  1.1702+				       (format "%%.%df" max))
  1.1703+				     (string-to-number label)))))
  1.1704+    (setq y-labels (cl-coerce y-labels 'vector))
  1.1705+    ;; Ensure that we have enough room to display the Y labels
  1.1706+    ;; (unless overridden).
  1.1707+    (when (eplot--default-p 'margin-left (slot-value chart 'data))
  1.1708+      (with-slots (label-font label-font-size) chart
  1.1709+	(setq margin-left (max margin-left
  1.1710+			       (+ (eplot--text-width
  1.1711+				   (elt y-labels (1- (length y-labels)))
  1.1712+				   label-font label-font-size)
  1.1713+				  10))
  1.1714+	      xs (- width margin-left margin-right))))))
  1.1715+
  1.1716+(defun eplot--draw-y-ticks (svg chart)
  1.1717+  (with-slots ( y-ticks y-labels y-tick-step y-label-step label-color
  1.1718+		label-font label-font-size
  1.1719+		width height min max ys
  1.1720+		margin-top margin-bottom margin-left margin-right
  1.1721+		axes-color
  1.1722+		grid grid-opacity grid-color)
  1.1723+      chart
  1.1724+    ;; Make Y ticks.
  1.1725+    (cl-loop with lnum = 0
  1.1726+	     with text-height = (eplot--text-height
  1.1727+				 "012" label-font label-font-size)
  1.1728+	     for y in y-ticks
  1.1729+	     for i from 0
  1.1730+	     for py = (- (- height margin-bottom)
  1.1731+			 (* (/ (- (* 1.0 y) min) (- max min))
  1.1732+			    ys))
  1.1733+	     do
  1.1734+	     (when (and (<= margin-top py (- height margin-bottom))
  1.1735+			(zerop (e% y y-tick-step)))
  1.1736+	       (svg-line svg margin-left py
  1.1737+			 (- margin-left 3) py
  1.1738+			 :stroke-color axes-color)
  1.1739+	       (when (or (eq grid 'xy) (eq grid 'y))
  1.1740+		 (svg-line svg margin-left py
  1.1741+			   (- width margin-right) py
  1.1742+			   :opacity grid-opacity
  1.1743+			   :stroke-color grid-color))
  1.1744+	       (when (zerop (e% y y-label-step))
  1.1745+		 (svg-text svg (elt y-labels lnum)
  1.1746+			   :font-family label-font
  1.1747+			   :text-anchor "end"
  1.1748+			   :font-size label-font-size
  1.1749+			   :fill label-color
  1.1750+			   :x (- margin-left 6)
  1.1751+			   :y (+ py (/ text-height 2) -1))
  1.1752+		 (cl-incf lnum))))))
  1.1753+
  1.1754+(defun eplot--text-width (text font font-size &optional font-weight)
  1.1755+  (string-pixel-width
  1.1756+   (propertize text 'face
  1.1757+	       (list :font (font-spec :family font
  1.1758+				      :weight (or font-weight 'normal)
  1.1759+				      :size font-size)))))
  1.1760+
  1.1761+(defvar eplot--text-size-cache (make-hash-table :test #'equal))
  1.1762+
  1.1763+(defun eplot--text-height (text font font-size &optional font-weight)
  1.1764+  (cdr (eplot--text-size text font font-size font-weight)))
  1.1765+
  1.1766+(defun eplot--text-size (text font font-size font-weight)
  1.1767+  (let ((key (list text font font-size font-weight)))
  1.1768+    (or (gethash key eplot--text-size-cache)
  1.1769+	(let ((size (eplot--text-size-1 text font font-size font-weight)))
  1.1770+	  (setf (gethash key eplot--text-size-cache) size)
  1.1771+	  size))))
  1.1772+
  1.1773+(defun eplot--text-size-1 (text font font-size font-weight)
  1.1774+  (if (not (executable-find "convert"))
  1.1775+      ;; This "default" text size is kinda bogus.
  1.1776+      (cons (* (length text) font-size) font-size)
  1.1777+    (let* ((size (* font-size 10))
  1.1778+	   (svg (svg-create size size))
  1.1779+	   text-size)
  1.1780+      (svg-rectangle svg 0 0 size size :fill "black")
  1.1781+      (svg-text svg text
  1.1782+		:font-family font
  1.1783+		:text-anchor "middle"
  1.1784+		:font-size font-size
  1.1785+		:font-weight (or font-weight 'normal)
  1.1786+		:fill "white"
  1.1787+		:x (/ size 2)
  1.1788+		:y (/ size 2))
  1.1789+      (with-temp-buffer
  1.1790+	(set-buffer-multibyte nil)
  1.1791+	(svg-print svg)
  1.1792+	(let* ((file (concat (make-temp-name "/tmp/eplot") ".svg"))
  1.1793+	       (png (file-name-with-extension file ".png")))
  1.1794+	  (unwind-protect
  1.1795+	      (progn
  1.1796+		(write-region (point-min) (point-max) file nil 'silent)
  1.1797+		;; rsvg-convert is 5x faster than convert when doing SVG, so
  1.1798+		;; if we have it, we use it.
  1.1799+		(when (executable-find "rsvg-convert")
  1.1800+		  (unwind-protect
  1.1801+		      (call-process "rsvg-convert" nil nil nil
  1.1802+				    (format "--output=%s" png) file)
  1.1803+		    (when (file-exists-p png)
  1.1804+		      (delete-file file)
  1.1805+		      (setq file png))))
  1.1806+		(erase-buffer)
  1.1807+		(when (zerop (call-process "convert" nil t nil
  1.1808+					   "-trim" "+repage" file "info:-"))
  1.1809+		  (goto-char (point-min))
  1.1810+		  (when (re-search-forward " \\([0-9]+\\)x\\([0-9]+\\)" nil t)
  1.1811+		    (setq text-size
  1.1812+			  (cons (string-to-number (match-string 1))
  1.1813+				(string-to-number (match-string 2)))))))
  1.1814+	    (when (file-exists-p file)
  1.1815+	      (delete-file file)))))
  1.1816+      (or text-size
  1.1817+	  ;; This "default" text size is kinda bogus.
  1.1818+	  (cons (* (length text) font-size) font-size)))))
  1.1819+
  1.1820+(defun eplot--draw-legend (svg chart)
  1.1821+  (with-slots ( legend plots
  1.1822+		margin-left margin-top
  1.1823+		font font-size font-weight
  1.1824+		background-color axes-color
  1.1825+		legend-color legend-background-color legend-border-color)
  1.1826+      chart
  1.1827+    (when (eq legend 'true)
  1.1828+      (when-let ((names
  1.1829+		  (cl-loop for plot in plots
  1.1830+			   for name = (slot-value plot 'name)
  1.1831+			   when name
  1.1832+			   collect
  1.1833+			   (cons name (slot-value plot 'color)))))
  1.1834+	(svg-rectangle svg (+ margin-left 20) (+ margin-top 20)
  1.1835+		       (format "%dex"
  1.1836+			       (+ 2
  1.1837+				  (seq-max (mapcar (lambda (name)
  1.1838+						     (length (car name)))
  1.1839+						   names))))
  1.1840+		       (* font-size (+ (length names) 2))
  1.1841+		       :font-size font-size
  1.1842+		       :fill-color legend-background-color
  1.1843+		       :stroke-color legend-border-color)
  1.1844+	(cl-loop for name in names
  1.1845+		 for i from 0
  1.1846+		 do (svg-text svg (car name)
  1.1847+			      :font-family font
  1.1848+			      :text-anchor "front"
  1.1849+			      :font-size font-size
  1.1850+			      :font-weight font-weight
  1.1851+			      :fill (or (cdr name) legend-color)
  1.1852+			      :x (+ margin-left 25)
  1.1853+			      :y (+ margin-top 40 (* i font-size))))))))
  1.1854+
  1.1855+(defun eplot--format-y (y spacing whole format-string)
  1.1856+  (format (or format-string "%s")
  1.1857+	  (cond
  1.1858+	   ((or (= (round (* spacing 100)) 10) (= (round (* spacing 100)) 20))
  1.1859+	    (format "%.1f" y))
  1.1860+	   ((< spacing 0.01)
  1.1861+	    (format "%.3f" y))
  1.1862+	   ((< spacing 1)
  1.1863+	    (format "%.2f" y))
  1.1864+	   ((and (< spacing 1) (not (zerop (mod (* spacing 10) 1))))
  1.1865+	    (format "%.1f" y))
  1.1866+	   ((zerop (% spacing 1000000000))
  1.1867+	    (format "%dG" (/ y 1000000000)))
  1.1868+	   ((zerop (% spacing 1000000))
  1.1869+	    (format "%dM" (/ y 1000000)))
  1.1870+	   ((zerop (% spacing 1000))
  1.1871+	    (format "%dk" (/ y 1000)))
  1.1872+	   ((>= spacing 1)
  1.1873+	    (format "%s" y))
  1.1874+	   ((not whole)
  1.1875+	    (format "%.1f" y))
  1.1876+	   (t
  1.1877+	    (format "%s" y)))))
  1.1878+
  1.1879+(defun eplot--format-value (value print-format label-format)
  1.1880+  (replace-regexp-in-string
  1.1881+   ;; Texts in SVG collapse multiple spaces into one.  So do it here,
  1.1882+   ;; too, so that width calculations are correct.
  1.1883+   " +" " "
  1.1884+   (cond
  1.1885+    ((eq print-format 'date)
  1.1886+     (format-time-string
  1.1887+      (or label-format "%Y-%m-%d") (eplot--days-to-time value)))
  1.1888+    ((eq print-format 'year)
  1.1889+     (format-time-string (or label-format "%Y") (eplot--days-to-time value)))
  1.1890+    ((eq print-format 'time)
  1.1891+     (format-time-string (or label-format "%H:%M:%S") value))
  1.1892+    ((eq print-format 'minute)
  1.1893+     (format-time-string (or label-format "%H:%M") value))
  1.1894+    ((eq print-format 'hour)
  1.1895+     (format-time-string (or label-format "%H") value))
  1.1896+    (t
  1.1897+     (format (or label-format "%s") value)))))
  1.1898+
  1.1899+(defun eplot--compute-x-ticks (xs x-values print-format x-label-format
  1.1900+				  label-font label-font-size)
  1.1901+  (let* ((min (seq-min x-values))
  1.1902+	 (max (seq-max x-values))
  1.1903+	 (count (length x-values))
  1.1904+	 (max-print (eplot--format-value max print-format x-label-format))
  1.1905+	 ;; We want each label to be spaced at least as long apart as
  1.1906+	 ;; the length of the longest label, with room for two blanks
  1.1907+	 ;; in between.
  1.1908+	 (min-spacing (* 1.2 (eplot--text-width max-print label-font
  1.1909+						label-font-size)))
  1.1910+	 (digits (eplot--decimal-digits (- (cadr x-values) (car x-values))))
  1.1911+	 (every (e/ 1 (expt 10 digits))))
  1.1912+    (cond
  1.1913+     ;; We have room for every X value.
  1.1914+     ((< (* count min-spacing) xs)
  1.1915+      (list every every))
  1.1916+     ;; We have to prune X labels, but not grid lines.  (We shouldn't
  1.1917+     ;; have a grid line more than every 10 pixels.)
  1.1918+     ((< (* count 10) xs)
  1.1919+      (list every
  1.1920+	    (let ((label-step every))
  1.1921+	      (while (> (/ (- max min) label-step) (/ xs min-spacing))
  1.1922+		(setq label-step (eplot--next-weed label-step)))
  1.1923+	      label-step)))
  1.1924+     ;; We have to reduce both grid lines and labels.
  1.1925+     (t
  1.1926+      (let ((tick-step every))
  1.1927+	(while (> (/ (- max min) tick-step) (/ xs 10))
  1.1928+	  (setq tick-step (eplot--next-weed tick-step)))
  1.1929+	(list tick-step
  1.1930+	      (let ((label-step tick-step))
  1.1931+		(while (> (/ (- max min) label-step) (/ xs min-spacing))
  1.1932+		  (setq label-step (eplot--next-weed label-step))
  1.1933+		  (while (not (zerop (% label-step tick-step)))
  1.1934+		    (setq label-step (eplot--next-weed label-step))))
  1.1935+		label-step)))))))
  1.1936+
  1.1937+(defun eplot--compute-y-ticks (ys y-values text-height)
  1.1938+  (let* ((min (car y-values))
  1.1939+	 (max (car (last y-values)))
  1.1940+	 (count (length y-values))
  1.1941+	 ;; We want each label to be spaced at least as long apart as
  1.1942+	 ;; the height of the label.
  1.1943+	 (min-spacing (+ text-height 10))
  1.1944+	 (digits (eplot--decimal-digits (- (cadr y-values) (car y-values))))
  1.1945+	 (every (e/ 1 (expt 10 digits))))
  1.1946+    (cond
  1.1947+     ;; We have room for every X value.
  1.1948+     ((< (* count min-spacing) ys)
  1.1949+      (list every every))
  1.1950+     ;; We have to prune Y labels, but not grid lines.  (We shouldn't
  1.1951+     ;; have a grid line more than every 10 pixels.)
  1.1952+     ((< (* count 10) ys)
  1.1953+      (list every
  1.1954+	    (let ((label-step every))
  1.1955+	      (while (> (/ (- max min) label-step) (/ ys min-spacing))
  1.1956+		(setq label-step (eplot--next-weed label-step)))
  1.1957+	      label-step)))
  1.1958+     ;; We have to reduce both grid lines and labels.
  1.1959+     (t
  1.1960+      (let ((tick-step 1))
  1.1961+	(while (> (/ count tick-step) (/ ys 10))
  1.1962+	  (setq tick-step (eplot--next-weed tick-step)))
  1.1963+	(list tick-step
  1.1964+	      (let ((label-step tick-step))
  1.1965+		(while (> (/ count label-step) (/ ys min-spacing))
  1.1966+		  (setq label-step (eplot--next-weed label-step))
  1.1967+		  (while (not (zerop (% label-step tick-step)))
  1.1968+		    (setq label-step (eplot--next-weed label-step))))
  1.1969+		label-step)))))))
  1.1970+
  1.1971+(defvar eplot--pleasing-numbers '(1 2 5 10))
  1.1972+
  1.1973+(defun eplot--next-weed (weed)
  1.1974+  (let (digits series)
  1.1975+    (if (>= weed 1)
  1.1976+	(setq digits (truncate (log weed 10))
  1.1977+	      series (/ weed (expt 10 digits)))
  1.1978+      (setq digits (eplot--decimal-digits weed)
  1.1979+	    series (truncate (* weed (expt 10 digits)))))
  1.1980+    (let ((next (cadr (memq series eplot--pleasing-numbers))))
  1.1981+      (unless next
  1.1982+	(error "Invalid weed: %s" weed))
  1.1983+      (if (>= weed 1)
  1.1984+	  (* next (expt 10 digits))
  1.1985+	(e/ next (expt 10 digits))))))
  1.1986+
  1.1987+(defun eplot--parse-gradient (string)
  1.1988+  (when string
  1.1989+    (let ((bits (split-string string)))
  1.1990+      (list
  1.1991+       (cons 'from (nth 0 bits))
  1.1992+       (cons 'to (nth 1 bits))
  1.1993+       (cons 'direction (intern (or (nth 2 bits) "top-down")))
  1.1994+       (cons 'position (intern (or (nth 3 bits) "below")))))))
  1.1995+
  1.1996+(defun eplot--smooth (values algo xs)
  1.1997+  (if (not algo)
  1.1998+      values
  1.1999+    (let* ((vals (cl-coerce values 'vector))
  1.2000+	   (max (1- (length vals)))
  1.2001+	   (period (* 4 (ceiling (/ max xs)))))
  1.2002+      (cl-case algo
  1.2003+	(moving-average
  1.2004+	 (cl-loop for i from 0 upto max
  1.2005+		  collect (e/ (cl-loop for ii from 0 upto (1- period)
  1.2006+				       sum (elt vals (min (+ i ii) max)))
  1.2007+			      period)))))))
  1.2008+
  1.2009+(defun eplot--vary-color (color n)
  1.2010+  (let ((colors ["#e6194b" "#3cb44b" "#ffe119" "#4363d8" "#f58231" "#911eb4"
  1.2011+		 "#46f0f0" "#f032e6" "#bcf60c" "#fabebe" "#008080" "#e6beff"
  1.2012+		 "#9a6324" "#fffac8" "#800000" "#aaffc3" "#808000" "#ffd8b1"
  1.2013+		 "#000075" "#808080" "#ffffff" "#000000"]))
  1.2014+    (unless (equal color "vary")
  1.2015+      (setq colors
  1.2016+	    (if (string-search " " color)
  1.2017+		(split-string color)
  1.2018+	      (list color))))
  1.2019+    (elt colors (mod n (length colors)))))
  1.2020+
  1.2021+(defun eplot--pv (plot slot &optional default)
  1.2022+  (let ((user (cdr (assq slot eplot--user-defaults))))
  1.2023+    (when (and (stringp user) (zerop (length user)))
  1.2024+      (setq user nil))
  1.2025+    (or user (slot-value plot slot) default)))
  1.2026+
  1.2027+(defun eplot--draw-plots (svg chart)
  1.2028+  (if (eq (slot-value chart 'format) 'horizontal-bar-chart)
  1.2029+      (eplot--draw-horizontal-bar-chart svg chart)
  1.2030+    (eplot--draw-normal-plots svg chart)))
  1.2031+    
  1.2032+(defun eplot--draw-normal-plots (svg chart)
  1.2033+  (with-slots ( plots chart-color height format
  1.2034+		margin-bottom margin-left
  1.2035+		min max xs ys
  1.2036+		margin-top
  1.2037+		x-values x-min x-max
  1.2038+		label-font label-font-size)
  1.2039+      chart
  1.2040+    ;; Draw all the plots.
  1.2041+    (cl-loop for plot in (reverse plots)
  1.2042+	     for plot-number from 0
  1.2043+	     for values = (slot-value plot 'values)
  1.2044+	     for stride = (eplot--stride chart values)
  1.2045+	     for vals = (eplot--smooth
  1.2046+			 (seq-map (lambda (v) (plist-get v :value)) values)
  1.2047+			 (slot-value plot 'smoothing)
  1.2048+			 xs)
  1.2049+	     for polygon = nil
  1.2050+	     for gradient = (eplot--parse-gradient (eplot--pv plot 'gradient))
  1.2051+	     for lpy = nil
  1.2052+	     for lpx = nil
  1.2053+	     for style = (if (eq format 'bar-chart)
  1.2054+			     'bar
  1.2055+			   (slot-value plot 'style))
  1.2056+	     for bar-gap = (* stride 0.1)
  1.2057+	     for clip-id = (format "url(#clip-%d)" plot-number)
  1.2058+	     do
  1.2059+	     (svg--append
  1.2060+	      svg
  1.2061+	      (dom-node 'clipPath
  1.2062+			`((id . ,(format "clip-%d" plot-number)))
  1.2063+			(dom-node 'rect
  1.2064+				  `((x . ,margin-left)
  1.2065+				    (y . , margin-top)
  1.2066+				    (width . ,xs)
  1.2067+				    (height . ,ys)))))
  1.2068+	     (unless gradient
  1.2069+	       (when-let ((fill (slot-value plot 'fill-color)))
  1.2070+		 (setq gradient `((from . ,fill) (to . ,fill)
  1.2071+				  (direction . top-down) (position . below)))))
  1.2072+	     (when gradient
  1.2073+	       (if (eq (eplot--vs 'position gradient) 'above)
  1.2074+		   (push (cons margin-left margin-top) polygon)
  1.2075+		 (push (cons margin-left (- height margin-bottom)) polygon)))
  1.2076+	     (cl-loop
  1.2077+	      for val in vals
  1.2078+	      for value in values
  1.2079+	      for x in x-values
  1.2080+	      for i from 0
  1.2081+	      for settings = (plist-get value :settings)
  1.2082+	      for color = (eplot--vary-color
  1.2083+			   (eplot--vs 'color settings (slot-value plot 'color))
  1.2084+			   i)
  1.2085+	      for py = (- (- height margin-bottom)
  1.2086+			  (* (/ (- (* 1.0 val) min) (- max min))
  1.2087+			     ys))
  1.2088+	      for px = (if (eq style 'bar)
  1.2089+			   (+ margin-left
  1.2090+			      (* (e/ (- x x-min) (- x-max x-min -1))
  1.2091+				 xs))
  1.2092+			 (+ margin-left
  1.2093+			    (* (e/ (- x x-min) (- x-max x-min))
  1.2094+			       xs)))
  1.2095+	      do
  1.2096+	      ;; Some data points may have texts.
  1.2097+	      (when-let ((text (eplot--vs 'text settings)))
  1.2098+		(svg-text svg text
  1.2099+			  :font-family label-font
  1.2100+			  :text-anchor "middle"
  1.2101+			  :font-size label-font-size
  1.2102+			  :font-weight 'normal
  1.2103+			  :fill color
  1.2104+			  :x px 
  1.2105+			  :y (- py (eplot--text-height
  1.2106+				    text label-font label-font-size)
  1.2107+				-5)))
  1.2108+	      ;; You may mark certain points.
  1.2109+	      (when-let ((mark (eplot--vy 'mark settings)))
  1.2110+		(cl-case mark
  1.2111+		  (cross
  1.2112+		   (let ((s (eplot--element-size val plot settings 3)))
  1.2113+		     (svg-line svg (- px s) (- py s)
  1.2114+			       (+ px s) (+ py s)
  1.2115+			       :clip-path clip-id
  1.2116+			       :stroke color)
  1.2117+		     (svg-line svg (+ px s) (- py s)
  1.2118+			       (- px s) (+ py s)
  1.2119+			       :clip-path clip-id
  1.2120+			       :stroke color)))
  1.2121+		  (otherwise
  1.2122+		   (svg-circle svg px py 3
  1.2123+			       :fill color))))
  1.2124+	      (cl-case style
  1.2125+		(bar
  1.2126+		 (if (not gradient)
  1.2127+		     (svg-rectangle
  1.2128+		      svg (+ px bar-gap) py
  1.2129+		      (- stride bar-gap) (- height margin-bottom py)
  1.2130+		      :clip-path clip-id
  1.2131+		      :fill color)
  1.2132+		   (let ((id (format "gradient-%s" (make-temp-name "grad"))))
  1.2133+		     (eplot--gradient svg id 'linear
  1.2134+				      (eplot--stops (eplot--vs 'from gradient)
  1.2135+						    (eplot--vs 'to gradient))
  1.2136+				      (eplot--vs 'direction gradient))
  1.2137+		     (svg-rectangle
  1.2138+		      svg (+ px bar-gap) py
  1.2139+		      (- stride bar-gap) (- height margin-bottom py)
  1.2140+		      :clip-path clip-id
  1.2141+		      :gradient id))))
  1.2142+		(impulse
  1.2143+		 (let ((width (eplot--element-size val plot settings 1)))
  1.2144+		   (if (= width 1)
  1.2145+		       (svg-line svg
  1.2146+				 px py
  1.2147+				 px (- height margin-bottom)
  1.2148+				 :clip-path clip-id
  1.2149+				 :stroke color)
  1.2150+		     (svg-rectangle svg
  1.2151+				    (- px (e/ width 2)) py
  1.2152+				    width (- height py margin-bottom)
  1.2153+				    :clip-path clip-id
  1.2154+				    :fill color))))
  1.2155+		(point
  1.2156+		 (svg-line svg px py (1+ px) (1+ py)
  1.2157+			   :clip-path clip-id
  1.2158+			   :stroke color))
  1.2159+		(line
  1.2160+		 ;; If we're doing a gradient, we're just collecting
  1.2161+		 ;; points and will draw the polygon later.
  1.2162+		 (if gradient
  1.2163+		     (push (cons px py) polygon)
  1.2164+		   (when lpx
  1.2165+		     (svg-line svg lpx lpy px py
  1.2166+			       :stroke-width (eplot--pv plot 'size 1)
  1.2167+			       :clip-path clip-id
  1.2168+			       :stroke color))))
  1.2169+		(curve
  1.2170+		 (push (cons px py) polygon))
  1.2171+		(square
  1.2172+		 (if gradient
  1.2173+		     (progn
  1.2174+		       (when lpx
  1.2175+			 (push (cons lpx py) polygon))
  1.2176+		       (push (cons px py) polygon))
  1.2177+		   (when lpx
  1.2178+		     (svg-line svg lpx lpy px lpy
  1.2179+			       :clip-path clip-id
  1.2180+			       :stroke color)
  1.2181+		     (svg-line svg px lpy px py
  1.2182+			       :clip-path clip-id
  1.2183+			       :stroke color))))
  1.2184+		(circle
  1.2185+		 (svg-circle svg px py
  1.2186+			     (eplot--element-size val plot settings 3)
  1.2187+			     :clip-path clip-id
  1.2188+			     :stroke color
  1.2189+			     :fill (eplot--vary-color
  1.2190+				    (eplot--vs
  1.2191+				     'fill-color settings
  1.2192+				     (or (slot-value plot 'fill-color) "none"))
  1.2193+				    i)))
  1.2194+		(cross
  1.2195+		 (let ((s (eplot--element-size val plot settings 3)))
  1.2196+		   (svg-line svg (- px s) (- py s)
  1.2197+			     (+ px s) (+ py s)
  1.2198+			     :clip-path clip-id
  1.2199+			     :stroke color)
  1.2200+		   (svg-line svg (+ px s) (- py s)
  1.2201+			     (- px s) (+ py s)
  1.2202+			     :clip-path clip-id
  1.2203+			     :stroke color)))
  1.2204+		(triangle
  1.2205+		 (let ((s (eplot--element-size val plot settings 5)))
  1.2206+		   (svg-polygon svg
  1.2207+				(list
  1.2208+				 (cons (- px (e/ s 2)) (+ py (e/ s 2)))
  1.2209+				 (cons px (- py (e/ s 2)))
  1.2210+				 (cons (+ px (e/ s 2)) (+ py (e/ s 2))))
  1.2211+				:clip-path clip-id
  1.2212+				:stroke color
  1.2213+				:fill-color
  1.2214+				(or (slot-value plot 'fill-color) "none"))))
  1.2215+		(rectangle
  1.2216+		 (let ((s (eplot--element-size val plot settings 3)))
  1.2217+		   (svg-rectangle svg (- px (e/ s 2)) (- py (e/ s 2))
  1.2218+				  s s
  1.2219+				  :clip-path clip-id
  1.2220+				  :stroke color
  1.2221+				  :fill-color
  1.2222+				  (or (slot-value plot 'fill-color) "none")))))
  1.2223+	      (setq lpy py
  1.2224+		    lpx px))
  1.2225+
  1.2226+	     ;; We're doing a gradient of some kind (or a curve), so
  1.2227+	     ;; draw it now when we've collected the polygon.
  1.2228+	     (when polygon
  1.2229+	       ;; We have a "between" chart, so collect the data points
  1.2230+	       ;; from the "extra" values, too.
  1.2231+	       (when (memq 'two-values (slot-value plot 'data-format))
  1.2232+		 (cl-loop
  1.2233+		  for val in (nreverse
  1.2234+			      (seq-map (lambda (v) (plist-get v :extra-value))
  1.2235+				       values))
  1.2236+		  for x from (1- (length vals)) downto 0
  1.2237+		  for py = (- (- height margin-bottom)
  1.2238+			      (* (/ (- (* 1.0 val) min) (- max min))
  1.2239+				 ys))
  1.2240+		  for px = (+ margin-left
  1.2241+			      (* (e/ (- x x-min) (- x-max x-min))
  1.2242+				 xs))
  1.2243+		  do
  1.2244+		  (cl-case style
  1.2245+		    (line
  1.2246+		     (push (cons px py) polygon))
  1.2247+		    (square
  1.2248+		     (when lpx
  1.2249+		       (push (cons lpx py) polygon))
  1.2250+		     (push (cons px py) polygon)))
  1.2251+		  (setq lpx px lpy py)))
  1.2252+	       (when gradient
  1.2253+		 (if (eq (eplot--vs 'position gradient) 'above)
  1.2254+		     (push (cons lpx margin-top) polygon)
  1.2255+		   (push (cons lpx (- height margin-bottom)) polygon)))
  1.2256+	       (let ((id (format "gradient-%d" plot-number)))
  1.2257+		 (when gradient
  1.2258+		   (eplot--gradient svg id 'linear
  1.2259+				    (eplot--stops (eplot--vs 'from gradient)
  1.2260+						  (eplot--vs 'to gradient))
  1.2261+				    (eplot--vs 'direction gradient)))
  1.2262+		 (if (eq style 'curve)
  1.2263+		     (apply #'svg-path svg
  1.2264+			    (nconc
  1.2265+			     (cl-loop
  1.2266+			      with points = (cl-coerce
  1.2267+					     (nreverse polygon) 'vector)
  1.2268+			      for i from 0 upto (1- (length points))
  1.2269+			      collect
  1.2270+			      (cond
  1.2271+			       ((zerop i)
  1.2272+				`(moveto ((,(car (elt points 0)) .
  1.2273+					   ,(cdr (elt points 0))))))
  1.2274+			       (t
  1.2275+				`(curveto
  1.2276+				  (,(eplot--bezier
  1.2277+				     (eplot--pv plot 'bezier-factor)
  1.2278+				     i points))))))
  1.2279+			     (and gradient '((closepath))))
  1.2280+			    `( :clip-path ,clip-id
  1.2281+			       :stroke-width ,(eplot--pv plot 'size 1)
  1.2282+			       :stroke ,(slot-value plot 'color)
  1.2283+			       ,@(if gradient
  1.2284+				     `(:gradient ,id)
  1.2285+				   `(:fill "none"))))
  1.2286+		   (svg-polygon
  1.2287+		    svg (nreverse polygon)
  1.2288+		    :clip-path clip-id
  1.2289+		    :gradient id
  1.2290+		    :stroke (slot-value plot 'fill-border-color))))))))
  1.2291+
  1.2292+(defun eplot--element-size (value plot settings default)
  1.2293+  (eplot--vn 'size settings
  1.2294+	     (if (slot-value plot 'size-factor)
  1.2295+		 (* value (slot-value plot 'size-factor))
  1.2296+	       (or (slot-value plot 'size) default))))
  1.2297+
  1.2298+(defun eplot--draw-horizontal-bar-chart (svg chart)
  1.2299+  (with-slots ( plots chart-color height format
  1.2300+		margin-bottom margin-left
  1.2301+		min max xs ys
  1.2302+		margin-top
  1.2303+		x-values x-min x-max
  1.2304+		label-font label-font-size label-color)
  1.2305+      chart
  1.2306+    (cl-loop with plot = (car plots)
  1.2307+	     with values = (slot-value plot 'values)
  1.2308+	     with stride = (e/ ys (length values))
  1.2309+	     with label-height = (eplot--text-height "xx" label-font
  1.2310+						     label-font-size)
  1.2311+	     with bar-gap = (* stride 0.1)
  1.2312+	     for i from 0
  1.2313+	     for value in values
  1.2314+	     for settings = (plist-get value :settings)
  1.2315+	     for py = (+ margin-top (* i stride))
  1.2316+	     for px = (* (e/ (plist-get value :x) x-max) xs)
  1.2317+	     for color = (eplot--vary-color
  1.2318+			  (eplot--vs 'color settings (slot-value plot 'color))
  1.2319+			  i)
  1.2320+	     do
  1.2321+	     (svg-text svg (eplot--vs 'label settings)
  1.2322+		       :font-family label-font
  1.2323+		       :text-anchor "left"
  1.2324+		       :font-size label-font-size
  1.2325+		       :font-weight 'normal
  1.2326+		       :fill label-color
  1.2327+		       :x 5
  1.2328+		       :y (+ py label-height (/ (- stride label-height) 2)))
  1.2329+	     (svg-rectangle svg
  1.2330+			    margin-left (+ py (e/ bar-gap 2))
  1.2331+			    px (- stride bar-gap)
  1.2332+			    :fill color))))
  1.2333+
  1.2334+(defun eplot--stops (from to)
  1.2335+  (append `((0 . ,from))
  1.2336+	  (cl-loop for (pct col) on (split-string to "-") by #'cddr
  1.2337+		   collect (if col
  1.2338+			       (cons (string-to-number pct) col)
  1.2339+			     (cons 100 pct)))))
  1.2340+
  1.2341+(defun eplot--gradient (svg id type stops &optional direction)
  1.2342+  "Add a gradient with ID to SVG.
  1.2343+TYPE is `linear' or `radial'.
  1.2344+
  1.2345+STOPS is a list of percentage/color pairs.
  1.2346+
  1.2347+DIRECTION is one of `top-down', `bottom-up', `left-right' or `right-left'.
  1.2348+nil means `top-down'."
  1.2349+  (svg--def
  1.2350+   svg
  1.2351+   (apply
  1.2352+    #'dom-node
  1.2353+    (if (eq type 'linear)
  1.2354+	'linearGradient
  1.2355+      'radialGradient)
  1.2356+    `((id . ,id)
  1.2357+      (x1 . ,(if (eq direction 'left-right) 1 0))
  1.2358+      (x2 . ,(if (eq direction 'right-left) 1 0))
  1.2359+      (y1 . ,(if (eq direction 'bottom-up) 1 0))
  1.2360+      (y2 . ,(if (eq direction 'top-down) 1 0)))
  1.2361+    (mapcar
  1.2362+     (lambda (stop)
  1.2363+       (dom-node 'stop `((offset . ,(format "%s%%" (car stop)))
  1.2364+			 (stop-color . ,(cdr stop)))))
  1.2365+     stops))))
  1.2366+
  1.2367+(defun e% (num1 num2)
  1.2368+  (let ((factor (max (expt 10 (eplot--decimal-digits num1))
  1.2369+		     (expt 10 (eplot--decimal-digits num2)))))
  1.2370+    (% (truncate (* num1 factor)) (truncate (* num2 factor)))))
  1.2371+
  1.2372+(defun eplot--decimal-digits (number)
  1.2373+  (- (length (replace-regexp-in-string
  1.2374+	      "0+\\'" ""
  1.2375+	      (format "%.10f" (- number (truncate number)))))
  1.2376+     2))
  1.2377+
  1.2378+(defun e/ (&rest numbers)
  1.2379+  (if (cl-every #'integerp numbers)
  1.2380+      (let ((int (apply #'/ numbers))
  1.2381+	    (float (apply #'/ (* 1.0 (car numbers)) (cdr numbers))))
  1.2382+	(if (= int float)
  1.2383+	    int
  1.2384+	  float))
  1.2385+    (apply #'/ numbers)))
  1.2386+
  1.2387+(defun eplot--get-ticks (min max height &optional whole)
  1.2388+  (let* ((diff (abs (- min max)))
  1.2389+	 (even (eplot--pleasing-numbers (* (e/ diff height) 10)))
  1.2390+	 (factor (max (expt 10 (eplot--decimal-digits even))
  1.2391+		      (expt 10 (eplot--decimal-digits diff))))
  1.2392+	 (fmin (truncate (* min factor)))
  1.2393+	 (feven (truncate (* factor even)))
  1.2394+	 start)
  1.2395+    (when whole
  1.2396+      (setq even 1
  1.2397+	    feven factor))
  1.2398+
  1.2399+    (setq start
  1.2400+	  (cond
  1.2401+	   ((< min 0)
  1.2402+	    (+ (floor fmin)
  1.2403+	       feven
  1.2404+	       (- (% (floor fmin) feven))
  1.2405+	       (- feven)))
  1.2406+	   (t
  1.2407+	    (- fmin (% fmin feven)))))
  1.2408+    (cl-loop for x from start upto (* max factor) by feven
  1.2409+	     collect (e/ x factor))))
  1.2410+
  1.2411+(defun eplot--days-to-time (days)
  1.2412+  (days-to-time (- days (time-to-days 0))))
  1.2413+
  1.2414+(defun eplot--get-date-ticks (start end xs label-font label-font-size
  1.2415+				    x-label-format &optional skip-until)
  1.2416+  (let* ((duration (- end start))
  1.2417+	 (limits
  1.2418+	  (list
  1.2419+	   (list (/ 368 16) 'date
  1.2420+		 (lambda (_d) t))
  1.2421+	   (list (/ 368 4) 'date
  1.2422+		 ;; Collect Mondays.
  1.2423+		 (lambda (decoded)
  1.2424+		   (= (decoded-time-weekday decoded) 1)))
  1.2425+	   (list (/ 368 2) 'date
  1.2426+		 ;; Collect 1st and 15th.
  1.2427+		 (lambda (decoded)
  1.2428+		   (or (= (decoded-time-day decoded) 1)
  1.2429+		       (= (decoded-time-day decoded) 15))))
  1.2430+	   (list (* 368 2) 'date
  1.2431+		 ;; Collect 1st of every month.
  1.2432+		 (lambda (decoded)
  1.2433+		   (= (decoded-time-day decoded) 1)))
  1.2434+	   (list (* 368 4) 'date
  1.2435+		 ;; Collect every quarter.
  1.2436+		 (lambda (decoded)
  1.2437+		   (and (= (decoded-time-day decoded) 1)
  1.2438+			(memq (decoded-time-month decoded) '(1 4 7 10)))))
  1.2439+	   (list (* 368 8) 'date
  1.2440+		 ;; Collect every half year.
  1.2441+		 (lambda (decoded)
  1.2442+		   (and (= (decoded-time-day decoded) 1)
  1.2443+			(memq (decoded-time-month decoded) '(1 7)))))
  1.2444+	   (list 1.0e+INF 'year
  1.2445+		 ;; Collect every Jan 1st.
  1.2446+		 (lambda (decoded)
  1.2447+		   (and (= (decoded-time-day decoded) 1)
  1.2448+			(= (decoded-time-month decoded) 1)))))))
  1.2449+    ;; First we collect the potential ticks.
  1.2450+    (while (or (>= duration (caar limits))
  1.2451+	       (and skip-until (>= skip-until (caar limits))))
  1.2452+      (pop limits))
  1.2453+    (let* ((x-ticks (cl-loop for day from start upto end
  1.2454+			     for time = (eplot--days-to-time day)
  1.2455+			     for decoded = (decode-time time)
  1.2456+			     when (funcall (nth 2 (car limits)) decoded)
  1.2457+			     collect day))
  1.2458+	   (count (length x-ticks))
  1.2459+	   (print-format (nth 1 (car limits)))
  1.2460+	   (max-print (eplot--format-value (car x-ticks) print-format
  1.2461+					   x-label-format))
  1.2462+	   (min-spacing (* 1.2 (eplot--text-width max-print label-font
  1.2463+						  label-font-size))))
  1.2464+      (cond
  1.2465+       ;; We have room for every X value.
  1.2466+       ((< (* count min-spacing) xs)
  1.2467+	(list x-ticks print-format))
  1.2468+       ;; We have to prune X labels, but not grid lines.  (We shouldn't
  1.2469+       ;; have a grid line more than every 10 pixels.)
  1.2470+       ((< (* count 10) xs)
  1.2471+	(cond
  1.2472+	 ((not (cdr limits))
  1.2473+	  (eplot--year-ticks
  1.2474+	   x-ticks xs label-font label-font-size x-label-format))
  1.2475+	 ;; The Mondays grid is special, because it doesn't resolve
  1.2476+	 ;; into any of the bigger limits evenly.
  1.2477+	 ((= (caar limits) (/ 368 4))
  1.2478+	  (let* ((max-print (eplot--format-value
  1.2479+			     (car x-ticks) print-format x-label-format))
  1.2480+		 (min-spacing (* 1.2 (eplot--text-width
  1.2481+				      max-print label-font label-font-size)))
  1.2482+		 (weed-factor 2))
  1.2483+	    (while (> (* (/ (length x-ticks) weed-factor) min-spacing) xs)
  1.2484+	      (setq weed-factor (* weed-factor 2)))
  1.2485+	    (list x-ticks 'date
  1.2486+		  (cl-loop for val in x-ticks
  1.2487+			   for i from 0
  1.2488+			   collect (list val t (zerop (% i weed-factor)))))))
  1.2489+	 (t
  1.2490+	  (pop limits)
  1.2491+	  (catch 'found
  1.2492+	    (while limits
  1.2493+	      (let ((candidate
  1.2494+		     (cl-loop for day in x-ticks
  1.2495+			      for time = (eplot--days-to-time day)
  1.2496+			      for decoded = (decode-time time)
  1.2497+			      collect (list day t
  1.2498+					    (not (not
  1.2499+						  (funcall (nth 2 (car limits))
  1.2500+							   decoded)))))))
  1.2501+		(setq print-format (nth 1 (car limits)))
  1.2502+		(let* ((max-print (eplot--format-value
  1.2503+				   (car x-ticks) print-format x-label-format))
  1.2504+		       (min-spacing (* 1.2 (eplot--text-width
  1.2505+					    max-print label-font
  1.2506+					    label-font-size)))
  1.2507+		       (num-labels (seq-count (lambda (v) (nth 2 v))
  1.2508+					      candidate)))
  1.2509+		  (when (and (not (zerop num-labels))
  1.2510+			     (< (* num-labels min-spacing) xs))
  1.2511+		    (throw 'found (list x-ticks print-format candidate)))))
  1.2512+	      (pop limits))
  1.2513+	    (eplot--year-ticks
  1.2514+	     x-ticks xs label-font label-font-size x-label-format)))))
  1.2515+       ;; We have to reduce both grid lines and labels.
  1.2516+       (t
  1.2517+	(eplot--get-date-ticks start end xs label-font label-font-size
  1.2518+			       x-label-format (caar limits)))))))
  1.2519+
  1.2520+(defun eplot--year-ticks (x-ticks xs label-font label-font-size x-label-format)
  1.2521+  (let* ((year-ticks (mapcar (lambda (day)
  1.2522+			       (decoded-time-year
  1.2523+				(decode-time (eplot--days-to-time day))))
  1.2524+			     x-ticks))
  1.2525+	 (xv (eplot--compute-x-ticks
  1.2526+	      xs year-ticks 'year x-label-format label-font label-font-size)))
  1.2527+    (let ((tick-step (car xv))
  1.2528+	  (label-step (cadr xv)))
  1.2529+      (list x-ticks 'year
  1.2530+	    (cl-loop for year in year-ticks
  1.2531+		     for val in x-ticks
  1.2532+		     collect (list val
  1.2533+				   (zerop (% year tick-step))
  1.2534+				   (zerop (% year label-step))))))))
  1.2535+
  1.2536+(defun eplot--get-time-ticks (start end xs label-font label-font-size
  1.2537+				    x-label-format
  1.2538+				    &optional skip-until)
  1.2539+  (let* ((duration (- end start))
  1.2540+	 (limits
  1.2541+	  (list
  1.2542+	   (list (* 2 60) 'time
  1.2543+		 (lambda (_d) t))
  1.2544+	   (list (* 2 60 60) 'time
  1.2545+		 ;; Collect whole minutes.
  1.2546+		 (lambda (decoded)
  1.2547+		   (zerop (decoded-time-second decoded))))
  1.2548+	   (list (* 3 60 60) 'minute
  1.2549+		 ;; Collect five minutes.
  1.2550+		 (lambda (decoded)
  1.2551+		   (zerop (% (decoded-time-minute decoded) 5))))
  1.2552+	   (list (* 4 60 60) 'minute
  1.2553+		 ;; Collect fifteen minutes.
  1.2554+		 (lambda (decoded)
  1.2555+		   (and (zerop (decoded-time-second decoded))
  1.2556+			(memq (decoded-time-minute decoded) '(0 15 30 45)))))
  1.2557+	   (list (* 8 60 60) 'minute
  1.2558+		 ;; Collect half hours.
  1.2559+		 (lambda (decoded)
  1.2560+		   (and (zerop (decoded-time-second decoded))
  1.2561+			(memq (decoded-time-minute decoded) '(0 30)))))
  1.2562+	   (list 1.0e+INF 'hour
  1.2563+		 ;; Collect whole hours.
  1.2564+		 (lambda (decoded)
  1.2565+		   (and (zerop (decoded-time-second decoded))
  1.2566+			(zerop (decoded-time-minute decoded))))))))
  1.2567+    ;; First we collect the potential ticks.
  1.2568+    (while (or (>= duration (caar limits))
  1.2569+	       (and skip-until (>= skip-until (caar limits))))
  1.2570+      (pop limits))
  1.2571+    (let* ((x-ticks (cl-loop for time from start upto end
  1.2572+			     for decoded = (decode-time time)
  1.2573+			     when (funcall (nth 2 (car limits)) decoded)
  1.2574+			     collect time))
  1.2575+	   (count (length x-ticks))
  1.2576+	   (print-format (nth 1 (car limits)))
  1.2577+	   (max-print (eplot--format-value (car x-ticks) print-format
  1.2578+					   x-label-format))
  1.2579+	   (min-spacing (* (+ (length max-print) 2) (e/ label-font-size 2))))
  1.2580+      (cond
  1.2581+       ;; We have room for every X value.
  1.2582+       ((< (* count min-spacing) xs)
  1.2583+	(list x-ticks print-format))
  1.2584+       ;; We have to prune X labels, but not grid lines.  (We shouldn't
  1.2585+       ;; have a grid line more than every 10 pixels.)
  1.2586+       ;; If we're plotting just seconds, then just weed out some seconds.
  1.2587+       ((and (< (* count 10) xs)
  1.2588+	     (= (caar limits) (* 2 60)))
  1.2589+	(let ((xv (eplot--compute-x-ticks
  1.2590+		   xs x-ticks 'time x-label-format label-font label-font-size)))
  1.2591+	  (let ((tick-step (car xv))
  1.2592+		(label-step (cadr xv)))
  1.2593+	    (list x-ticks 'time
  1.2594+		  (cl-loop for val in x-ticks
  1.2595+			   collect (list val
  1.2596+					 (zerop (% val tick-step))
  1.2597+					 (zerop (% val label-step))))))))
  1.2598+       ;; Normal case for pruning labels, but not grid lines.
  1.2599+       ((< (* count 10) xs)
  1.2600+	(if (not (cdr limits))
  1.2601+	    (eplot--hour-ticks x-ticks xs label-font label-font-size
  1.2602+			       x-label-format)
  1.2603+	  (pop limits)
  1.2604+	  (catch 'found
  1.2605+	    (while limits
  1.2606+	      (let ((candidate
  1.2607+		     (cl-loop for val in x-ticks
  1.2608+			      for decoded = (decode-time val)
  1.2609+			      collect (list val t
  1.2610+					    (not (not
  1.2611+						  (funcall (nth 2 (car limits))
  1.2612+							   decoded)))))))
  1.2613+		(setq print-format (nth 1 (car limits)))
  1.2614+		(let ((min-spacing (* (+ (length max-print) 2)
  1.2615+				      (e/ label-font-size 2))))
  1.2616+		  (when (< (* (seq-count (lambda (v) (nth 2 v)) candidate)
  1.2617+			      min-spacing)
  1.2618+			   xs)
  1.2619+		    (throw 'found (list x-ticks print-format candidate)))))
  1.2620+	      (pop limits))
  1.2621+	    (eplot--hour-ticks x-ticks xs label-font label-font-size
  1.2622+			       x-label-format))))
  1.2623+       ;; We have to reduce both grid lines and labels.
  1.2624+       (t
  1.2625+	(eplot--get-time-ticks start end xs label-font label-font-size
  1.2626+			       x-label-format (caar limits)))))))
  1.2627+
  1.2628+(defun eplot--hour-ticks (x-ticks xs label-font label-font-size
  1.2629+				  x-label-format)
  1.2630+  (let* ((eplot--pleasing-numbers '(1 3 6 12))
  1.2631+	 (hour-ticks (mapcar (lambda (time)
  1.2632+			       (decoded-time-hour (decode-time time)))
  1.2633+			     x-ticks))
  1.2634+	 (xv (eplot--compute-x-ticks
  1.2635+	      xs hour-ticks 'year x-label-format label-font label-font-size)))
  1.2636+    (let ((tick-step (car xv))
  1.2637+	  (label-step (cadr xv)))
  1.2638+      (list x-ticks 'hour
  1.2639+	    (cl-loop for hour in hour-ticks
  1.2640+		     for val in x-ticks
  1.2641+		     collect (list val
  1.2642+				   (zerop (% hour tick-step))
  1.2643+				   (zerop (% hour label-step))))))))
  1.2644+
  1.2645+(defun eplot--int (number)
  1.2646+  (cond
  1.2647+   ((integerp number)
  1.2648+    number)
  1.2649+   ((= number (truncate number))
  1.2650+    (truncate number))
  1.2651+   (t
  1.2652+    number)))
  1.2653+
  1.2654+(defun eplot--pleasing-numbers (number)
  1.2655+  (let* ((digits (eplot--decimal-digits number))
  1.2656+	 (one (e/ 1 (expt 10 digits)))
  1.2657+	 (two (e/ 2 (expt 10 digits)))
  1.2658+	 (five (e/ 5 (expt 10 digits))))
  1.2659+    (catch 'found
  1.2660+      (while t
  1.2661+	(when (< number one)
  1.2662+	  (throw 'found one))
  1.2663+	(setq one (* one 10))
  1.2664+	(when (< number two)
  1.2665+	  (throw 'found two))
  1.2666+	(setq two (* two 10))
  1.2667+	(when (< number five)
  1.2668+	  (throw 'found five))
  1.2669+	(setq five (* five 10))))))
  1.2670+
  1.2671+(defun eplot-parse-and-insert (file)
  1.2672+  "Parse and insert a file in the current buffer."
  1.2673+  (interactive "fEplot file: ")
  1.2674+  (let ((default-directory (file-name-directory file)))
  1.2675+    (setq-local eplot--current-chart
  1.2676+		(eplot--render (with-temp-buffer
  1.2677+				 (insert-file-contents file)
  1.2678+				 (eplot--parse-buffer))))))
  1.2679+
  1.2680+(defun eplot-list-chart-headers ()
  1.2681+  "Pop to a buffer showing all chart parameters."
  1.2682+  (interactive)
  1.2683+  (pop-to-buffer "*eplot help*")
  1.2684+  (let ((inhibit-read-only t))
  1.2685+    (special-mode)
  1.2686+    (erase-buffer)
  1.2687+    (insert "The following headers influence the overall\nlook of the chart:\n\n")
  1.2688+    (eplot--list-headers eplot--chart-headers)
  1.2689+    (ensure-empty-lines 2)
  1.2690+    (insert "The following headers are per plot:\n\n")
  1.2691+    (eplot--list-headers eplot--plot-headers)
  1.2692+    (goto-char (point-min))))
  1.2693+
  1.2694+(defun eplot--list-headers (headers)
  1.2695+  (dolist (header (sort (copy-sequence headers)
  1.2696+			(lambda (e1 e2)
  1.2697+			  (string< (car e1) (car e2)))))
  1.2698+    (insert (propertize (capitalize (symbol-name (car header))) 'face 'bold)
  1.2699+	    "\n")
  1.2700+    (let ((start (point)))
  1.2701+      (insert (plist-get (cdr header) :doc) "\n")
  1.2702+      (when-let ((valid (plist-get (cdr header) :valid)))
  1.2703+	(insert "Possible values are: "
  1.2704+		(mapconcat (lambda (v) (format "`%s'" v)) valid ", ")
  1.2705+		".\n"))
  1.2706+      (indent-rigidly start (point) 2))
  1.2707+    (ensure-empty-lines 1)))
  1.2708+
  1.2709+(defvar eplot--transients
  1.2710+  '((("Size"
  1.2711+      ("sw" "Width")
  1.2712+      ("sh" "Height")
  1.2713+      ("sl" "Margin-Left")
  1.2714+      ("st" "Margin-Top")
  1.2715+      ("sr" "Margin-Right")
  1.2716+      ("sb" "Margin-Bottom"))
  1.2717+     ("Colors"
  1.2718+      ("ca" "Axes-Color")
  1.2719+      ("cb" "Border-Color")
  1.2720+      ("cc" "Chart-Color")
  1.2721+      ("cf" "Frame-Color")
  1.2722+      ("cs" "Surround-Color")
  1.2723+      ("ct" "Title-Color"))
  1.2724+     ("Background"
  1.2725+      ("bc" "Background-Color")
  1.2726+      ("bg" "Background-Gradient")
  1.2727+      ("bi" "Background-Image-File")
  1.2728+      ("bv" "Background-Image-Cover")
  1.2729+      ("bo" "Background-Image-Opacity")))
  1.2730+    (("General"
  1.2731+      ("gt" "Title")
  1.2732+      ("gf" "Font")
  1.2733+      ("gs" "Font-Size")
  1.2734+      ("ge" "Font-Weight")
  1.2735+      ("go" "Format")
  1.2736+      ("gw" "Frame-Width")
  1.2737+      ("gh" "Header-File")
  1.2738+      ("gi" "Min")
  1.2739+      ("ga" "Max")
  1.2740+      ("gm" "Mode")
  1.2741+      ("gr" "Reset" eplot--reset-transient)
  1.2742+      ("gv" "Save" eplot--save-transient))
  1.2743+     ("Axes, Grid & Legend"
  1.2744+      ("xx" "X-Title")
  1.2745+      ("xy" "Y-Title")
  1.2746+      ("xf" "Label-Font")
  1.2747+      ("xz" "Label-Font-Size")
  1.2748+      ("xs" "X-Axis-Title-Space")
  1.2749+      ("xl" "X-Label-Format")
  1.2750+      ("xa" "Y-Label-Format")
  1.2751+      ("il" "Grid-Color")
  1.2752+      ("io" "Grid-Opacity")
  1.2753+      ("ip" "Grid-Position")
  1.2754+      ("ll" "Legend")
  1.2755+      ("lb" "Legend-Background-Color")
  1.2756+      ("lo" "Legend-Border-Color")
  1.2757+      ("lc" "Legend-Color"))
  1.2758+     ("Plot"
  1.2759+      ("ps" "Style")
  1.2760+      ("pc" "Color")
  1.2761+      ("po" "Data-Column")
  1.2762+      ("pr" "Data-format")
  1.2763+      ("pn" "Fill-Border-Color")
  1.2764+      ("pi" "Fill-Color")
  1.2765+      ("pg" "Gradient")
  1.2766+      ("pz" "Size")
  1.2767+      ("pm" "Smoothing")
  1.2768+      ("pb" "Bezier-Factor")))))
  1.2769+
  1.2770+(defun eplot--define-transients ()
  1.2771+  (cl-loop for row in eplot--transients
  1.2772+	   collect (cl-coerce
  1.2773+		    (cl-loop for column in row
  1.2774+			     collect
  1.2775+			     (cl-coerce
  1.2776+			      (cons (pop column)
  1.2777+				    (mapcar #'eplot--define-transient column))
  1.2778+			      'vector))
  1.2779+		    'vector)))
  1.2780+
  1.2781+(defun eplot--define-transient (action)
  1.2782+  (list (nth 0 action)
  1.2783+	(nth 1 action)
  1.2784+	;; Allow explicit commands.
  1.2785+	(or (nth 2 action)
  1.2786+	    ;; Make a command for altering a setting.
  1.2787+	    (lambda ()
  1.2788+	      (interactive)
  1.2789+	      (eplot--execute-transient (nth 1 action))))))
  1.2790+
  1.2791+(defun eplot--execute-transient (action)
  1.2792+  (with-current-buffer (or eplot--data-buffer (current-buffer))
  1.2793+    (unless eplot--transient-settings
  1.2794+      (setq-local eplot--transient-settings nil))
  1.2795+    (let* ((name (intern (downcase action)))
  1.2796+	   (spec (assq name (append eplot--chart-headers eplot--plot-headers)))
  1.2797+	   (type (plist-get (cdr spec) :type)))
  1.2798+      ;; Sanity check.
  1.2799+      (unless spec
  1.2800+	(error "No such header type: %s" name))
  1.2801+      (setq eplot--transient-settings
  1.2802+	    (append
  1.2803+	     eplot--transient-settings
  1.2804+	     (list
  1.2805+	      (cons
  1.2806+	       name
  1.2807+	       (cond
  1.2808+		((eq type 'number)
  1.2809+		 (read-number (format "Value for %s (%s): " action type)))
  1.2810+		((string-match "color" (downcase action))
  1.2811+		 (eplot--read-color (format "Value for %s (color): " action)))
  1.2812+		((string-match "font" (downcase action))
  1.2813+		 (eplot--read-font-family
  1.2814+		  (format "Value for %s (font family): " action)))
  1.2815+		((string-match "gradient" (downcase action))
  1.2816+		 (eplot--read-gradient action))
  1.2817+		((string-match "file" (downcase action))
  1.2818+		 (read-file-name (format "File for %s: " action)))
  1.2819+		((eq type 'symbol)
  1.2820+		 (intern
  1.2821+		  (completing-read (format "Value for %s: " action)
  1.2822+				   (plist-get (cdr spec) :valid)
  1.2823+				   nil t)))
  1.2824+		(t
  1.2825+		 (read-string (format "Value for %s (string): " action))))))))
  1.2826+      (eplot-update-view-buffer))))
  1.2827+
  1.2828+(defun eplot--read-gradient (action)
  1.2829+  (format "%s %s %s %s"
  1.2830+	  (eplot--read-color (format "%s from color: " action))
  1.2831+	  (eplot--read-color (format "%s to color: " action))
  1.2832+	  (completing-read (format "%s direction: " action)
  1.2833+			   '(top-down bottom-up left-right right-left)
  1.2834+			   nil t)
  1.2835+	  (completing-read (format "%s position: " action)
  1.2836+			   '(below above)
  1.2837+			   nil t)))
  1.2838+
  1.2839+(defun eplot--reset-transient ()
  1.2840+  (interactive)
  1.2841+  (with-current-buffer (or eplot--data-buffer (current-buffer))
  1.2842+    (setq-local eplot--transient-settings nil)
  1.2843+    (eplot-update-view-buffer)))
  1.2844+
  1.2845+(defun eplot--save-transient (file)
  1.2846+  (interactive "FSave parameters to file: ")
  1.2847+  (when (and (file-exists-p file)
  1.2848+	     (not (yes-or-no-p "File exists; overwrite? ")))
  1.2849+    (user-error "Exiting"))
  1.2850+  (let ((settings (with-current-buffer (or eplot--data-buffer (current-buffer))
  1.2851+		    eplot--transient-settings)))
  1.2852+    (with-temp-buffer
  1.2853+      (cl-loop for (name . value) in settings
  1.2854+	       do (insert (capitalize (symbol-name name)) ": "
  1.2855+			  (format "%s" value) "\n"))
  1.2856+      (write-region (point-min) (point-max) file))))
  1.2857+
  1.2858+(defvar-keymap eplot-control-mode-map
  1.2859+  "RET" #'eplot-control-update
  1.2860+  "TAB" #'eplot-control-next-field
  1.2861+  "C-<tab>" #'eplot-control-next-field
  1.2862+  "<backtab>" #'eplot-control-prev-field)
  1.2863+
  1.2864+(define-derived-mode eplot-control-mode special-mode "eplot control"
  1.2865+  (setq-local completion-at-point-functions
  1.2866+	      (cons 'eplot--complete-control completion-at-point-functions))
  1.2867+  (add-hook 'before-change-functions #'eplot--process-text-input-before nil t)
  1.2868+  (add-hook 'after-change-functions #'eplot--process-text-value nil t)
  1.2869+  (add-hook 'after-change-functions #'eplot--process-text-input nil t)
  1.2870+  (setq-local nobreak-char-display nil)
  1.2871+  (setq truncate-lines t))
  1.2872+
  1.2873+(defun eplot--complete-control ()
  1.2874+  ;; Complete headers names.
  1.2875+  (when-let* ((input (get-text-property (point) 'input))
  1.2876+	      (name (plist-get input :name))
  1.2877+	      (spec (cdr (assq name (append eplot--plot-headers
  1.2878+					    eplot--chart-headers))))
  1.2879+	      (start (plist-get input :start))
  1.2880+	      (end (- (plist-get input :end) 2))
  1.2881+	      (completion-ignore-case t))
  1.2882+    (skip-chars-backward " " start)
  1.2883+    (or
  1.2884+     (and (eq (plist-get spec :type) 'symbol)
  1.2885+	  (lambda ()
  1.2886+	    (let ((valid (plist-get spec :valid)))
  1.2887+	      (completion-in-region
  1.2888+	       (save-excursion
  1.2889+		 (skip-chars-backward "^ " start)
  1.2890+		 (point))
  1.2891+	       end
  1.2892+	       (mapcar #'symbol-name valid))
  1.2893+	      'completion-attempted)))
  1.2894+     (and (string-match "color" (symbol-name name))
  1.2895+	  (lambda ()
  1.2896+	    (completion-in-region
  1.2897+	     (save-excursion
  1.2898+	       (skip-chars-backward "^ " start)
  1.2899+	       (point))
  1.2900+	     end eplot--colors)
  1.2901+	    'completion-attempted))
  1.2902+     (and (string-match "\\bfile\\b" (symbol-name name))
  1.2903+	  (lambda ()
  1.2904+	    (completion-in-region
  1.2905+	     (save-excursion
  1.2906+	       (skip-chars-backward "^ " start)
  1.2907+	       (point))
  1.2908+	     end (directory-files "."))
  1.2909+	    'completion-attempted))
  1.2910+     (and (string-match "\\bfont\\b" (symbol-name name))
  1.2911+	  (lambda ()
  1.2912+	    (completion-in-region
  1.2913+	     (save-excursion
  1.2914+	       (skip-chars-backward "^ " start)
  1.2915+	       (point))
  1.2916+	     end
  1.2917+	     (eplot--font-families))
  1.2918+	    'completion-attempted)))))
  1.2919+
  1.2920+(defun eplot--read-font-family (prompt)
  1.2921+  "Prompt for a font family, possibly offering autocomplete."
  1.2922+  (let ((families (eplot--font-families)))
  1.2923+    (if families
  1.2924+	(completing-read prompt families)
  1.2925+      (read-string prompt))))
  1.2926+
  1.2927+(defun eplot--font-families ()
  1.2928+  (when (executable-find "fc-list")
  1.2929+    (let ((fonts nil))
  1.2930+      (with-temp-buffer
  1.2931+	(call-process "fc-list" nil t nil ":" "family")
  1.2932+	(goto-char (point-min))
  1.2933+	(while (re-search-forward "^\\([^,\n]+\\)" nil t)
  1.2934+	  (push (downcase (match-string 1)) fonts)))
  1.2935+      (seq-uniq (sort fonts #'string<)))))
  1.2936+
  1.2937+(defun eplot-control-next-input ()
  1.2938+  "Go to the next input field."
  1.2939+  (interactive)
  1.2940+  (when-let ((match (text-property-search-forward 'input)))
  1.2941+    (goto-char (prop-match-beginning match))))
  1.2942+
  1.2943+(defun eplot-control-update ()
  1.2944+  "Update the chart based on the current settings."
  1.2945+  (interactive)
  1.2946+  (let ((settings nil))
  1.2947+    (save-excursion
  1.2948+      (goto-char (point-min))
  1.2949+      (while-let ((match (text-property-search-forward 'input)))
  1.2950+	(when (equal (get-text-property (prop-match-beginning match) 'face)
  1.2951+		     'eplot--input-changed)
  1.2952+	  (let* ((name (plist-get (prop-match-value match) :name))
  1.2953+		 (spec (cdr (assq name (append eplot--plot-headers
  1.2954+					       eplot--chart-headers))))
  1.2955+		 (value
  1.2956+		  (or (plist-get (prop-match-value match) :value)
  1.2957+		      (plist-get (prop-match-value match) :original-value))))
  1.2958+	    (setq value (string-trim (string-replace "\u00A0" " " value)))
  1.2959+	    (push (cons name
  1.2960+			(cl-case (plist-get spec :type)
  1.2961+			  (number
  1.2962+			   (string-to-number value))
  1.2963+			  (symbol
  1.2964+			   (intern (downcase value)))
  1.2965+			  (symbol-list
  1.2966+			   (mapcar #'intern (split-string (downcase value))))
  1.2967+			  (t
  1.2968+			   value)))
  1.2969+		  settings)))))
  1.2970+    (with-current-buffer eplot--data-buffer
  1.2971+      (setq-local eplot--transient-settings (nreverse settings))
  1.2972+      (eplot-update-view-buffer))))
  1.2973+
  1.2974+(defvar eplot--column-width nil)
  1.2975+
  1.2976+(defun eplot-create-controls ()
  1.2977+  "Pop to a buffer that lists all parameters and allows editing."
  1.2978+  (interactive)
  1.2979+  (with-current-buffer (or eplot--data-buffer (current-buffer))
  1.2980+    (let ((settings eplot--transient-settings)
  1.2981+	  (data-buffer (current-buffer))
  1.2982+	  (chart eplot--current-chart)
  1.2983+	  ;; Find the max width of all the different names.
  1.2984+	  (width (seq-max
  1.2985+		  (mapcar (lambda (e)
  1.2986+			    (length (cadr e)))
  1.2987+			  (apply #'append
  1.2988+				 (mapcar #'cdr
  1.2989+					 (apply #'append eplot--transients))))))
  1.2990+	  (transients (mapcar #'copy-sequence
  1.2991+			      (copy-sequence eplot--transients))))
  1.2992+      (unless chart
  1.2993+	(user-error "Must be called from an eplot buffer that has rendered a chart"))
  1.2994+      ;; Rearrange the transients a bit for better display.
  1.2995+      (let ((size (caar transients)))
  1.2996+	(setcar (car transients) (caadr transients))
  1.2997+	(setcar (cadr transients) size))
  1.2998+      (pop-to-buffer "*eplot controls*")
  1.2999+      (unless (eq major-mode 'eplot-control-mode)
  1.3000+	(eplot-control-mode))
  1.3001+      (setq-local eplot--data-buffer data-buffer
  1.3002+		  eplot--column-width (+ width 12 2))
  1.3003+      (let ((inhibit-read-only t)
  1.3004+	    (before-change-functions nil)
  1.3005+	    (after-change-functions nil))
  1.3006+	(erase-buffer)
  1.3007+	(cl-loop for column in transients
  1.3008+		 for cn from 0
  1.3009+		 do
  1.3010+		 (goto-char (point-min))
  1.3011+		 (end-of-line)
  1.3012+		 (cl-loop
  1.3013+		  for row in column
  1.3014+		  do
  1.3015+		  (if (zerop cn)
  1.3016+		      (when (not (bobp))
  1.3017+			(insert (format (format "%%-%ds" (+ width 14)) "")
  1.3018+				"\n"))
  1.3019+		    (unless (= (count-lines (point-min) (point)) 1)
  1.3020+		      (if (eobp)
  1.3021+			  (progn
  1.3022+			    (insert (format (format "%%-%ds" (+ width 14)) "")
  1.3023+				    "\n")
  1.3024+			    (insert (format (format "%%-%ds" (+ width 14)) "")
  1.3025+				    "\n")
  1.3026+			    (forward-line -1)
  1.3027+			    (end-of-line))
  1.3028+			(forward-line 1)
  1.3029+			(end-of-line))))
  1.3030+		  ;; If we have a too-long input in the first column,
  1.3031+		  ;; then go to the next line.
  1.3032+		  (when (and (> cn 0)
  1.3033+			     (> (- (point) (pos-bol))
  1.3034+				(+ width 12 2)))
  1.3035+		    (forward-line 1)
  1.3036+		    (end-of-line))
  1.3037+		  (insert (format (format "%%-%ds" (+ width 14))
  1.3038+				  (propertize (pop row) 'face 'bold)))
  1.3039+		  (if (looking-at "\n")
  1.3040+		      (forward-line 1)				
  1.3041+		    (insert "\n"))
  1.3042+		  (cl-loop
  1.3043+		   for elem in row
  1.3044+		   for name = (cadr elem)
  1.3045+		   for slot = (intern (downcase name))
  1.3046+		   when (null (nth 2 elem))
  1.3047+		   do
  1.3048+		   (let* ((object (if (assq slot eplot--chart-headers)
  1.3049+				      chart
  1.3050+				    (car (slot-value chart 'plots))))
  1.3051+			  (value (format "%s"
  1.3052+					 (or (cdr (assq slot settings))
  1.3053+					     (if (not (slot-boundp object slot))
  1.3054+						 ""
  1.3055+					       (or (slot-value object slot)
  1.3056+						   ""))))))
  1.3057+		     (end-of-line)
  1.3058+		     ;; If we have a too-long input in the first column,
  1.3059+		     ;; then go to the next line.
  1.3060+		     (when (and (> cn 0)
  1.3061+				(> (- (point) (pos-bol))
  1.3062+				   (+ width 12 2)))
  1.3063+		       (forward-line 1)
  1.3064+		       (end-of-line))
  1.3065+		     (when (and (> cn 0)
  1.3066+				(bolp))
  1.3067+		       (insert (format (format "%%-%ds" (+ width 14)) "") "\n")
  1.3068+		       (forward-line -1)
  1.3069+		       (end-of-line))
  1.3070+		     (insert (format (format "%%-%ds" (1+ width)) name))
  1.3071+		     (eplot--input slot value
  1.3072+				   (if (cdr (assq slot settings))
  1.3073+				       'eplot--input-changed
  1.3074+				     'eplot--input-default))
  1.3075+		     (if (looking-at "\n")
  1.3076+			 (forward-line 1)				
  1.3077+		       (insert "\n")))))))
  1.3078+      (goto-char (point-min)))))
  1.3079+
  1.3080+(defface eplot--input-default
  1.3081+  '((t :background "#505050"
  1.3082+       :foreground "#a0a0a0"
  1.3083+       :box (:line-width 1)))
  1.3084+  "Face for eplot default inputs.")
  1.3085+
  1.3086+(defface eplot--input-changed
  1.3087+  '((t :background "#505050"
  1.3088+       :foreground "white"
  1.3089+       :box (:line-width 1)))
  1.3090+  "Face for eplot changed inputs.")
  1.3091+
  1.3092+(defvar-keymap eplot--input-map
  1.3093+  :full t :parent text-mode-map
  1.3094+  "RET" #'eplot-control-update
  1.3095+  "TAB" #'eplot-input-complete
  1.3096+  "C-a" #'eplot-move-beginning-of-input
  1.3097+  "C-e" #'eplot-move-end-of-input
  1.3098+  "C-k" #'eplot-kill-input
  1.3099+  "C-<tab>" #'eplot-control-next-field
  1.3100+  "<backtab>" #'eplot-control-prev-field)
  1.3101+
  1.3102+(defun eplot-input-complete ()
  1.3103+  "Complete values in inputs."
  1.3104+  (interactive)
  1.3105+  (cond
  1.3106+   ((let ((completion-fail-discreetly t))
  1.3107+      (completion-at-point))
  1.3108+    ;; Completion was performed; nothing else to do.
  1.3109+    nil)
  1.3110+   ((not (get-text-property (point) 'input))
  1.3111+    (eplot-control-next-input))
  1.3112+   (t
  1.3113+    (user-error "No completion in this field"))))
  1.3114+
  1.3115+(defun eplot-move-beginning-of-input ()
  1.3116+  "Move to the start of the current input field."
  1.3117+  (interactive)
  1.3118+  (if (= (point) (eplot--beginning-of-field))
  1.3119+      (goto-char (pos-bol))
  1.3120+    (goto-char (eplot--beginning-of-field))))
  1.3121+  
  1.3122+(defun eplot-move-end-of-input ()
  1.3123+  "Move to the end of the current input field."
  1.3124+  (interactive)
  1.3125+  (let ((input (get-text-property (point) 'input)))
  1.3126+    (if (or (not input)
  1.3127+	    (= (point) (1- (plist-get input :end))))
  1.3128+	(goto-char (pos-eol))
  1.3129+      (goto-char (1+ (eplot--end-of-field))))))
  1.3130+
  1.3131+(defun eplot-control-next-field ()
  1.3132+  "Move to the beginning of the next field."
  1.3133+  (interactive)
  1.3134+  (let ((input (get-text-property (point) 'input))
  1.3135+	(start (point)))
  1.3136+    (when input
  1.3137+      (goto-char (plist-get input :end)))
  1.3138+    (let ((match (text-property-search-forward 'input)))
  1.3139+      (if match
  1.3140+	  (goto-char (prop-match-beginning match))
  1.3141+	(goto-char start)
  1.3142+	(user-error "No next field")))))
  1.3143+
  1.3144+(defun eplot-control-prev-field ()
  1.3145+  "Move to the beginning of the previous field."
  1.3146+  (interactive)
  1.3147+  (let ((input (get-text-property (point) 'input))
  1.3148+	(start (point)))
  1.3149+    (when input
  1.3150+      (goto-char (plist-get input :start))
  1.3151+      (unless (bobp)
  1.3152+	(forward-char -1)))
  1.3153+    (let ((match (text-property-search-backward 'input)))
  1.3154+      (unless match
  1.3155+	(goto-char start)
  1.3156+	(user-error "No previous field")))))
  1.3157+
  1.3158+(defun eplot-kill-input ()
  1.3159+  "Remove the part of the input after point."
  1.3160+  (interactive)
  1.3161+  (let ((end (1+ (eplot--end-of-field))))
  1.3162+    (kill-new (string-trim (buffer-substring (point) end)))
  1.3163+    (delete-region (point) end)))
  1.3164+
  1.3165+(defun eplot--input (name value face)
  1.3166+  (let ((start (point))
  1.3167+	input)
  1.3168+    (insert value)
  1.3169+    (when (< (length value) 11)
  1.3170+      (insert (make-string (- 11 (length value)) ?\u00A0)))
  1.3171+    (put-text-property start (point) 'face face)
  1.3172+    (put-text-property start (point) 'inhibit-read-only t)
  1.3173+    (put-text-property start (point) 'input
  1.3174+		       (setq input
  1.3175+			     (list :name name
  1.3176+				   :size 11
  1.3177+				   :is-default (eq face 'eplot--input-default)
  1.3178+				   :original-value value
  1.3179+				   :original-face face
  1.3180+				   :start (set-marker (make-marker) start)
  1.3181+				   :value value)))
  1.3182+    (put-text-property start (point) 'local-map eplot--input-map)
  1.3183+    ;; This seems like a NOOP, but redoing the properties like this
  1.3184+    ;; somehow makes `delete-region' work better.
  1.3185+    (set-text-properties start (point) (text-properties-at start))
  1.3186+    (insert (propertize " " 'face face
  1.3187+			'input input
  1.3188+			'inhibit-read-only t
  1.3189+			'local-map eplot--input-map))
  1.3190+    (plist-put input :end (point-marker))
  1.3191+    (insert " ")))
  1.3192+
  1.3193+(defun eplot--end-of-field ()
  1.3194+  (- (plist-get (get-text-property (point) 'input) :end) 2))
  1.3195+
  1.3196+(defun eplot--beginning-of-field ()
  1.3197+  (plist-get (get-text-property (point) 'input) :start))
  1.3198+
  1.3199+(defvar eplot--prev-deletion nil)
  1.3200+
  1.3201+(defun eplot--process-text-input-before (beg end)
  1.3202+  (message "Before: %s %s" beg end)
  1.3203+  (cond
  1.3204+   ((= beg end)
  1.3205+    (setq eplot--prev-deletion nil))
  1.3206+   ((> end beg)
  1.3207+    (setq eplot--prev-deletion (buffer-substring beg end)))))
  1.3208+
  1.3209+(defun eplot--process-text-input (beg end _replace-length)
  1.3210+  ;;(message "After: %s %s %s %s" beg end replace-length eplot--prev-deletion)
  1.3211+  (when-let ((props (if eplot--prev-deletion
  1.3212+ 			(text-properties-at 0 eplot--prev-deletion)
  1.3213+ 		      (if (get-text-property end 'input)
  1.3214+ 			  (text-properties-at end)
  1.3215+ 			(text-properties-at beg))))
  1.3216+ 	     (input (plist-get props 'input)))
  1.3217+    ;; The action concerns something in the input field.
  1.3218+    (let ((buffer-undo-list t)
  1.3219+	  (inhibit-read-only t)
  1.3220+	  (size (plist-get input :size)))
  1.3221+      (save-excursion
  1.3222+	(set-text-properties beg (- (plist-get input :end) 2) props)
  1.3223+	(goto-char (1- (plist-get input :end)))
  1.3224+	(let* ((remains (- (point) (plist-get input :start) 1))
  1.3225+	       (trim (- size remains 1)))
  1.3226+	  (if (< remains size)
  1.3227+	      ;; We need to add some padding.
  1.3228+	      (insert (apply #'propertize (make-string trim ?\u00A0)
  1.3229+			     props))
  1.3230+	    ;; We need to delete some padding, but only delete
  1.3231+	    ;; spaces at the end.
  1.3232+	    (setq trim (abs trim))
  1.3233+	    (while (and (> trim 0)
  1.3234+			(eql (char-after (1- (point))) ?\u00A0))
  1.3235+	      (delete-region (1- (point)) (point))
  1.3236+	      (cl-decf trim))
  1.3237+	    (when (> trim 0)
  1.3238+	      (eplot--possibly-open-column)))))
  1.3239+      ;; We re-set the properties so that they are continguous.  This
  1.3240+      ;; somehow makes the machinery that decides whether we can kill
  1.3241+      ;; a word work better.
  1.3242+      (set-text-properties (plist-get input :start)
  1.3243+			   (1- (plist-get input :end)) props)
  1.3244+      ;; Compute what the value is now.
  1.3245+      (let ((value (buffer-substring-no-properties
  1.3246+		    (plist-get input :start)
  1.3247+		    (plist-get input :end))))
  1.3248+	(when (string-match "\u00A0+\\'" value)
  1.3249+	  (setq value (substring value 0 (match-beginning 0))))
  1.3250+	(plist-put input :value value)))))
  1.3251+
  1.3252+(defun eplot--possibly-open-column ()
  1.3253+  (save-excursion
  1.3254+    (when-let ((input (get-text-property (point) 'input)))
  1.3255+      (goto-char (plist-get input :end)))
  1.3256+    (unless (looking-at " *\n")
  1.3257+      (skip-chars-forward " ")
  1.3258+      (while (not (eobp))
  1.3259+	(let ((text (buffer-substring (point) (pos-eol))))
  1.3260+	  (delete-region (point) (pos-eol))
  1.3261+	  (forward-line 1)
  1.3262+	  (if (eobp)
  1.3263+	      (insert (make-string eplot--column-width ?\s) text "\n")
  1.3264+	    (forward-char eplot--column-width)
  1.3265+	    (if (get-text-property (point) 'input)
  1.3266+		(forward-line 1)
  1.3267+	      (insert text)
  1.3268+	      ;; We have to fix up the markers.
  1.3269+	      (save-excursion
  1.3270+		(let* ((match (text-property-search-backward 'input))
  1.3271+		       (input (prop-match-value match)))
  1.3272+		  (plist-put input :start
  1.3273+			     (set-marker (plist-get input :start)
  1.3274+					 (prop-match-beginning match)))
  1.3275+		  (plist-put input :end
  1.3276+			     (set-marker (plist-get input :end)
  1.3277+					 (+ (prop-match-end match) 1))))))))))))
  1.3278+
  1.3279+(defun eplot--process-text-value (beg _end _replace-length)
  1.3280+  (when-let* ((input (get-text-property beg 'input)))
  1.3281+    (let ((inhibit-read-only t))
  1.3282+      (when (plist-get input :is-default)
  1.3283+	(put-text-property (plist-get input :start)
  1.3284+			   (plist-get input :end)
  1.3285+			   'face
  1.3286+			   (if (equal (plist-get input :original-value)
  1.3287+				      (plist-get input :value))
  1.3288+			       'eplot--input-default
  1.3289+			     'eplot--input-changed))))))
  1.3290+
  1.3291+(defun eplot--read-color (prompt)
  1.3292+  "Read an SVG color."
  1.3293+  (completing-read prompt eplot--colors))
  1.3294+
  1.3295+(eval `(transient-define-prefix eplot-customize ()
  1.3296+	 "Customize Chart"
  1.3297+	 ,@(eplot--define-transients)))
  1.3298+
  1.3299+(defun eplot--bezier (factor i points)
  1.3300+  (cl-labels ((padd (p1 p2)
  1.3301+		(cons (+ (car p1) (car p2)) (+ (cdr p1) (cdr p2))))
  1.3302+	      (psub (p1 p2)
  1.3303+		(cons (- (car p1) (car p2)) (- (cdr p1) (cdr p2))))
  1.3304+	      (pscale (factor point)
  1.3305+		(cons (* factor (car point)) (* factor (cdr point)))))
  1.3306+    (let* ((start (elt points (1- i)))
  1.3307+	   (end (elt points i))
  1.3308+	   (prev (if (< (- i 2) 0)
  1.3309+		     start
  1.3310+		   (elt points (- i 2))))
  1.3311+	   (next (if (> (1+ i) (1- (length points)))
  1.3312+		     end
  1.3313+		   (elt points (1+ i))))
  1.3314+	   (start-control-point
  1.3315+	    (padd start (pscale factor (psub end prev))))
  1.3316+	   (end-control-point
  1.3317+	    (padd end (pscale factor (psub start next)))))
  1.3318+      (list (car start-control-point)
  1.3319+	    (cdr start-control-point)
  1.3320+	    (car end-control-point)
  1.3321+	    (cdr end-control-point)
  1.3322+	    (car end)
  1.3323+	    (cdr end)))))
  1.3324+
  1.3325+;;; CSV Parsing Stuff.
  1.3326+
  1.3327+(defun eplot--csv-buffer-p ()
  1.3328+  (save-excursion
  1.3329+    (goto-char (point-min))
  1.3330+    (let ((min 1.0e+INF)
  1.3331+	  (max -1.0e+INF)
  1.3332+	  (total 0)
  1.3333+	  (lines 0))
  1.3334+      (while (not (eobp))
  1.3335+	(let ((this 0))
  1.3336+	  (while (search-forward "," (pos-eol) t)
  1.3337+	    (cl-incf total)
  1.3338+	    (cl-incf this))
  1.3339+	  (forward-line 1)
  1.3340+	  (cl-incf lines)
  1.3341+	  (setq min (min min this)
  1.3342+		max (max max this))))
  1.3343+      (let ((mid (e/ total lines)))
  1.3344+	;; If we have a comma on each line, and it's fairly evenly
  1.3345+	;; distributed, it's a CSV buffer.
  1.3346+	(and (>= min 1)
  1.3347+	     (< (* mid 0.9) min)
  1.3348+	     (> (* mid 1.1) max))))))
  1.3349+
  1.3350+(defun eplot--numericalp (value)
  1.3351+  (string-match-p "\\`[-.0-9]*\\'" value))
  1.3352+
  1.3353+(defun eplot--numberish (value)
  1.3354+  (if (or (zerop (length value))
  1.3355+	  (not (eplot--numericalp value)))
  1.3356+      value
  1.3357+    (string-to-number value)))
  1.3358+
  1.3359+(defun eplot--parse-csv-buffer ()
  1.3360+  (unless (fboundp 'pcsv-parse-buffer)
  1.3361+    (user-error "You need to install the pcsv package to parse CSV files"))
  1.3362+  (let ((csv (and (fboundp 'pcsv-parse-buffer)
  1.3363+		  ;; This repeated check is just to silence the byte
  1.3364+		  ;; compiler.
  1.3365+		  (pcsv-parse-buffer)))
  1.3366+	names)
  1.3367+    ;; Check whether the first line looks like a header.
  1.3368+    (when (and (length> csv 1)
  1.3369+	       ;; The second line is all numbers...
  1.3370+	       (cl-every #'eplot--numericalp (nth 1 csv))
  1.3371+	       ;; .. and the first line isn't.
  1.3372+	       (not (cl-every #'eplot--numericalp (nth 0 csv))))
  1.3373+      (setq names (pop csv)))
  1.3374+    (list
  1.3375+     (cons 'legend (and names "true"))
  1.3376+     (cons :plots
  1.3377+	   (cl-loop
  1.3378+	    for column from 1 upto (1- (length (car csv)))
  1.3379+	    collect
  1.3380+	    (list (cons :headers
  1.3381+			(list
  1.3382+			 (cons 'name (elt names column))
  1.3383+			 (cons 'data-format
  1.3384+			       (cond
  1.3385+				((cl-every (lambda (e) (<= (length e) 4))
  1.3386+					   (mapcar #'car csv))
  1.3387+				 "year")
  1.3388+				((cl-every (lambda (e) (= (length e) 8))
  1.3389+					   (mapcar #'car csv))
  1.3390+				 "date")
  1.3391+				(t
  1.3392+				 "number")))
  1.3393+			 (cons 'color (eplot--vary-color "vary" (1- column)))))
  1.3394+		  (cons
  1.3395+		   :values
  1.3396+		   (cl-loop for line in csv
  1.3397+			    collect (list :x (eplot--numberish (car line))
  1.3398+					  :value (eplot--numberish
  1.3399+						  (elt line column)))))))))))
  1.3400+
  1.3401+(declare-function org-element-parse-buffer "org-element")
  1.3402+
  1.3403+(defun eplot--parse-org-buffer ()
  1.3404+  (require 'org-element)
  1.3405+  (let* ((table (nth 2 (nth 2 (org-element-parse-buffer))))
  1.3406+	 (columns (cl-loop for cell in (nthcdr 2 (nth 2 table))
  1.3407+			   collect (substring-no-properties (nth 2 cell))))
  1.3408+	 (value-column (or (seq-position columns "value") 0))
  1.3409+	 (date-column (seq-position columns "date")))
  1.3410+    `((:plots
  1.3411+       ((:headers
  1.3412+	 ,@(and date-column '((data-format . "date"))))
  1.3413+	(:values 
  1.3414+	 ,@(cl-loop for row in (nthcdr 4 table)
  1.3415+		    collect
  1.3416+		    (let ((cells (cl-loop for cell in (nthcdr 2 row)
  1.3417+					  collect (substring-no-properties
  1.3418+						   (nth 2 cell)))))
  1.3419+		      (list :value (string-to-number (elt cells value-column))
  1.3420+			    :x (string-to-number
  1.3421+				(replace-regexp-in-string
  1.3422+				 "[^0-9]" "" (elt cells date-column)))
  1.3423+			    )))))))))
  1.3424+
  1.3425+(provide 'eplot)
  1.3426+
  1.3427+;;; eplot.el ends here