Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create a colour blind test with ggplot

Tags:

r

ggplot2

I would like to create a colour blind test, similar to that below, using ggplot.

enter image description here

The basic idea is to use geom_hex (or perhaps a voronoi diagram, or possibly even circles as in the figure above) as the starting point, and define a dataframe that, when plotted in ggplot, produces the image.

We would start by creating a dataset, such as:

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

then plot this:

ggplot(df, aes(x, y)) +
  geom_hex() + 
  coord_equal() +
  scale_fill_gradient(low = "red", high = "green", guide = FALSE) +
  theme_void()

which gives the image below:

enter image description here

The main missing step is to create a dataset that actually plots a meaningful symbol (letter or number), and I'm not sure how best to go about this without painstakingly mapping the coordinates. Ideally one would be able to read in the coordinates perhaps from an image file.

Finally, a bit of tidying up could round the plot edges by removing the outlying points.

All suggestions are very welcome!

EDIT

Getting a little closer to what I'm after, we can use the image below of the letter 'e':

enter image description here

Using the imager package, we can read this in and convert it to a dataframe:

img <- imager::load.image("e.png")
df <- as.data.frame(img)

then plot that dataframe using geom_raster:

ggplot(df, aes(x, y)) +
  geom_raster(aes(fill = value)) +
  coord_equal() +
  scale_y_continuous(trans = scales::reverse_trans()) +
  scale_fill_gradient(low = "red", high = "green", guide = FALSE) +
  theme_void()

enter image description here

If we use geom_hex instead of geom_raster, we can get the following plot:

ggplot(df %>% filter(value %in% 1), aes(x, y)) +
  geom_hex() + 
  coord_equal() +
  scale_y_continuous(trans = scales::reverse_trans()) +
  scale_fill_gradient(low = "red", high = "green", guide = FALSE) +
  theme_void()

enter image description here

so, getting there but clearly still a long way off...

like image 642
Jonny Avatar asked Apr 18 '17 15:04

Jonny


People also ask

Is Ggplot colorblind friendly?

More specifically, ggpubfigs contains six color palettes that are colorblind friendly and aim to increase the accessibility of scientific figures and eight themes that modify 21 parameters of a default ggplot2 figure.

How to manually set colors in ggplot?

To specify colors of the bar in Barplot in ggplot2, we use the scale_fill_manual function of the ggplot2 package. Within this function, we need to specify a color for each of the bars as a vector. We can use colors using names as well as hex codes.

How to change colour in ggplot in R?

Change ggplot colors by assigning a single color value to the geometry functions ( geom_point , geom_bar , geom_line , etc). You can use R color names or hex color codes. Set a ggplot color by groups (i.e. by a factor variable). This is done by mapping a grouping variable to the color or to the fill arguments.

How to change the color of a ggplot plot?

# Use deutan, protan or tritan functions [in colorspace] p2 <- edit_colors (p, deutan, sev = 0.7 ) cowplot::plot_grid (p, p2) The colorblindr package comes with a color scale that works better for people with color-vision deficiency. You can use it to modify a ggplot color.

Is there a way to ensure plots will be color blind?

Lastly is there a way to ensure my plots will be color blind frinedly? Packages viridis and viridisLite both target palettes for various types of color blindness.

How to use the ColorBrewer palette in ggplot?

Many graphics packages allow you to easily make use of the ColorBrewer palettes. In ggplot2, this is done with the command. Plot of diamond price as a function of weight using the ColorBrewer’s Dark2 palette and assigning colors based on category.

Can I create plots and figures using default color palettes?

Although there are many outstanding tools for creating beautiful plots, practically all of them have default color palettes that can present decoding challenges for individuals with color vision deficiencies. This is an introduction to creating plots and figures using color palettes that are more accessible.


1 Answers

Here's an approach for creating this plot:

enter image description here


Packages you need:

library(tidyverse)
library(packcircles)

Get image into a 2D matrix (x and y coordinates) of values. To do this, I downloaded the .png file of the e as "e.png" and saved in my working directory. Then some processing:

img <- png::readPNG("e.png")

# From http://stackoverflow.com/questions/16496210/rotate-a-matrix-in-r
rotate <- function(x) t(apply(x, 2, rev))

# Convert to one colour layer and rotate it to be in right direction
img <- rotate(img[,,1])

# Check that matrix makes sense:
image(img)

enter image description here

Next, create a whole lot of circles! I did this based on this post.

# Create random "circles"
# *** THESE VALUES WAY NEED ADJUSTING
ncircles <- 1200
offset   <- 100
rmax     <- 80
x_limits <- c(-offset, ncol(img) + offset)
y_limits <- c(-offset, nrow(img) + offset)

xyr <- data.frame(
  x = runif(ncircles, min(x_limits), max(x_limits)),
  y = runif(ncircles, min(y_limits), max(y_limits)),
  r = rbeta(ncircles, 1, 10) * rmax)

# Find non-overlapping arrangement
res <- circleLayout(xyr, x_limits, y_limits, maxiter = 1000)
cat(res$niter, "iterations performed")
#> 1000 iterations performed

# Convert to data for plotting (just circles for now)
plot_d <- circlePlotData(res$layout)

# Check circle arrangement
ggplot(plot_d) + 
  geom_polygon(aes(x, y, group=id), colour = "white", fill = "skyblue") +
  coord_fixed() +
  theme_minimal()

enter image description here

Finally, interpolate the image pixel values for the centre of each circle. This will indicate whether a circle is centered over the shape or not. Add some noise to get variance in colour and plot.

# Get x,y positions of centre of each circle
circle_positions <- plot_d %>%
  group_by(id) %>% 
  summarise(x = min(x) + (diff(range(x)) / 2),
            y = min(y) + (diff(range(y)) / 2))

# Interpolate on original image to get z value for each circle
circle_positions <- circle_positions %>% 
  mutate(
    z = fields::interp.surface(
      list(x = seq(nrow(img)), y = seq(ncol(img)), z = img),
      as.matrix(.[, c("x", "y")])),
    z = ifelse(is.na(z), 1, round(z))  # 1 is the "empty" area shown earlier
  )

# Add a little noise to the z values
set.seed(070516)
circle_positions <- circle_positions %>%
  mutate(z = z + rnorm(n(), sd = .1))

# Bind z value to data for plotting and use as fill
plot_d %>% 
  left_join(select(circle_positions, id, z)) %>% 
  ggplot(aes(x, y, group = id, fill = z)) + 
  geom_polygon(colour = "white", show.legend = FALSE) +
  scale_fill_gradient(low = "#008000", high = "#ff4040") +
  coord_fixed() +
  theme_void()
#> Joining, by = "id"

enter image description here

To get colours right, tweak them in scale_fill_gradient

like image 111
Simon Jackson Avatar answered Sep 29 '22 15:09

Simon Jackson