2021-05-29 07:40:06 +00:00
|
|
|
#+TITLE: chronometrist-spark
|
2021-06-03 08:32:22 +00:00
|
|
|
#+AUTHOR: contrapunctus
|
2021-05-30 19:31:25 +00:00
|
|
|
#+SUBTITLE: Show sparklines in Chronometrist
|
2021-06-01 13:07:29 +00:00
|
|
|
#+PROPERTY: header-args :tangle yes :load yes
|
2021-05-29 07:40:06 +00:00
|
|
|
|
2021-06-02 19:58:17 +00:00
|
|
|
* Library headers and commentary
|
2021-05-29 07:40:06 +00:00
|
|
|
#+BEGIN_SRC emacs-lisp
|
2022-01-23 05:32:55 +00:00
|
|
|
;;; chronometrist-spark.el --- Show sparklines in Chronometrist buffers -*- lexical-binding: t; -*-
|
2021-05-29 07:40:06 +00:00
|
|
|
|
2021-06-01 13:07:29 +00:00
|
|
|
;; Author: contrapunctus <xmpp:contrapunctus@jabjab.de>
|
|
|
|
;; Maintainer: contrapunctus <xmpp:contrapunctus@jabjab.de>
|
2021-05-29 07:40:06 +00:00
|
|
|
;; Keywords: calendar
|
|
|
|
;; Homepage: https://tildegit.org/contrapunctus/chronometrist
|
2022-01-23 07:39:57 +00:00
|
|
|
;; Package-Requires: ((emacs "25.1") (chronometrist "0.7.0") (spark "0.1"))
|
2021-05-29 07:40:06 +00:00
|
|
|
;; Version: 0.1.0
|
|
|
|
|
|
|
|
;; This is free and unencumbered software released into the public domain.
|
|
|
|
;;
|
|
|
|
;; Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
|
|
;; distribute this software, either in source code form or as a compiled
|
|
|
|
;; binary, for any purpose, commercial or non-commercial, and by any
|
|
|
|
;; means.
|
|
|
|
;;
|
|
|
|
;; For more information, please refer to <https://unlicense.org>
|
2021-06-01 13:07:29 +00:00
|
|
|
|
2021-05-29 07:40:06 +00:00
|
|
|
#+END_SRC
|
|
|
|
|
|
|
|
"Commentary" is displayed when the user clicks on the package's entry in =M-x list-packages=.
|
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
|
|
;;; Commentary:
|
|
|
|
;;
|
|
|
|
;; This package adds a column to Chronometrist displaying sparklines for each task.
|
|
|
|
#+END_SRC
|
2022-01-23 05:36:02 +00:00
|
|
|
|
2021-06-02 19:58:17 +00:00
|
|
|
* Dependencies
|
2021-05-29 07:40:06 +00:00
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
|
|
;;; Code:
|
2022-01-23 05:36:02 +00:00
|
|
|
;; This file was automatically generated from chronometrist-spark.org.
|
2021-05-29 07:40:06 +00:00
|
|
|
(require 'chronometrist)
|
|
|
|
(require 'spark)
|
|
|
|
#+END_SRC
|
2022-01-23 05:36:02 +00:00
|
|
|
|
2021-06-02 19:58:17 +00:00
|
|
|
* Code
|
2021-06-07 16:29:57 +00:00
|
|
|
** custom group :custom:group:
|
2021-06-02 19:56:19 +00:00
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
|
|
(defgroup chronometrist-spark nil
|
|
|
|
"Show sparklines in `chronometrist'."
|
|
|
|
:group 'applications)
|
|
|
|
#+END_SRC
|
|
|
|
|
2021-06-07 16:29:57 +00:00
|
|
|
** length :custom:variable:
|
2021-05-29 11:41:05 +00:00
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
|
|
(defcustom chronometrist-spark-length 7
|
2021-06-02 19:56:19 +00:00
|
|
|
"Length of each sparkline in number of days."
|
|
|
|
:type 'integer)
|
2021-05-29 11:41:05 +00:00
|
|
|
#+END_SRC
|
|
|
|
|
2021-06-07 17:42:50 +00:00
|
|
|
** show-range :custom:variable:
|
2021-06-07 14:18:16 +00:00
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
|
|
(defcustom chronometrist-spark-show-range t
|
|
|
|
"If non-nil, display range of each sparkline."
|
|
|
|
:type 'boolean)
|
|
|
|
#+END_SRC
|
|
|
|
|
2021-06-07 21:03:42 +00:00
|
|
|
** range :function:
|
|
|
|
#+BEGIN_SRC emacs-lisp
|
2021-06-13 16:06:39 +00:00
|
|
|
(defun chronometrist-spark-range (durations)
|
2021-06-07 21:03:42 +00:00
|
|
|
"Return range for DURATIONS as a string.
|
2021-06-13 16:06:39 +00:00
|
|
|
DURATIONS must be a list of integer seconds."
|
|
|
|
(let* ((duration-minutes (--map (/ it 60) durations))
|
|
|
|
(durations-nonzero (seq-remove #'zerop duration-minutes))
|
|
|
|
(length (length durations-nonzero)))
|
|
|
|
(cond ((not durations-nonzero) "")
|
|
|
|
((> length 1)
|
|
|
|
(format "(%sm~%sm)" (apply #'min durations-nonzero)
|
|
|
|
(apply #'max duration-minutes)))
|
|
|
|
((= 1 length)
|
|
|
|
;; This task only had activity on one day in the given
|
|
|
|
;; range of days - these durations, then, cannot really
|
|
|
|
;; have a minimum and maximum range.
|
|
|
|
(format "(%sm)" (apply #'max duration-minutes))))))
|
2021-06-07 21:03:42 +00:00
|
|
|
#+END_SRC
|
|
|
|
|
2021-06-13 16:06:39 +00:00
|
|
|
*** tests
|
|
|
|
#+BEGIN_SRC emacs-lisp :tangle ../tests/chronometrist-spark-tests :load test
|
|
|
|
(ert-deftest chronometrist-spark-range ()
|
|
|
|
(should (equal (chronometrist-spark-range '(0 0 0)) ""))
|
|
|
|
(should (equal (chronometrist-spark-range '(0 1 2)) ""))
|
|
|
|
(should (equal (chronometrist-spark-range '(60 0 0)) "(1m)"))
|
|
|
|
(should (equal (chronometrist-spark-range '(60 0 120)) "(1m~2m)")))
|
|
|
|
#+END_SRC
|
2021-09-08 14:18:15 +00:00
|
|
|
** durations :function:
|
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
|
|
(defun chronometrist-spark-durations (task length stop-ts)
|
2021-11-18 06:30:28 +00:00
|
|
|
"Return a list of durations for time tracked for TASK in the last LENGTH days before STOP-TS."
|
2021-09-08 14:18:15 +00:00
|
|
|
(cl-loop for day from (- (- length 1)) to 0
|
|
|
|
collect
|
2021-11-18 06:30:28 +00:00
|
|
|
(chronometrist-task-time-one-day task (ts-adjust 'day day stop-ts))))
|
2021-09-08 14:18:15 +00:00
|
|
|
#+END_SRC
|
|
|
|
|
2021-06-07 16:29:57 +00:00
|
|
|
** TODO row-transformer :function:
|
2021-05-29 11:41:05 +00:00
|
|
|
if larger than 7
|
|
|
|
add space after (% length 7)th element
|
|
|
|
then add space after every 7 elements
|
|
|
|
|
2021-06-07 17:42:50 +00:00
|
|
|
+ if task has no time tracked for it - ""
|
|
|
|
+ don't display 0 as the minimum time tracked
|
|
|
|
+ [ ] if task has only one day of time tracked - "(40m)"
|
|
|
|
|
2021-05-29 11:41:05 +00:00
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
|
|
(defun chronometrist-spark-row-transformer (row)
|
|
|
|
"Add a sparkline cell to ROW.
|
|
|
|
Used to add a sparkline column to `chronometrist-rows'.
|
|
|
|
|
|
|
|
ROW must be a valid element of the list specified by
|
|
|
|
`tabulated-list-entries'."
|
2021-06-01 13:07:29 +00:00
|
|
|
(-let* (((task vector) row)
|
2021-09-08 14:18:15 +00:00
|
|
|
(durations (chronometrist-spark-durations task chronometrist-spark-length (ts-now)))
|
|
|
|
(sparkline (if (and (not (seq-every-p #'zerop durations))
|
|
|
|
chronometrist-spark-show-range)
|
|
|
|
(format "%s %s" (spark durations) (chronometrist-spark-range durations))
|
|
|
|
(format "%s" (spark durations)))))
|
2021-05-29 11:41:05 +00:00
|
|
|
(list task (vconcat vector `[,sparkline]))))
|
|
|
|
|
|
|
|
#+END_SRC
|
|
|
|
|
2021-06-07 16:29:57 +00:00
|
|
|
** TODO schema-transformer :function:
|
2021-05-29 11:41:05 +00:00
|
|
|
calculate length while accounting for space
|
|
|
|
|
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
|
|
(defun chronometrist-spark-schema-transformer (schema)
|
|
|
|
"Add a sparkline column to SCHEMA.
|
|
|
|
Used to add a sparkline column to `chronometrist-schema-transformers'.
|
|
|
|
SCHEMA should be a vector as specified by `tabulated-list-format'."
|
2021-06-07 14:18:16 +00:00
|
|
|
(vconcat schema `[("Graph"
|
|
|
|
,(if chronometrist-spark-show-range
|
2021-06-07 21:03:42 +00:00
|
|
|
(+ chronometrist-spark-length 12)
|
2021-06-07 17:42:50 +00:00
|
|
|
chronometrist-spark-length)
|
2021-06-07 14:18:16 +00:00
|
|
|
t)]))
|
2021-05-29 11:41:05 +00:00
|
|
|
#+END_SRC
|
|
|
|
|
2021-06-07 16:29:57 +00:00
|
|
|
** setup :writer:
|
2021-05-29 11:41:05 +00:00
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
|
|
(defun chronometrist-spark-setup ()
|
|
|
|
"Add `chronometrist-sparkline' functions to `chronometrist' hooks."
|
|
|
|
(add-to-list 'chronometrist-row-transformers #'chronometrist-spark-row-transformer)
|
2021-05-29 15:25:38 +00:00
|
|
|
(add-to-list 'chronometrist-schema-transformers #'chronometrist-spark-schema-transformer))
|
2021-05-29 11:41:05 +00:00
|
|
|
#+END_SRC
|
|
|
|
|
2021-06-07 16:29:57 +00:00
|
|
|
** teardown :writer:
|
2021-05-29 11:41:05 +00:00
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
|
|
(defun chronometrist-spark-teardown ()
|
|
|
|
"Remove `chronometrist-sparkline' functions from `chronometrist' hooks."
|
|
|
|
(setq chronometrist-row-transformers
|
|
|
|
(remove #'chronometrist-spark-row-transformer chronometrist-row-transformers)
|
|
|
|
chronometrist-schema-transformers
|
2021-05-29 15:25:38 +00:00
|
|
|
(remove #'chronometrist-spark-schema-transformer chronometrist-schema-transformers)))
|
2021-05-29 11:41:05 +00:00
|
|
|
#+END_SRC
|
|
|
|
|
2021-06-07 16:29:57 +00:00
|
|
|
** minor-mode :minor:mode:
|
2021-05-29 11:41:05 +00:00
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
|
|
(define-minor-mode chronometrist-spark-minor-mode
|
|
|
|
nil nil nil nil
|
|
|
|
;; when being enabled/disabled, `chronometrist-spark-minor-mode' will already be t/nil here
|
|
|
|
(if chronometrist-spark-minor-mode (chronometrist-spark-setup) (chronometrist-spark-teardown)))
|
|
|
|
#+END_SRC
|
|
|
|
|
2021-06-02 19:58:17 +00:00
|
|
|
* Provide
|
2021-05-29 07:40:06 +00:00
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
|
|
(provide 'chronometrist-spark)
|
|
|
|
;;; chronometrist-spark.el ends here
|
|
|
|
#+END_SRC
|
2022-01-23 05:52:30 +00:00
|
|
|
|
2022-02-14 19:56:15 +00:00
|
|
|
* Local variables :noexport:
|
2022-01-23 05:52:30 +00:00
|
|
|
# Local Variables:
|
|
|
|
# eval: (when (package-installed-p 'literate-elisp) (require 'literate-elisp) (literate-elisp-load (buffer-file-name)))
|
|
|
|
# End:
|