---
title: "Qualpal Alternatives"
author: "Johan Larsson"
date: "`r Sys.Date()`"
bibliography: qualpal.bib
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Qualpal Alternatives}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
## Background
Finding a good color palette for categorical data is a problem
that has been tackled previously. In this vignette, we will compare
the qualpal library with several alternatives, namely
- [Colorgorical](https://gramaz.io/colorgorical/)
- [Palletailor](https://www.yunhaiwang.net/infoVis2020/palettailor/color-palette-generation.html)
- [iWantHue](https://medialab.github.io/iwanthue/)
- [Distinctipy](https://distinctipy.readthedocs.io/en/latest/)
- [Glasbey](https://glasbey.readthedocs.io/en/latest/)
```{r functions, echo = FALSE}
plot_analysis <- function(pal, metric = "ciede2000", cvd = "normal", ...) {
pal_result <- analyze_palette(pal, metric = metric)[[cvd]][["min_distances"]]
colors <- names(pal_result)
res <- as.numeric(pal_result)
opar <- par(no.readonly = TRUE)
par(mar = c(5.1, 4.1, 3, 0.1))
barplot(
res,
names.arg = colors,
col = pal,
ylim = c(0, 50),
las = 2,
ylab = "Color difference (CIEDE2000)",
...
)
par(opar)
}
```
## iWantHue
[iWantHue](https://medialab.github.io/iwanthue/) served as the
main inspiration for the qualpal package. It is a web-based (Javascript) tool
that allows users to generate color palettes for categorical data.
Like qualpal, it generates a set of points. But unlike qualpal, it uses
a $k$-means clustering or force-vector algorithm to generate the final palette.
It also only supports input in the LCHab color space.
To compare the two, we will start by generating a palette with iWantHue.
I used the web interface, but the Javascript code to generate the palette
is as follows (which the app gratefully provides):
```javascript
var colors = paletteGenerator.generate(
5, // Colors
function(color){ // This function filters valid colors
var hcl = color.hcl();
return (hcl[0]>=50 || hcl[0]<=290)
&& hcl[1]>=0 && hcl[1]<=134
&& hcl[2]>=25 && hcl[2]<=85;
},
false, // Using Force Vector instead of k-Means
50, // Steps (quality)
false, // Ultra precision
'Default' // Color distance type (colorblindness)
);
// Sort colors by differenciation first
colors = paletteGenerator.diffSort(colors, 'Default');
```
To compare the two, we now run `qualpal()` with same input.
```{r}
library(qualpalr)
hcl_space <- list(h = c(50, 290), c = c(0, 134), l = c(25, 85))
pal <- qualpal(n = 5, hcl_space)
pal
```
Finally, to compare the two palettes, we can use the `analyze_palette()` function
from **qualpalr**, which returns a list of analyses of the palettes,
one for each type of color vision deficiency (CVD), with
a configurable severity.
I've hard-coded the iWantHue palette here, and will compare it
to the qualpal palette we just generated. Here, we show the
color difference values for the normal vision analysis.
```{r}
iwanthue <- c(
"#5f683c",
"#5c87b5",
"#79bf4b",
"#c79041",
"#7bb99d"
)
analyze_palette(pal$hex, metric = "ciede2000")$normal
analyze_palette(iwanthue, metric = "ciede2000")$normal
```
The result shows a matrix of color differences for each color in the palette,
as well as a vector of the minimum color difference for each color in the palette
and a color difference with respect to the background (if used, which
we did not in this case).
For a better visual comparison, we can plot the color difference values for
each color in the palette.^[The code for these plots can be found in the source
code of this vignette.]
```{r plot-iwanthue, echo = FALSE}
plot_analysis(pal$hex, main = "Qualpal")
plot_analysis(iwanthue, main = "iWantHue")
```
As you can see, qualpal produces a palette that's much more distinct,
with higher color difference values across the board.
Like qualpal, iWantHue also supports some adaptation for Color Vision Deficiency (CVD),
but much less granular than qualpal. In fact I'm not sure how it works or what
kind of CVD adaptation it supports.
## Colorgorical
[Colorgorical](https://gramaz.io/colorgorical/) is a web-based tool
for generating color palettes for categorical data. It uses an iterative
method with random sampling to incrementally build the palette. The
algorithm is described in the paper by @gramazio2016.
It's based on Python under the hood and the code is available [on
GitHub](https://github.com/connorgr/colorgorical). The web app is, at the time
of writing, down, but is otherwise accessible via a link on
. Prior to it going down, however, I generated
a palette using the same HCL color space as for the iWantHue example above. By
default, Colorgorical also tries to maximize the name difference of the colors,
but I have turned that off for the sake of this comparison. The resulting
palette is as follows:
```{r}
colorgorical <- c("#b4ddd4", "#9b233d", "#2eece6", "#f82387", "#32a190")
```
```{r plot-colorgorical, echo = FALSE}
#| fig.cap: Colorgorical vs Qualpal
plot_analysis(pal$hex, main = "Qualpal")
plot_analysis(colorgorical, main = "Colorgorical")
```
As before, we see that the qualpal palette is considerably more distinct.
## Glasbey
[Glasbey](https://glasbey.readthedocs.io/en/latest/) is a python library for
generating color palettes for categorical data. It is based on academic work by
@glasbey2007 and uses simulated annealing and the CIE76 metric to compute color
differences. Here we used a slightly modified version of the colorspace above
to generate a palette with 8 colors.
```python
import glasbey
pal = glasbey.create_palette(
palette_size=8,
hue_bounds=(-90, 90),
chroma_bounds=(50, 100),
lightness_bounds=(30, 70),
)
```
The resulting palette as well as the palette generated by qualpal
are as follows:
```{r}
glasbey <- c(
"#b20000",
"#caa200",
"#ff8ab2",
"#aa145d",
"#9e5d00",
"#ff7910",
"#ff008a",
"#ca5d55"
)
pal2 <- qualpal(8, list(h = c(-90, 90), c = c(50, 100), l = c(30, 70)))
```
```{r plot-glasbey, echo = FALSE}
plot_analysis(pal2$hex, main = "Qualpal")
plot_analysis(glasbey, main = "Glasbey")
```
One option that's shared by both qualpal and Glasbey is the
possibility to extend an existing palette with more colors.
Glasbey has a function for this, which we can use to extend
a palette taken from the Glasbey documentation.
```python
base_pal = ["#2a3e63", "#7088b8", "#fcaf3e", "#b87088"]
pal_extend = glasbey.extend_palette(
base_pal,
palette_size=8,
hue_bounds=(50,280),
chroma_bounds=(50, 80),
lightness_bounds=(25, 70),
)
```
To do this in qualpal, we can use the `extend` argument
to `qualpal()`, which will extend the base palette.
```{r}
glasbey_extend <- c(
"#2a3e63",
"#7088b8",
"#fcaf3e",
"#b87088",
"#417108",
"#45dfa6",
"#6d0c49",
"#dfaaff"
)
base_pal <- c(
"#2a3e63",
"#7088b8",
"#fcaf3e",
"#b87088"
)
pal_extend <- qualpal(
8,
list(h = c(50, 280), c = c(50, 80), l = c(25, 70)),
extend = base_pal
)
```
```{r plot-glasbey-extend, echo = FALSE}
plot_analysis(pal_extend$hex, main = "Qualpal (extended)")
plot_analysis(glasbey_extend, main = "Glasbey (extended)")
```
Both palettes are quite distinct, but the qualpal palette
keeps a better color difference across the board.
As in qualpal, glasbey also supports some adaptation for Color Vision Deficiency (CVD).
Unlike qualpal, however, it only supports adaptation for a single type of CVD at a time,
but also supports a severity parameter to control the strength of the adaptation.
We generated a palette with 6 colors, adapted for protanomaly
with a severity of 100% (the maximum), using the following code:
```python
cvd_pal = glasbey.create_palette(
palette_size=6,
colorblind_safe=True,
cvd_type="protanomaly",
cvd_severity=100,
hue_bounds=(50,280),
chroma_bounds=(50, 80),
lightness_bounds=(25, 70),
)
```
The resulting palette and the qualpal palette is given below.
```{r}
glasbey_cvd <- c(
"#512deb",
"#317d00",
"#92d200",
"#39ceff",
"#00ba92",
"#0082b2"
)
cvd_pal <- qualpal(
6,
list(h = c(50, 280), c = c(50, 80), l = c(25, 70)),
cvd = c(protan = 1)
)
```
```{r plot-glasbey-cvd, echo = FALSE}
plot_analysis(cvd_pal$hex, main = "Qualpal (CVD)", cvd = "protan")
plot_analysis(glasbey_cvd, main = "Glasbey (CVD)", cvd = "protan")
```
## Distinctipy
[Distinctipy](https://distinctipy.readthedocs.io/en/latest/) is another python
library for generating color palettes for categorical data. It randomly
samples colors from the RGB color space and then computes pairwise color
differences with the CIE76 metric, repeats this for a set number of attempts,
and then picks the best palette among the attempts.
I ran the following code to generate a 9-color palette with the
default settings, included colors from all over
the spectrum.[^2] There are no options other than a pastel color setting for
changing this.
[^2]: There is only a pastels filter for limiting the colors.
```python
import distinctipy
dpal = distinctipy.get_colors(9, rng = 0, n_attempts=1000)
[distinctipy.get_hex(c) for c in dpal]
```
```{r}
distinctipy <- c(
"#cf02fd",
"#00ff00",
"#ff8000",
"#01aced",
"#0905c3",
"#538337",
"#80ff80",
"#a80549",
"#b28cd2"
)
pal3 <- qualpal(9, list(h = c(0, 360), s = c(0, 1), l = c(0, 1)))
```
```{r plot-distinctipy, echo = FALSE}
plot_analysis(pal3$hex, main = "Qualpal")
plot_analysis(distinctipy, main = "Distinctipy")
```
We see that qualpal produces a more distinct palette and that the palette from
distinctipy is quite uneven, with two colors being very similar to each
other.
## Palettailor
[Palettailor](https://www.yunhaiwang.net/infoVis2020/palettailor/color-palette-generation.html)
is another web-based tool for generating color palettes for categorical data.
It is based on academic work by @lu2021 and, like Glasbey, it uses
a simulated annealing approach, albeit with the CIEDE2000 metric instead.
Compared to qualpal and the
other alternatives here, it takes a different approach to generating
color palettes. It optimizes the color palette for a specific
plot design by trying to maximize the distinctiveness of the colors
that are close to each other in the plot.For that reason, the comparisons here
are not entirely fair, but if we turn off optimizations for name difference and
point distinctiveness (in the web app), we can still compare the generated
palette with qualpal.
Here is the result from the Palettailor web app, using a 10-color palette with the
color space defined in the `hcl_space` object. Palettailor does not support
controls for saturation, so we use the full range in qualpal to
facilitate the comparison. Palettailor also forces optimization with a
background color in mind, although choices are restricted to either white or
black, so we set the `bg` argument to `"white"` in qualpal here.
```{r}
palettailor <- c(
"#4ca600",
"#5dfbbf",
"#eda59f",
"#32998d",
"#605ec4",
"#0099ef",
"#e950e8",
"#e1f32e",
"#936411",
"#e9003e"
)
hsl_space <- list(h = c(0, 360), s = c(0, 1), l = c(0.45, 0.95))
pal3 <- qualpal(10, hsl_space, bg = "white")
```
```{r plot-palettailor, echo = FALSE}
plot_analysis(pal3$hex, main = "Qualpal")
plot_analysis(palettailor, main = "Palettailor")
```
We again see that qualpal produces a more distinct palette, but this time the
difference is not as pronounced as with the other alternatives.
## References