1

Here is an example of a table to format,

  • the first row represents how often a task is done per year,
  • the first column represents how long a task takes in seconds
|-------+------------+-----------+----------+---------+---------+-------|
|       |      18250 |      1825 |      365 |      52 |      12 |     1 |
|-------+------------+-----------+----------+---------+---------+-------|
|     1 |      18250 |      1825 |      365 |      52 |      12 |     1 |
|     5 |      91250 |      9125 |     1825 |     260 |      60 |     5 |
|    30 |     547500 |     54750 |    10950 |    1560 |     360 |    30 |
|    60 |    1095000 |    109500 |    21900 |    3120 |     720 |    60 |
|   300 |    5475000 |    547500 |   109500 |   15600 |    3600 |   300 |
|  1800 |   32850000 |   3285000 |   657000 |   93600 |   21600 |  1800 |
|  3600 |   65700000 |   6570000 |  1314000 |  187200 |   43200 |  3600 |
| 21600 |  394200000 |  39420000 |  7884000 | 1123200 |  259200 | 21600 |
| 86400 | 1576800000 | 157680000 | 31536000 | 4492800 | 1036800 | 86400 |
|-------+------------+-----------+----------+---------+---------+-------|
#+TBLFM: $2=$1*@1$2::$3=$1*@1$3::$4=$1*@1$4::$5=$1*@1$5::$6=$1*@1$6::$7=$1*@1$7

Is it possible to change the cell display while keeping the values?

Here are some examples of displays:

|-------+-------+---------|
| Cell  | Value | Display |
|-------+-------+---------|
| @1$4  |   365 | Daily   |
| @1$7  |     1 | Yearly  |
| @3$6  |    60 | 1 m     |
| @6$6  |  3600 | 1 h     |
| @10$1 | 86400 | 1 d     |
|-------+-------+---------|

Here is a complete example from https://xkcd.com (where values are multiplied by 5):

XKCD chart

Ideally, I would like the display to be generated according to some rules (e.g. for all rows below row 1, if 3600 < x > 59, divide by 60 and add " m"), and to show a heat map.

I don't necessarily need the formatting to be done by org-mode. The table will be exported to HTML, so ideally, the formatting would be done through HTML export and JS code (using some cdn library).

1
  • 1
    As the question is mainly about JS, maybe I should post the question to StackOverflow instead. If this is the case, please tell me here.
    – crocefisso
    Jan 21 at 20:59

1 Answer 1

1

EDIT The easiest (best?) solution is in the 'list of lists' part at the end of this answer

In principle this would be best achieved using the org-export-before-processing-hook, as you could replace contents directly instead of modifying the html code. However, I am not aware of any function that would directly provide you the 'cell address' in that case.

Because such 'address' function is available for the export filters, we use a filter here. So in this case you can use the somewhat hidden function org-export-table-cell-address in ox.el to obtain a cell's row and column (index starting from 0). For getting the number values, you can use org-element-contents. The following example shows how you could achieve what you ask for:

(defun my-cell-export-filter-fn (data backend info)
  (let* ((cell (seq-some (lambda (tp)
                         (plist-get (caddr tp) :parent))
                         (object-intervals data)))
         (address (org-export-table-cell-address cell info)))
    (when cell
      ;; (print (org-export-table-cell-address cell info))
      ;; (print (substring-no-properties data))
      (let ((contents (car (org-element-contents cell))))
        (if (= (car address) 0)
            (setq data (string-replace contents (pcase contents
                                                  ("365" "Daily")
                                                  ("52" "Weekly")
                                                  ("12" "Monthy")
                                                  ("52" "Yearly")
                                                  (t contents))
                                       data))
          (setq data (pcase address
                       ((or '(6 . 1) '(7 . 1) '(8 . 1)
                            '(9 . 1) '(8 . 2) '(9 . 2)
                            '(8 . 3) '(9 . 3) '(9 . 4))
                        (string-replace "<td" "<td style=\"background-color:red;color:black;\" " data))
                       (_ data)))
          (let ((value (string-to-number contents)))
            (when (< 59 value 3600)
              ;; (print value)
              ;; (print (substring-no-properties data))
              (setq value (/ value 60.0)
                    data (replace-regexp-in-string "[[:digit:]]+" (format "%.1fm" value) data))))))))
  data)

(add-to-list 'org-export-filter-table-cell-functions #'my-cell-export-filter-fn)

Evaluate the code, and export your org file to html. You should see the modified table when viewing the html file in a browser.

To see the original html format of the cells you can uncomment the commented lines.

org-export-before-processing-hook solution example:

Although it might be redundant, as I have the code anyway, I'll include a code example for a solution when using the org-export-before-processing-hook, where we have to implement our own tricks to determine the cell address

(defun format-table-fn (backend)
  (let ((rows 0)
        (cells 0)
        cols)
    (when (eq backend 'html)
      ;; first we determine the number of rows and columns
      (org-element-map (org-element-parse-buffer) 'table
        (lambda (table)
          (when (string= (org-element-property :name table) "efficiency-table")
                  (org-element-map table 'table-row
                    (lambda (row)
                      (let ((n (length (org-element-map row 'table-cell #'identity))))
                        (when (> n 0)
                          (cl-incf cells n)
                          (cl-incf rows))))))))
      (setq cols (/ cells rows))
      ;; now we can use the table dimensions to 'account for cell address'
      (org-element-map (org-element-parse-buffer) 'table
        (lambda (table)
          (when (string= (org-element-property :name table) "efficiency-table")
            (let ((row 1)
                  (col 1))
              (org-element-map table 'table-cell
                (lambda (cell)
                  (pcase (cons row col)
                    ('(1 . 3) (let ((beg (org-element-property :contents-begin cell))
                                    (end (org-element-property :contents-end cell)))
                                (replace-region-contents beg end (lambda () "Daily")))))
                  (if (= (% col cols) 0)
                      (setq col 1)
                    (cl-incf col))
                  (if (= (% row rows) 0)
                      (setq row 1)
                    (cl-incf row)))))))))))

(add-hook 'org-export-before-processing-functions #'format-table-fn)

list of lists

The easiest solution is to parse the table to a list of lists first.

Actually, this happens automatically when passing a table as a variable to another src-block. Unfortunately, the text-properties (i.e. the face) do not get included in the formatted table, but we can still use emphasis markers to change the formatting. You can then additonaly configure org-emphasis-alist to configure the formatting in org, and org-html-text-markup-alist e.g. using:

(add-to-list 'org-html-text-markup-alist  '(strike-through . "<font color=\"red\">%s</font>"))

to configure the formatting on html export.

(to use the below code block, name your table 'efficiency-table')

#+begin_src emacs-lisp :var tbl=efficiency-table :exports result
(seq-map-indexed (lambda (e i)
           (if (= i 0)
               (mapcar (lambda (label)
                 (pcase label
                   (365 "Daily")
                   (52 "Weekly")
                   (12 "Monthly")
                   (1 "Yearly")
                   (_ label)))
                   e)
             (seq-map-indexed (lambda (f j)
                    (when (< 59 f 3600)
                      (setq f (format "%.1fm" (/ (round (/ f 60.0) 0.1) 10.0))))
                    (pcase (cons i j)
                      ((or '(6 . 1) '(7 . 1) '(8 . 1)
                           '(9 . 1) '(8 . 2) '(9 . 2)
                           '(8 . 3) '(9 . 3) '(9 . 4))
                       ;; unfortunately the face
                       ;; does not get included in
                       ;; the resulting table

                       ;; (propertize (number-to-string f) 'help-echo "HOI"))
                       (format "+%d+" f))
                      (_ f)))
                      e)))
         tbl)
#+end_src
10
  • Thank you! It works perfectly for formatting one cell at a time. But can we format several cells at once? For example, apply (string-replace "<td" "<td style=\"background-color:grey;color:grey;\" " data) to cells (6 . 1),(7 . 1), (8.1), (9.1), (8.2), (9.2), (8.3), (9.3), (9.4). Or apply rules based on values (e.g. for all rows below row 1, if 3600 < x > 59, divide by 60 and add " m")? I was not able to find much documentation on org-export-table-cell-address...
    – crocefisso
    Jan 22 at 9:58
  • 1
    The org-export-table-cell-address is just to get the row and column numbers (you can wrap it in a print, to see its return value). You can print the contents of the cells by commenting out the lines. Then you can use the pcase to select the cells you mentioned (read about pcase here). Or you could replace the pcase with a when or cond for applying rules based on values. Jan 22 at 11:02
  • Ok, thank you for the hints. I don't know much about Elisp, but I'll try to find out the solution from there. I'll post the solution here when I find it.
    – crocefisso
    Jan 22 at 13:38
  • 1
    I am not sure what you mean by your question, but I think the 'list of lists' section of my answer might answer it. You should name your original table and then pass it as a variable to an org src-block. The table will be 'parsed' to a list of lists, which you can transform to create your new table. The only problem with that solution is that I don't know how to apply a 'face' to (parts of) the table. (Try it... name your table as instructed and then copy and evaluate that example src-block) Jan 23 at 21:24
  • 1
    And yes, this solution is the best. I think the color issue can be solved by just removing the values, so these cells are shown as blanks.
    – crocefisso
    Jan 23 at 21:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.