Modus themes: review of the org-habit graph colours

Overview of a tricky exercise in recalibrating colour values for optimal results in their context

UPDATE 2022-01-06 08:09 +0200: I discovered a combination that I had not considered before (light blue next to intense green followed by more light blue), so I tweaked things further. The tables have been updated accordingly.


I spent this afternoon reviewing the subset of the Modus themes’ colour palette that is used in the org-habit graph. In this post I present the technicalities of the endeavour. The gist of this publication is that picking colour values is hard.

Before I delve into the technicalities, I should inform you that I also took this opportunity to make all the variants of the org-habit graph work with the modus-themes-deuteranopia toggle, whereas before it only worked with the traffic-light style (check the doc string of modus-themes-org-agenda on what those styles are). In short: more power for users with red-green colour deficiency. Play around with these:

(setq modus-themes-deuteranopia t) ; try with nil too

(setq modus-themes-org-agenda
      '(;; ...
        ;; Other key . value pairs
        ;; ...
        (habit . nil)))

;; OR

(setq modus-themes-org-agenda
      '(;; ...
        ;; Other key . value pairs
        ;; ...
        (habit . simplified)))

;; OR

(setq modus-themes-org-agenda
      '(;; ...
        ;; Other key . value pairs
        ;; ...
        (habit . traffic-light)))

Now on to the minutia of picking colour values. Since I have started clocking my time with Org, I am informed that the edits in modus-themes.el took me 3 hours and 16 minutes. (This does not include the time required to compile the data and write this entry.) All those hours to produce this sort of diff:

diff --git a/modus-themes.el b/modus-themes.el
index 7767acf..17f3362 100644
--- a/modus-themes.el
+++ b/modus-themes.el
@@ -495,18 +495,18 @@ (defconst modus-themes-operandi-colors
     ;; those background values should only be used for graphs or similar
     ;; applications where colored blocks are expected to be positioned
     ;; next to each other
-    (red-graph-0-bg . "#ef6f79")
-    (red-graph-1-bg . "#ff9f9f")
-    (green-graph-0-bg . "#49d239")
-    (green-graph-1-bg . "#6dec6d")
-    (yellow-graph-0-bg . "#efec08")
-    (yellow-graph-1-bg . "#dbff4e")
-    (blue-graph-0-bg . "#55a2f0")
-    (blue-graph-1-bg . "#7fcfff")
-    (magenta-graph-0-bg . "#ba86ef")
-    (magenta-graph-1-bg . "#e7afff")
-    (cyan-graph-0-bg . "#30d3f0")
-    (cyan-graph-1-bg . "#6fefff")
+    (red-graph-0-bg . "#ef7969")
+    (red-graph-1-bg . "#ffaab4")
+    (green-graph-0-bg . "#4faa09")
+    (green-graph-1-bg . "#8fef00")
+    (yellow-graph-0-bg . "#ffcf00")
+    (yellow-graph-1-bg . "#f9ff00")
+    (blue-graph-0-bg . "#7090ff")
+    (blue-graph-1-bg . "#9fc6ff")
+    (magenta-graph-0-bg . "#e07fff")
+    (magenta-graph-1-bg . "#fad0ff")
+    (cyan-graph-0-bg . "#70d3f0")
+    (cyan-graph-1-bg . "#afefff")
     ;; the following are for cases where both the foreground and the
     ;; background need to have a similar hue and so must be combined
     ;; with themselves, even though the foregrounds can be paired with
@@ -736,18 +736,18 @@ (defconst modus-themes-vivendi-colors
     ;; those background values should only be used for graphs or similar
     ;; applications where colored blocks are expected to be positioned
     ;; next to each other
-    (red-graph-0-bg . "#af0404")
-    (red-graph-1-bg . "#801f2f")
-    (green-graph-0-bg . "#24ba2f")
-    (green-graph-1-bg . "#0f8f07")
-    (yellow-graph-0-bg . "#ffd03e")
-    (yellow-graph-1-bg . "#d7d800")
-    (blue-graph-1-bg . "#2f50c8")
-    (blue-graph-0-bg . "#5f8fff")
-    (magenta-graph-0-bg . "#af7bee")
-    (magenta-graph-1-bg . "#7f59cf")
-    (cyan-graph-0-bg . "#47dcfa")
-    (cyan-graph-1-bg . "#0bc0df")
+    (red-graph-0-bg . "#bb0404")
+    (red-graph-1-bg . "#6f1f1f")
+    (green-graph-0-bg . "#24ba0f")
+    (green-graph-1-bg . "#1f6f00")
+    (yellow-graph-0-bg . "#f1e00a")
+    (yellow-graph-1-bg . "#b08600")
+    (blue-graph-0-bg . "#2fafef")
+    (blue-graph-1-bg . "#1f2f8f")
+    (magenta-graph-0-bg . "#bf94fe")
+    (magenta-graph-1-bg . "#5f509f")
+    (cyan-graph-0-bg . "#47dfea")
+    (cyan-graph-1-bg . "#00808f")
     ;; the following are for cases where both the foreground and the
     ;; background need to have a similar hue and so must be combined
     ;; with themselves, even though the foregrounds can be paired with

To be clear, these are very subtle differences in some cases. Consider, for instance a visualisation of one of the tables presented below with rainbow-mode enabled (check my dotemacs for relevant configs):

Modus Operandi sample visualisation of table

Technical considerations on the usage of those colours

What we are dealing with here are pairs of colour values with a subtle and an intense variant. Here are screenshots of how the org-habit graph looks by default (though do check modus-themes-org-agenda).

Modus Operandi (the light theme):

Modus Operandi sample org-habit graph

Modus Vivendi (the dark theme):

Modus Vivendi sample org-habit graph

Unlike most colour combinations where we have a foreground value that is intended to be used by text and we compare it to a background value to arrive at a minimum contrast in relative luminance of 7:1 (per the modus-themes-contrast function), the org-habit colours are all backgrounds. This means that the 7:1 contrast ratio is not pertinent to our consideration as we are not testing the legibility of text. Instead, we have to measure the proximity in colour space of background values in comparison to each other or, more precisely, in relation to colours that appear adjacent to them in their particular context (i.e. we are not particularly interested in all possible combinations).

Furthermore, because of differences in colour perception between light and dark themes, we must treat modus-operandi and modus-vivendi in their own right. This is not some simplistic exercise in colour inversion. To clarify: it is easier to discern colour differences between bright colours than subdued ones regardless of actual distance in colour space. The brighter background colours are used with modus-operandi so you will notice that the distance between them for some of the pairs of red, green, yellow, blue, magenta, cyan is smaller than in modus-vivendi (note that we use “magenta” for naming consistency, though sometimes the actual colour is violet or purple for the purposes of context-dependent variation).

Unfiltered data of the changes

In the following tables, we show the overall effect of those changes. The built-in colour-distance function is written as Δ thanks to (defalias #'Δ #'color-distance). There is no need to focus on the exact distance value, but mostly on the relative effect. In the next section we elaborate on the particularities.

[ The formula for those tables which were written in Org notation is #+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) :: $8='(Δ $1 @1$8) :: $9='(Δ $1 @1$9) :: $10='(Δ $1 @1$10) :: $11='(Δ $1 @1$11) :: $12='(Δ $1 @1$12) :: $13='(Δ $1 @1$13). ]

OLD Modus Operandi graph (org-habit) colour distance in hueness:

  #ef6f79 #ff9f9f #49d239 #6dec6d #efec08 #dbff4e #55a2f0 #7fcfff #ba86ef #e7afff #30d3f0 #6fefff
#ef6f79 0 13010 121902 109012 89534 88678 107221 112812 40558 54313 169319 152275
#ff9f9f 13010 0 123434 87960 71347 54670 93134 75587 30037 21759 138833 103793
#49d239 121902 123434 0 13006 81037 64507 100420 110104 138754 165234 94658 111103
#6dec6d 109012 87960 13006 0 69513 35970 68764 58701 98593 105128 57735 55227
#efec08 89534 71347 81037 69513 0 13003 213337 178080 166483 142984 228952 186655
#dbff4e 88678 54670 64507 35970 13003 0 145428 105437 119715 93081 147673 106441
#55a2f0 107221 93134 100420 68764 213337 145428 0 13044 29187 57497 12798 26119
#7fcfff 112812 75587 110104 58701 178080 105437 13044 0 31268 33581 15404 4764
#ba86ef 40558 30037 138754 98593 166483 119715 29187 31268 0 13090 71096 59708
#e7afff 54313 21759 165234 105128 142984 93081 57497 33581 13090 0 91746 55269
#30d3f0 169319 138833 94658 57735 228952 147673 12798 15404 71096 91746 0 13017
#6fefff 152275 103793 111103 55227 186655 106441 26119 4764 59708 55269 13017 0

NEW Modus Operandi graph (org-habit) colour distance in hueness:

  #ef7969 #ffaab4 #4faa09 #8fef00 #ffcf00 #f9ff00 #7090ff #9fc6ff #e07fff #fad0ff #70d3f0 #afefff
#ef7969 0 21961 99439 106676 53152 95386 98252 92167 48246 77159 118813 117359
#ffaab4 21961 0 151991 126821 70826 94926 71701 41681 21983 17290 71098 49758
#4faa09 99439 151991 0 29455 88591 106302 165458 173577 209155 227396 150578 194988
#8fef00 106676 126821 29455 0 39278 32383 202638 165240 217995 181945 150709 158446
#ffcf00 53152 70826 88591 39278 0 9396 221491 169846 163698 131790 188491 163814
#f9ff00 95386 94926 106302 32383 9396 0 251105 180129 203715 141390 192087 158776
#7090ff 98252 71701 165458 202638 221491 251105 0 17390 34778 68521 18677 46633
#9fc6ff 92167 41681 173577 165240 169846 180129 17390 0 32035 23787 6876 7461
#e07fff 48246 21983 209155 217995 163698 203715 34778 32035 0 28445 62589 57301
#fad0ff 77159 17290 227396 181945 131790 141390 68521 23787 28445 0 52564 19936
#70d3f0 118813 71098 150578 150709 188491 192087 18677 6876 62589 52564 0 13964
#afefff 117359 49758 194988 158446 163814 158776 46633 7461 57301 19936 13964 0

OLD Modus Vivendi graph (org-habit) colour distance in hueness:

  #af0404 #801f2f #24ba2f #0f8f07 #ffd03e #d7d800 #5f8fff #2f50c8 #af7bee #7f59cf #47dcfa #0bc0df
#af0404 0 13197 185352 139125 193417 185677 251071 162793 184769 135146 368694 333961
#801f2f 13197 0 116655 84300 171524 163539 164986 87854 128352 78064 260218 220994
#24ba2f 185352 116655 0 13063 126754 89687 134979 112496 158089 126444 123269 92241
#0f8f07 139125 84300 13063 0 171398 120415 186877 126247 204125 150274 199253 148378
#ffd03e 193417 171524 126754 171398 0 13021 173197 225287 114798 150176 174725 217076
#d7d800 185677 163539 89687 120415 13021 0 216096 246294 166931 186457 207224 232936
#5f8fff 251071 164986 134979 186877 173197 216096 0 29588 18647 20223 25318 28261
#2f50c8 162793 87854 112496 126247 225287 246294 29588 0 51398 15558 87284 54868
#af7bee 184769 128352 158089 204125 114798 166931 18647 51398 0 13011 65355 83889
#7f59cf 135146 78064 126444 150274 150176 186457 20223 15558 13011 0 81596 74264
#47dcfa 368694 260218 123269 199253 174725 207224 25318 87284 65355 81596 0 13086
#0bc0df 333961 220994 92241 148378 217076 232936 28261 54868 83889 74264 13086 0

NEW Modus Vivendi graph (org-habit) colour distance in hueness:

  #b52c2c #702020 #4fd100 #007800 #f1e00a #b08600 #2fafef #1f2f8f #bf94fe #5f509f #47dfea #00808f
#b52c2c 0 13285 140927 106198 143404 37209 211299 80341 144794 56938 250752 132324
#702020 13285 0 131612 62148 194886 55042 208369 49756 190061 52156 259676 99738
#4fd100 140927 131612 0 45485 70640 46382 165460 168324 207617 135432 150260 98638
#007800 106198 62148 45485 0 188588 74000 184251 84053 261174 98026 211507 62084
#f1e00a 143404 194886 70640 188588 0 44871 235668 282865 159638 193095 196844 226941
#b08600 37209 55042 46382 74000 44871 0 195184 134954 149680 91391 198362 128070
#2fafef 211299 208369 165460 184251 235668 195184 0 93048 55062 59227 10653 40571
#1f2f8f 80341 49756 168324 84053 282865 134954 93048 0 135801 14376 151789 28445
#bf94fe 144794 190061 207617 261174 159638 149680 55062 135801 0 64611 60159 121516
#5f509f 56938 52156 135432 98026 193095 91391 59227 14376 64611 0 98948 29899
#47dfea 250752 259676 150260 211507 196844 198362 10653 151789 60159 98948 0 71126
#00808f 132324 99738 98638 62084 226941 128070 40571 28445 121516 29899 71126 0

Particular requirements for each theme

For modus-operandi the intent was two-fold: (i) to reduce the overall intensity of most values without undermining their utility and (ii) to fine-tune specific colour combinations. Here is a comparison of old and new value pairs.

Modus Operandi graph (org-habit) colour pair distance in hueness:

      Distance
Old reds #ef6f79 #ff9f9f 13010
New reds #ef7969 #ffaab4 21961
Old greens #49d239 #6dec6d 13006
New greens #4faa09 #8fef00 29455
Old yellows #efec08 #dbff4e 13003
New yellows #ffcf00 #f9ff00 9396
Old blues #55a2f0 #7fcfff 13044
New blues #7090ff #9fc6ff 17390
Old magentas #ba86ef #e7afff 13090
New magentas #e07fff #fad0ff 28445
Old cyans #30d3f0 #6fefff 13017
New cyans #70d3f0 #afefff 13964

Notice that the yellow pair have been brought closer, despite the general trend to amplify the distinction between the values in each pair (well “amplify” may give the wrong impression as these are subtle tweaks—check again the first screenshot). This reduction may seem like an error, though it is part of the design (and why we must look at things holistically without following every rule to the letter while remaining oblivious to its spirit). Consider that the old subtle yellow (#dbff4e) was too close to the subtle red (#ff9f9f). Same for the subtle yellow next to the intense red. Those combinations appear next to each other and thus need to be accounted for. The compromise we had to make was to marginally reduce the distance betweent the new subtle red (#ffaab4) and the new intense yellow (#ffcf00). The following table illustrates this point:

Modus Operandi select graph (org-habit) colour pairs distance in hueness:

      Distance
Old subtle yellow vs subtle red #dbff4e #ff9f9f 54670
New subtle yellow vs subtle red #f9ff00 #ffaab4 94926
Old subtle yellow vs intense red #dbff4e #ef6f79 88678
New subtle yellow vs intense red #f9ff00 #ef7969 95386
Old subtle red vs intense yellow #ff9f9f #efec08 71347
New subtle red vs intense yellow #ffaab4 #ffcf00 70826

The new colours work better in general, but let us not belabour the point.

For modus-vivendi the task was somewhat more straightforward as all we had to do was to tone down the colours while paying attention to inter-pair relations. This is paradoxically more tricky to convey with raw data as one has to compare the visuals of the before and after states (it is easier to discern colours on a dark backdrop because “colour” is an expression of light). Suffice to say that we have eliminated any exaggerations without making compromises on the relevant functionality. Still, we managed to amplify the colour distance almost across the board while avoiding any overshoot in intensity. This table compares each pair of intense and subtle hues:

Modus Vivendi graph (org-habit) colour pair distance in hueness:

      Distance
Old reds #af0404 #801f2f 13197
New reds #b52c2c #702020 13285
Old greens #24ba2f #0f8f07 13063
New greens #24bf00 #007800 23026
Old yellows #ffd03e #d7d800 13021
New yellows #f7ef00 #b08600 58819
Old blues #5f8fff #2f50c8 29588
New blues #2fafef #1f2f8f 93048
Old magentas #af7bee #7f59cf 13011
New magentas #bf94fe #5f509f 64611
Old cyans #47dcfa #0bc0df 13086
New cyans #47dfea #00808f 71126

Now here comes the counter-intuitive part. For modus-operandi the yellow+red combinations had to be rendered more clear. Whereas for modus-vivendi we had to bring colours closer to each other to avoid exaggerations in intensity. Remember that modus-vivendi has a black background, so any extra intensity is immediately noticeable.

Modus Vivendi select graph (org-habit) colour pairs distance in hueness:

      Distance
Old subtle yellow vs subtle red #d7d800 #801f2f 163539
New subtle yellow vs subtle red #b08600 #702020 55042
Old subtle green vs subtle blue #0f8f07 #2f50c8 126247
New subtle green vs subtle blue #007800 #1f2f8f 84053

Tricky though perhaps dull

I understand this is not an interesting topic and it probably is too difficult to relate to the various data points without visualising them and comparing the before and after states. Furthermore, data can be deceptive and I have always maintained that theme development stands at the intersection of science and art (at least for the purposes of conforming with the rigorous accessibility standards of the Modus themes).

That granted, I wanted to shed light on the “behind the scenes” work that is not immediately obvious when one checks a diff that introduces some seemingly trivial tweaks like #49d239 -> #49c029 or #7fcfff -> #8fbfff.