Colors

Data Visualization and Exploration

Ozan Kahramanoğulları

Finding the cherries is much easier with color vision.

A little bit of theory

The visual system

The retina of the eye has two kinds of receptors:

  • rods: black and white vision in low light.

Little role in the preception of colors.

  • cones: color vision in normal light.

Concentrated around the visual axis.

Responsivity of human cone cells

The visual system

Image from Ware (2008)

How does this impact our work?

Showing small blue text on a black background is a bad idea. There is insufficient luminance contrast.

Showing small blue text on a black background is a bad idea. There is insufficient luminance contrast.

This effect is due to the low sensitivity of cones to blue wavelengths.

How does this impact our work?

Showing small yellow text on a white background is a bad idea. There is insufficient luminance contrast.

Showing small yellow text on a white background is a bad idea. There is insufficient luminance contrast.

Yellow wavelengths excite two different types of cones, making it almost as light as pure white.

Opponent process theory

The brain combines signals from different cones to build three channels:

  • Red-Green
  • Yellow-Blue
  • Black-White

Unique hues

When there is a strong positive or negative signal on one of the three channels, and a neutral one on the other two,

we have “special” colors.

In most languages, these six colors are identified as the basic ones.

[Brent Berlin and Paul Kay, 1969. Basic Color Terms: Their Universality and Evolution]

Color blindness

A considerable number of people is missing one or more color channels.

Most commonly, the missing channel is the red-green one.

When designing a color scale we need to take this into account in order to be inclusive.

Image from Ware (2008)

Contrast

The effect of contrast is distortion of a patch of color in a way that increases the difference between a color and its surroundings.

We talk about luminance contrast when it occurs on the black-white channel, and chromatic when it occurs on the other two channels.

This phenomenon is called simultaneous contrast, where the background interferes with our perception of a patch of color.

It can create problems when reading values from a graphic.

Spatial detail

The luminance channel is more effective at conveying spatial details.

Shapes from shades

We perceive three dimensional surfaces through changes of luminance, rather than through chromatic changes.

Saturation

The more vivid a color, the more saturated it is said to be.

More saturated colors are those that have strong signals on one or both of the chromatic channels.

Saturation

The maximum saturation for a given hue varies with luminance.

When colors are dark, the difference between cone signals on chromatic channels is smaller.


When colors are light, there is a reduction in saturation due to the color reproduction technology rather than perception.

Color segmentation

Remember the discriminability issue? Here it is at play!

Color spaces

To work with colors, we need to agree on a representation. Such representations of colors are called color spaces.

The RGB color space

\((red, green, blue)\)

In computer representations, each component goes from 0 to 255.

Why red, green and blue?

This is the set of colors with the widest gamut, that is the set of all colors that can be defined by means of combining the three primary colors, e.g., (235, 91, 52) or #eb5b34.

Color spaces

RGB is computationally convenient.

However, it is a poor fit for how our eyes work: it is not perceptually accurate.

In a perceptually uniform color space,

colors with the same perceptual distance are at the same distance in the space.

HCL color space

HCL (Hue-Chroma-Luminance): color space models that are designed to accord with human perception of color.

HCL - Hue

What we intuitively think of as pure colors:

HCL - Chroma

The “colorness” or intensity of the color.

From “vivid” to “muted”:

HCL - Luminance

Intuitively, the brightness of the color, or the amount of black mixed into the color.

From “dark” to “light”:

Designing a color space for color picking is a about finding which tradeoffs to make. In particular, independent control of hue, lightness and chroma can not be achieved in a color space that also maps sRGB to a simple geometrical shape.

https://bottosson.github.io/posts/colorpicker/

Interacting with the HSL color space

Visit https://bottosson.github.io/misc/colorpicker

Interacting with the HSL color space (2)

Visit https://www.hsluv.org/

HCL - mapping data types


  • Qualitative



  • Sequential


  • Sequential

Sequential mapping pitfalls:

When the bars touch, the dark areas seem darker and the light areas lighter.

Source: https://socviz.co/lookatdata.html#perception-and-data-visualization

Sequential mapping pitfalls:

Source: https://socviz.co/lookatdata.html#perception-and-data-visualization

Sequential mapping with Chroma and Luminance

Because of the effects above, it is best not to encode more than 3 to 5 levels using the Chroma or Luminance channel, if we want our readers to be able to distinguish the levels (discriminability).

Fortunately, almost all of the work has been done for us already.

Different color spaces have been defined and standardized in ways that account for uneven or nonlinear aspects of human color perception.

Our decisions about color will focus more on when and how it should be used.

Colormaps

Colormaps

A colormap specifies a mapping between colors and data values

  • Categorical

  • Ordered

    • Sequential
    • Diverging
  • Continuous vs. discrete

Categorical color maps

  • Use mainly hue to encode different categories

There are mainly two things to pay attention.

  • We can distinguish just about 12 bins of color, better to stick to at most 6.

  • Luminance contrast: we need our colored marks to “stand out” from the background.

Encoding with color, an example

Encoding with color, an example

Categorical color maps - a bad example

Source: Munzner, ch.10, fig 10.8.

Ordered colormaps: sequential

Ordered colormaps: diverging

Encoding with color, an example

Encoding with color, an example

Encoding with color, an example

Encoding with color, an example

Encoding with color, an example

Encoding with color, an example

Encoding with color, an example

The rainbow color map

It’s often used to encode ordered data, why is it confusing?

The rainbow color map

The rainbow color map

Monotonically increasing luminance colormap

The rainbow color map

The rainbow color map

An overview of R colormaps

You don’t need to create your colormaps from scratch.

R supports a rich collection of colormaps ready for use.

library(ggplot2)
library(viridis)
library(colorspace)

hcl_palettes("Qualitative", plot=T, n=5)

The palettes in the RColorBrewer package

hcl_palettes("Sequential (single-hue)", plot=T)

hcl_palettes("Sequential (multi-hue)", plot=T)

hcl_palettes("Diverging", plot=T)

Bivariate color palettes

Can be used to encode two different variables with color: use with extreme care!

pal1 <- tibble(c1 = c("#f5f5f5","#EE744B","#9F1401"), 
               dim1 = c("low", "mid", "high"))
pal2 <- tibble(c2 = c("#f5f5f5","#878FD3","#07489C"), 
               dim2 = c("low", "mid", "high"))
crossing(pal1, pal2) %>%
  mutate(
    color = hex(mixcolor(0.5, hex2RGB(c1), hex2RGB(c2))),
    across(starts_with("dim"), ~ factor(.x, 
                                        levels = c("low", "mid", "high"), 
                                        ordered=T))
  ) %>%
  ggplot(aes(x=dim1, y=dim2, fill=color)) +
  geom_tile() +
  scale_fill_identity() +
  theme_classic() +
  theme(axis.line = element_blank())

Bivariate color palettes

Bivariate color palettes

Can be used to encode two different variables with color: use with extreme care!

pal1 <- tibble(c1 = c("#f5f5f5","#EE744B","#9F1401"), 
               dim1 = c("low", "mid", "high"))
ggplot(pal1,aes(x=dim1,y=1,fill=c1)) + 
  geom_tile(show.legend = FALSE) +
  theme_void() + 
  theme(axis.text.x=element_text(size=24))

pal2 <- tibble(c2 = c("#f5f5f5","#878FD3","#07489C"), 
               dim2 = c("low", "mid", "high"))
ggplot(pal2,aes(x=dim2,y=1,fill=c2)) + 
  geom_tile(show.legend = FALSE) +
  theme_void() + 
  theme(axis.text.x=element_text(size=24))

Bivariate color palettes

Can be used to encode two different variables with color: use with extreme care!

pal1 <- tibble(c1 = c("#f5f5f5","#EE744B","#9F1401"), 
               dim1 = c("low", "mid", "high"))
pal2 <- tibble(c2 = c("#f5f5f5","#878FD3","#07489C"), 
               dim2 = c("low", "mid", "high"))
crossing(pal1, pal2) 
# A tibble: 9 × 4
  c1      dim1  c2      dim2 
  <chr>   <chr> <chr>   <chr>
1 #9F1401 high  #07489C high 
2 #9F1401 high  #878FD3 mid  
3 #9F1401 high  #f5f5f5 low  
4 #EE744B mid   #07489C high 
5 #EE744B mid   #878FD3 mid  
6 #EE744B mid   #f5f5f5 low  
7 #f5f5f5 low   #07489C high 
8 #f5f5f5 low   #878FD3 mid  
9 #f5f5f5 low   #f5f5f5 low  

Bivariate color palettes

Can be used to encode two different variables with color: use with extreme care!

pal1 <- tibble(c1 = c("#f5f5f5","#EE744B","#9F1401"), 
               dim1 = c("low", "mid", "high"))
pal2 <- tibble(c2 = c("#f5f5f5","#878FD3","#07489C"), 
               dim2 = c("low", "mid", "high"))
crossing(pal1, pal2) %>%
  mutate(
    color = hex(mixcolor(0.5, hex2RGB(c1), hex2RGB(c2))),
    across(starts_with("dim"), ~ factor(.x, 
                                        levels = c("low", "mid", "high"), 
                                        ordered=T))
  ) 
# A tibble: 9 × 5
  c1      dim1  c2      dim2  color  
  <chr>   <ord> <chr>   <ord> <chr>  
1 #9F1401 high  #07489C high  #532E4F
2 #9F1401 high  #878FD3 mid   #93526A
3 #9F1401 high  #f5f5f5 low   #CA857B
4 #EE744B mid   #07489C high  #7B5E74
5 #EE744B mid   #878FD3 mid   #BB828F
6 #EE744B mid   #f5f5f5 low   #F2B5A0
7 #f5f5f5 low   #07489C high  #7E9FC9
8 #f5f5f5 low   #878FD3 mid   #BEC2E4
9 #f5f5f5 low   #f5f5f5 low   #F5F5F5

Bivariate color palettes

Can be used to encode two different variables with color: use with extreme care!

pal1 <- tibble(c1 = c("#f5f5f5","#EE744B","#9F1401"), 
               dim1 = c("low", "mid", "high"))
pal2 <- tibble(c2 = c("#f5f5f5","#878FD3","#07489C"), 
               dim2 = c("low", "mid", "high"))
crossing(pal1, pal2) %>%
  mutate(
    color = hex(mixcolor(0.5, hex2RGB(c1), hex2RGB(c2))),
    across(starts_with("dim"), ~ factor(.x, 
                                        levels = c("low", "mid", "high"), 
                                        ordered=T))
  ) %>%
  ggplot(aes(x=dim1, y=dim2, fill=color)) +
  geom_tile() +
  scale_fill_identity() +
  theme_classic() +
  theme(axis.line = element_blank())

Colormaps in R and ggplot

Available colormaps

In R you have access to pre-made colormaps by using the following libraries, among others.

library(RColorBrewer)
library(colorspace)
library(viridis)

Available colormaps from RColorBrewer

The command will plot all available palettes in the package.

library(RColorBrewer)
display.brewer.all()

There are three types of palettes available.

They can be selected by using the type parameter in the command above:

  • seq, div for ordered data
  • qual for categorical data

Available colormaps from RColorBrewer

To select a particular palette, you can use:

brewer.pal(5, "Dark2")
[1] "#1B9E77" "#D95F02" "#7570B3" "#E7298A" "#66A61E"

where you specify the number of colors you need and the name of the palette.

[Pro tip: visit http://colorbrewer2.org/

Available colormaps in Viridis

There are eight colormaps in viridis:

  • “magma” (A)
  • “inferno” (B)
  • “plasma” (C)
  • “viridis” (D)
  • “cividis” (E)
  • “rocket” (F)
  • “mako” (G)
  • “turbo” (H)

Available colormaps in Viridis

viridis(n=5, option="D")
[1] "#440154FF" "#3B528BFF" "#21908CFF" "#5DC863FF" "#FDE725FF"

All these palettes are ordered.

Available colormaps in Viridis

viridis

library(hexbin)

dat <- data.frame(
  x = rnorm(10000), 
  y = rnorm(10000))

ggplot(dat, aes(x = x, y = y)) +
  geom_hex() + 
  coord_fixed() +
  scale_fill_gradientn(
    colours = viridis(256, option = "D"))

Available colormaps in Viridis

viridis

Available colormaps in Viridis

magma

Available colormaps in Viridis

inferno

Available colormaps in Viridis

plasma

Available colormaps in Viridis

cividis

Available colormaps in Viridis

rocket

Available colormaps in Viridis

mako

Available colormaps in Viridis

turbo

Available colormaps in colorspace

You can list all the colormaps in the colorspace package using:

hcl_palettes()

This command returns a whole lot of results, divided in:

  • qualitative palettes
  • ordered palettes:
    • Sequential (both single and multi hue)
    • Diverging

Available colormaps in colorspace

There are also specialized functions to get these palettes.

qualitative_hcl()
sequential_hcl()
diverging_hcl()

Note that some palettes come from the RColorBrewer and Viridis packages.

Easy visualization of palettes

The colorspace package provides the swatchplot function to visualize palettes:

swatchplot(
  "Viridis palette" = viridis(5),
  "RColorBrewer" = brewer.pal(5, "Dark2"),
  "colorspace palette" = sequential_hcl(5, palette="Peach"),
  "manual colors" = c("#441242", "#0000ff", "#289003")
)

Creating your own color palettes

You can use the hcl_wizard() function.

It requires to have shiny installed:

renv::install("shinyjs")

I want Hue!

https://medialab.github.io/iwanthue/

HCL picker

http://tristen.ca/hcl-picker/

Visualizing palette properties

colormap <- brewer.pal(5, "Pastel1")
specplot(colormap)

colormap <- sequential_hcl(5)
specplot(colormap)

Visualizing palette properties (2)

colormap <- brewer.pal(5, "Pastel1")
hclplot(colormap)

colormap <- sequential_hcl(5)
hclplot(colormap)

Visualizing palette properties (3)

The contrast ratio, as defined by the World Wide Web Consortium, is a number quantifying the contrast with the background. It should be higher than 4 for text, as a general guideline.

colormap <- brewer.pal(5, "Pastel1")
contrast_ratio(colormap, "white", plot=T)

colormap <- sequential_hcl(5)
contrast_ratio(colormap, "white", plot=T)

Using palettes with ggplot2

Built in

scale_<aes>_<type>()

Where <aes> is either fill or color and <type> is one of

  • continuous: continuous color map
  • discrete: categorical color map
  • manual: manually specify colors
  • identity: use the values as color codes

Using palettes with ggplot2: default

ggplot(iris, 
       aes(x=Sepal.Length, 
           y=Sepal.Width, 
           color=Species)) +
  geom_point() +
  
  scale_color_discrete() +
  
  theme_bw() +
  theme(legend.position = 'top')

Using palettes with ggplot2: default

ggplot(iris, 
       aes(x=Sepal.Length, 
           y=Sepal.Width, 
           color=Species)) +
  geom_point() +
  
  #scale_color_discrete() +
  
  theme_bw() +
  theme(legend.position = 'top')

Using palettes with ggplot2: manual

ggplot(iris, 
       aes(x=Sepal.Length, 
           y=Sepal.Width, 
           color=Species)) +
  geom_point() +
  
  scale_color_manual(
    values = c(
      "setosa"     = "#ff0033",
      "versicolor" = "green",
      "virginica"  = "#0000ee"
    )
  ) +
  
  theme_bw() +
  theme(legend.position = 'top')

Using palettes with ggplot2: viridis

ggplot(iris, 
       aes(x=Sepal.Length, 
           y=Sepal.Width, 
           color=Species)) +
  geom_point() +
  
  scale_color_viridis(discrete=T,
                      option="D") +
  
  theme_bw() +
  theme(legend.position = 'top')

Using palettes with ggplot2: RColorBrewer

ggplot(iris, 
       aes(x=Sepal.Length, 
           y=Sepal.Width, 
           color=Species)) +
  geom_point() +
  
  scale_color_brewer(palette='Dark2',
                     type='qual') +
  
  theme_bw() +
  theme(legend.position = 'top')

Using palettes with ggplot2: colorspace

ggplot(iris, 
       aes(x=Sepal.Length, 
           y=Sepal.Width, 
           color=Species)) +
  geom_point() +
  
  scale_color_discrete_qualitative(
    palette='Dynamic'
  ) +
  
  theme_bw() +
  theme(legend.position = 'top')