I have a column in an org-mode table with numbers in each cell. I'd like to change the background color of the cell to red if the number is below 1 or above 2.

How can I do that?

  • 3
    Great question! Both org-table-edit-formulas aka C-c ' and org-table-toggle-coordinate-overlays aka C-c } provide clues on how to implement this type of highlighting feature. Perhaps an elisp guru will provide some additional hints or examples. – Melioratus Jan 24 '15 at 17:30
up vote 2 down vote accepted

I got whole-table formatting to work with some Elisp:

A formula is evaluated for the cells' contents, and converted to a color using a gradient.

Org file including code:

#+name: item-prices
|-----------------------+--------+-------------+-------+------+------+--------+------+------+------+------+------|
| Item                  | Weight | Label Price | Ratio | CS-F | <-LR |   <-WR | CS-N | Si-N | Si-2 | St-N | St-F |
|-----------------------+--------+-------------+-------+------+------+--------+------+------+------+------+------|
| Охотничье ружьё       |    3.3 |         400 |   121 |   40 |   10 |  11.82 |      |   40 |   40 |   50 |   60 |
| «Гадюка-5»            |   2.88 |        3000 |  1042 |  300 |   10 | 103.82 |      |  300 |  300 |  375 |  450 |
| Обрез                 |   1.90 |         200 |   105 |   20 |   10 |  10.00 |      |   20 |   20 |   25 |   30 |
| ПМм                   |   0.73 |         300 |   411 |   30 |   10 |  39.73 |      |   30 |   30 |   37 |   45 |
| АКМ-74/2 *            |   3.07 |        4000 |  1303 |  637 |   16 | 207.49 |      |  318 |  318 |  398 |  478 |
| АКМ-74/2У             |   2.71 |        2100 |   775 |  420 |   20 | 154.61 |      |  210 |  210 |  262 |  315 |
| ПБ-1с                 |   0.97 |         400 |   412 |  120 |   30 | 122.68 |  100 |   40 |   40 |   50 |   60 |
| «Чeйзер-13»           |   3.00 |        1250 |   417 |      |      |        |      |  125 |      |      |      |
| «Чeйзер-13» *         |        |        1250 |   417 |  200 |   16 |  66.33 |      |  100 |  100 |  125 |  149 |
| ХПСС-1м               |   0.94 |         600 |   682 |      |      |        |      |   60 |      |      |      |
| ХПСС-1м *             |   0.88 |         600 |   682 |   92 |   15 | 104.55 |      |   46 |   46 |   57 |   69 |
| «Фора-12»             |   0.83 |         600 |   723 |  120 |   20 | 143.37 |      |   60 |   60 |   74 |   90 |
| «Кора-919»            |   1.10 |        1500 |       |      |      |        |      |  150 |  150 |      |  225 |
|-----------------------+--------+-------------+-------+------+------+--------+------+------+------+------+------|
| Прицел ПСО-1          |   0.20 |        1000 |  5000 |  100 |   10 | 500.00 |      |  150 |  150 |  150 |  200 |
| Детектор «Отклик»     |   0.00 |         500 |   inf |   50 |   10 |  50.00 |      |  100 |  100 |  175 |  250 |
| Детектор «Медведь»    |   0.00 |        1000 |   inf |  100 |   10 | 100.00 |      |      |      |      |  500 |
|-----------------------+--------+-------------+-------+------+------+--------+------+------+------+------+------|
| Кожаная куртка        |   3.00 |         500 |   167 |  250 |   50 |  83.33 |      |    - |    - |  200 |      |
| Бронежилет ЧН-1       |   4.00 |        5000 |  1250 | 2500 |   50 | 625.00 |      |    - |    - |      |      |
|-----------------------+--------+-------------+-------+------+------+--------+------+------+------+------+------|
| Аптечка               |   0.10 |         300 |  3000 |   30 |   10 | 300.00 |   16 |   45 |   45 |  105 |  150 |
| Бинт                  |   0.05 |         200 |  4000 |   20 |   10 | 400.00 |   11 |   30 |      |   70 |  100 |
| Противорад. п.        |   0.05 |         300 |  6000 |   30 |   10 | 600.00 |   16 |   45 |      |  105 |  150 |
|-----------------------+--------+-------------+-------+------+------+--------+------+------+------+------+------|
| Водка «Казаки»        |   0.60 |         100 |   167 |  100 |  100 | 166.67 |  100 |    - |    - |    - |    - |
| «Завтрак туриста»     |   0.30 |         100 |   333 |  100 |  100 | 333.33 |      |    - |    - |    - |    - |
| Колбаса «Диетическая» |   0.50 |          50 |   100 |   50 |  100 | 100.00 |      |    - |    - |    - |    - |
| Хлеб                  |   0.30 |          20 |    67 |   20 |  100 |  66.67 |      |    - |    - |    - |    - |
|-----------------------+--------+-------------+-------+------+------+--------+------+------+------+------+------|
| Патроны 9x18 мм       |   0.20 |          50 |   250 |    5 |   10 |  25.00 |    3 |    7 |    7 |    5 |    5 |
| Патроны 9x19 мм РВР   |   0.24 |         100 |   417 |   20 |   20 |  83.33 |   15 |      |      |      |      |
| Патроны 9x19 мм ЦМО   |   0.24 |         100 |   417 |      |    0 |   0.00 |      |   15 |   15 |   15 |   20 |
| Патроны 12x70 дробь   |   0.45 |          10 |    22 |    1 |   10 |   2.22 |    0 |    1 |      |    1 |    1 |
| Патроны 12x76 жекан   |   0.50 |          20 |    40 |    4 |   20 |   8.00 |    3 |    1 |      |    3 |    4 |
|-----------------------+--------+-------------+-------+------+------+--------+------+------+------+------+------|
| Граната РГД-5         |   0.30 |         350 |       |      |      |        |      |   52 |   52 |   70 |   70 |
|-----------------------+--------+-------------+-------+------+------+--------+------+------+------+------+------|
| «Медуза»              |    0.5 |        4000 |  8000 |      |    0 |   0.00 |      | 2800 | 3600 | 2500 | 2800 |
|-----------------------+--------+-------------+-------+------+------+--------+------+------+------+------+------|
#+TBLFM: $4='(/ (string-to-number $3) (string-to-number $2));%1.f
#+TBLFM: $6='(/ (string-to-number $5) 0.01 (string-to-number $3));%1.f
#+TBLFM: $7=$5/$2;%1.2f

#+begin_src emacs-lisp :var table=item-prices
  (defun cs/itpl (low high r rlow rhigh)
    "Return the point between LOW and HIGH that corresponds to where R is between RLOW and RHIGH."
    (+ low (/ (* (- high low) (- r rlow)) (- rhigh rlow))))

  (defun cs/gradient (gradient p)
    (if (< p (caar gradient))
        (cdar gradient)
      (while (and (cdr gradient) (> p (caadr gradient)))
        (setq gradient (cdr gradient)))
      (if (null (cdr gradient))
          (cdar gradient)
        (list
         (cs/itpl (nth 1 (car gradient)) (nth 1 (cadr gradient)) p (caar gradient) (caadr gradient))
         (cs/itpl (nth 2 (car gradient)) (nth 2 (cadr gradient)) p (caar gradient) (caadr gradient))
         (cs/itpl (nth 3 (car gradient)) (nth 3 (cadr gradient)) p (caar gradient) (caadr gradient))))))

  (defun cs/scs-table-color ()
    (when (boundp 'cs/cell-color-overlays)
      (mapc #'delete-overlay cs/cell-color-overlays))
    (setq-local cs/cell-color-overlays nil)

    (save-excursion
      (org-table-map-tables
       (lambda ()
         (let* ((table (cl-remove-if-not #'listp (org-table-to-lisp))) ; remove 'hline
                (heading (car table))
                (element (org-element-at-point)))
           (while (and element (not (eq (car element) 'table)))
             (setq element (plist-get (cadr element) :parent)))
           (cond
            ((equal (plist-get (cadr element) :name) "item-prices")

             (org-table-analyze)
             (cl-loop for row being the elements of (cdr table) using (index row-index)
                      do (cl-loop for col being the elements of row using (index col-index)
                                  if (and
                                      (string-match "^..-.$" (nth col-index heading))
                                      (not (zerop (length col)))
                                      (not (equal "0" col)))
                                  do (progn
                                       (org-table-goto-field (format "@%d$%d" (+ 2 row-index) (1+ col-index)))
                                       (forward-char)
                                       (let* ((base-price (string-to-number (nth 2 row)))
                                              (vendor-price (string-to-number col))
                                              (ratio (/ vendor-price 1.0 base-price))
                                              (gradient '((0.10 #x40 #x00 #x00)
                                                          (0.20 #xC0 #x00 #x00)
                                                          (0.50 #x00 #x80 #x00)
                                                          (1.00 #x00 #xFF #x80)))
                                              (color (cs/gradient gradient ratio))
                                              (overlay (make-overlay
                                                        (progn (org-table-beginning-of-field 1) (backward-char) (point))
                                                        (progn (org-table-end-of-field 1) (forward-char) (point))))
                                              (bg (apply #'message "#%02x%02x%02x" color))
                                              (fg (if (< (apply #'+ color) 383) "#ffffff" "#000000"))
                                              (face (list
                                                     :background bg
                                                     :foreground fg)))
                                         (overlay-put overlay 'face face)
                                         (push overlay cs/cell-color-overlays)))))))))
       t)))

  (add-hook 'org-ctrl-c-ctrl-c-hook 'cs/scs-table-color nil t)
  nil
#+end_src
  • That's fantastic! I'd love to read a slightly more in-depth description of how it's all connected, even if it's just "this code runs when you do X, it takes Y and Z as inputs, and this is what it does to the table" :) – Trevoke Dec 2 at 16:28
  • Thanks. This is pretty much a combination of your two answers. There is some redundancy as it both receives the table data from org-babel and searches for the table name declaration (so that it can add overlays etc.). From there, it maps row and column numbers from the received table data to org-table cell coordinates. cs/itpl does simple linear interpolation, and cs/gradient uses that to interpolate a color using a list of data points and color stops. From there, it just adds an overlay as in your answer. The example is a bit non-trivial as it consults data from elsewhere in the table. – Vladimir Panteleev Dec 2 at 16:52
  • Updated the code with a newer version which fixes the name/data redundancy, clears old overlays, and registers itself as an org-ctrl-c-ctrl-c-hook so you don't have to put point on the code block to run it. It can also update all tables in the document courtesy of org-table-map-tables. – Vladimir Panteleev Dec 4 at 3:35
  • That's great. I'd love some more comments for folks who may come here and be unfamiliar with elisp, but it's amazing, thank you! – Trevoke Dec 5 at 3:34

Using an overlay is how I'm going to want to do it. I can hook into org-ctrl-c-ctrl-c-hook. It means I can press C-c C-c to run the check.

I need to properly check that I'm inside a table and run this for all cells.

Then I probably need to hook into the alignment function to either redo the overlays or at least clear them.

This code will make the cell background red for the cell I'm in if the value is less than 1 or greater than 2 when I press C-c C-c ... But it is still buggy and will clear the overlays if one of them doesn't match the rules.

(defun staggering ()
  (save-excursion
    (let* ((ot-field-beginning (progn (org-table-beginning-of-field 1) (point)))
           (ot-field-end (progn (org-table-end-of-field 1) (point)))
           (cell-text (buffer-substring ot-field-beginning ot-field-end)))
      (if (or (< (string-to-number cell-text) 1)
              (> (string-to-number cell-text) 2))
          (overlay-put (make-overlay
                        (progn (org-table-beginning-of-field 1) (point))
                        (progn (org-table-end-of-field 1) (point)))
                       'face '(:background "#FF0000"))))))
(add-hook 'org-ctrl-c-ctrl-c-hook 'staggering)

This is not yet an answer but I want to keep track of the things I discover here, as they may give someone else an idea.

It is possible to conditionally modify the value of the cell itself:

We can create a formatting function in elisp and then call it from the formula line:

#+BEGIN_SRC emacs-lisp :results silent
(defun danger (cell)
  (if (or (< (string-to-number cell) 1)
          (> (string-to-number cell) 2))
        (concat (int-to-string (string-to-number cell)) "!")
        cell))
#+END_SRC

And it can be used like so:

| String | Num | 
|--------+-----| 
| Foo    |   2 | 
| Bar    |   1 | 
| Baz    |  3! | 
|--------+-----|
#+TBLFM: $2='(danger @0$0)

I think what I want may require the creation of an overlay.

Emacs provides the function hi-lock-face-buffer M-s h rwhich highlights a regular expression in the buffer as you type.

All we need is a regular expression which matches any number which is not 1 or 2 and is within the cell of a table. Try this:

| *\(-[0-9]+\|[03-9]\|[0-9][0-9]+\) *|

(You can recall previous expressions with M-n and M-p.)

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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