How to plot filled points with multicolored borders in ggplot (white black contrast)

Viewed 32

I was recently attempting to plot some filled points with an additional white border around the standard black border. I was unable to find a posted solution that did not rely on custom point shapes but still plotted all components of each point together.

After far too much time, I came up with the following solution. This solution works by duplicating each point and plotting a slightly larger white point behind it. This is similar to the solutions listed here (R ggplot2: How to draw geom_points that have a solid color and a transparent stroke and are colored depending on color?). However, my solution allows both borders to overlap adjacent points rather than plotting one set of borders behind the other.

Reproducible Example:

## Create test dataframe from built in mpg data frame and add scatter

test_mpg = mpg
test_mpg$displ = test_mpg$displ + runif(length(test_mpg$displ), min = 0, max = 0.5) # Add random values between 0 and 0.5 to test_mpg$displ
test_mpg$cty = test_mpg$cty + runif(length(test_mpg$displ), min = 0, max = 5) # Add random values between 0 and 5 to test_mpg$cty

### Add second border color

## Step 1: Create new dataset with duplicate rows.

rowind = seq(1,nrow(test_mpg)) # Create array of row indices
rowind2 = rep(rowind, each = 2, times = 1) # Create new array with doubled row indices (i.e. 1,2,3 becomes 1,1,2,2,3,3)

newdata = test_mpg[rowind2,] # Create new dataset with doubled rows
newdata$odd_row = seq_len(nrow(newdata)) %% 2 # Create column indicating even and odd rows. Odd rows = 1 and even rows = 0

## Step 2: Create columns with alternating aesthetics

# Create point size aesthetic column with alternating values

newdata$pointsize = NA # Create new column
newdata[newdata$odd_row == 1,]$pointsize = 2.0 # Set odd numbered rows to the desired outer point size. This is plotted behind the main point and determines the amount of secondary border visible.
newdata[newdata$odd_row == 0,]$pointsize = 1.5 # Set even numbered rows to the desired main point size

# Create point color aesthetic column with alternating values

newdata$pointcolor = NA # Create new column
newdata[newdata$odd_row == 1,]$pointcolor = "white" # Set odd numbered rows to the desired outer border color.
newdata[newdata$odd_row == 0,]$pointcolor = "black" # Set even numbered rows to the desired main point border color

## Step 3: Plot your data

# Plot your data with point size set to newdata$pointsize and point color set to newdata$pointcolor. These should be set outside of the aes()

test = ggplot(newdata, aes(displ, cty,  fill = drv, shape = drv)) +
  scale_fill_manual(values=c("green","blue","yellow")) +
  scale_shape_manual(values= c(23, 24, 25)) +
  geom_point(alpha = 1, size = newdata$pointsize, stroke = 0.75, color = newdata$pointcolor) +
  theme_bw()

test

Plot from above code

1 Answers

I am not at all sure that his is what you want, but the borders do not overlap.
The code below calls geom_point twice, what the question you link to tries to avoid.

suppressPackageStartupMessages({
  library(ggplot2)
  library(dplyr)
})

set.seed(2022)

mpg %>%
  mutate(displ = displ + runif(length(displ), min = 0, max = 0.5),
         cty = cty + runif(length(displ), min = 0, max = 5)) %>%
  ggplot(aes(displ, cty,  fill = drv, shape = drv)) +
  geom_point(size = 2, color = "white", fill = "white") +
  geom_point(size = 1.5, color = "black", stroke = 0.75) +
  scale_fill_manual(values = c("green", "blue", "yellow")) +
  scale_shape_manual(values = c(23, 24, 25)) +
  theme_bw()

Created on 2022-06-19 by the reprex package (v2.0.1)

Related