Complementary Text Colors

Automatically pick a contrasting text color for any background!

By a show of hands, who here has heard of "YIQ"? Until very recently, I certainly had not. Well, today it's going to be the secret sauce that's going to let us accomplish something quite slick in virtually any BI application. There are plenty of applications for the below technique, but I'm going to focus on a single one for the sake of simplicity: heat maps.

Heat Maps

In recent years, heat maps have assumed a place of honor among my favorite types of visualizations. I especially like to employ them when a business use case requires that a lot of numbers be displayed all at once.

As you can see, the overall effect of this table is a bit overwhelming—and that's the case even with really small numbers! It may be the perfect type of visualization for an accountant who will immediately be exporting this table to Excel, but I would argue its sheer "density" makes it an ineffective way of conveying information for almost anyone else. A heat map with a neutral color (i.e. not green or red) is a great way of drawing viewers' attention to large numbers without making a qualitative (good/bad) statement about those numbers. I personally like shades of orange:

But something has been bothering me for a while. See those darker cells, the ones that represent larger numbers? The black text in those cells tends to blend into the background a bit. The problem may not be extreme, but I couldn't help wondering: wouldn't it be nice if I could ease the strain on users' eyes by showing the text in darker cells in a contrasting color, such as an off-white? The rest of this article is going to show you how to do exactly that, in a surprisingly simple way.


The Trouble With Colors

When I first set out to tackle this challenge, my instinct was to take whatever RGB color was used for a given heat map cell, and simply say: use a white font if its component parameters are greater than X (or less than X for that matter). I quickly discovered, however, that the RGB color scheme does not follow any rhyme or reason. A higher R value does not make something more red in the way we humans perceive color, for example.
Hmmm...OK...time for plan B: convert the RGB color to HSL. I figured I could use the S (saturation) and L (lightness) parameters to quickly tell if a color was "light" or "dark." That seemed more promising, but was ultimately also a dead end for a couple reasons. First, the formula to convert RGB to HSL on the fly is heavy enough that running it for multiple cells all at once would have quite a noticeably adverse effect on performance. Second, it turned out that the H (hue) component was critical in interpreting when a color "felt" light or dark. There's a fascinating reason behind this involving how the human eye perceives colors in different spectrums, but the bottom line was that the calculation would become even heavier if we had to run different scenarios for different hues. It's possible this approach would work with other BI tools, but it was definitely a non-starter with either QlikView or Qlik Sense.

YIQ to the Rescue

Bummer—there was no plan C. And then I stumbled upon YIQ, a color system used by TV broadcasters in certain countries. Here's an excerpt from Wikipedia:

YIQ is the color space used by the NTSC color TV system, employed mainly in North and Central America, and Japan[...] The YIQ system is intended to take advantage of human color-response characteristics.

And it turns out that any RGB value can easily be converted to YIQ! A programmer named Stephen Flannery leveraged YIQ to create the exact functionality I was looking for in JavaScript. Using YIQ's innate ability to correct for human perception in colors, the task of determining whether a color felt "light" or "dark" to human eyes was child's play. It was simply a matter of saying that any YIQ value greater than a given threshold (Flannery used 128) was "light" and anything below that threshold was "dark." This rule can be applied to any color hue with consistent results; a YIQ number already takes into account that we perceive different colors differently and flattens them out into a single dark/light scale between 0 and 255.

Applying YIQ in Qlik

Try saying that 10 times fast! So exactly how easy is it to convert RGB to YIQ? This easy:

Flannery used a threshold value of 128 in his script, which is the exact midway point in the YIQ scale. In his formula, YIQ background color values over 128 get black text, while values under 128 get white text. Through experimentation, I've found that I prefer 138, but that might very well be subjective. Depending on the exact layout of your visualization components, you may find that tweaking the threshold produces better results. I have included two examples in the below application, each with independent threshold adjustments.


For those who might not have a license of QlikView, here's the font color formula from the first example, where R, G, and B are variables set the by the sliders you see in the screenshot, and vExample1YIQ is a variable that is currently set to 138:

Seriously, that's it! I hope that, regardless of which BI platform you're using, you found this tip helpful. Please let me know in the comments below if you have any questions, or just want to share how you were able to apply the techniques from this article in real life!
This entry was posted in Tips & Tricks, Visualization and tagged . Bookmark the permalink.

7 Responses to Complementary Text Colors

  1. Hugo says:

    Excelente! Gracias por compartir

  2. Vegar Lie Arntsen says:

    Thanks for sharing. It is addressing an often met situation when working with heat maps. I’m reading this on my mobile device and I am now aching to open my computer and try out my self.


  3. Miguel García says:

    This is awesome. Thanks for sharing. Just last week I found myself needing something like this. Will definitely use it in the app I am working on currently.

  4. Andreas says:

    I’m just struggling with this issue.
    Have to try it out asap.
    Thanks! 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *

Notify via email when new comments are added

Blog Home