Black border around certain points in ggplot?

I am plotting some data using ggplot2. The data has several different colors and a few different shapes. I want to put a black border only around circles and triangles, but leaving squares with no border.

Essentially I need something like 'scale_border_manual' if there is anything that exists like that. If I scale_shape_manual I am not able to control borders depending on a variable, I can only modify filled/unfilled.

Minimal reprex:

ggplot(mtcars, aes(x=mpg, y=cyl, color=as.character(carb), shape=as.character(gear))) +

How do I place black borders only around certain shapes?

Thanks for posting your question, this turned out to be a fun challenge! A couple of high level points. First, plotting symbols 21 through 25 allow you to set the interior color with the fill aesthetic and the border with the color aesthetic. I mapped the carb variable to the fill aesthetic. Sadly, although you can change the thickness of the border with the stroke aesthetic, there is no scale_stroke_manual function. I had hoped to map gear to stroke and set it equal to zero for 5 cylinder cars. Instead, my strategy was to map the number of gears to the color aesthetic. Then using scale_color_manual, I used the alpha argument of rgb to make the border of the squares transparent. Because that made the squares appear a bit smaller, I bumped up their size a smidge. Finally, you'll notice that I modified the appearance of the legends to make it clear that a circle in the shape legend wasn't the same as a circle in the fill legend.

I hope this is what you were after. Holler if you have any questions!


ggplot(mtcars, aes(x=mpg, y=cyl,
                   color=as.character(gear))) +
  geom_point() +
  scale_shape_manual(breaks=c(3, 4, 5),
                     values=c(21, 24, 22),
                     guide=guide_legend(override.aes = list(fill="black",
                                                            color="black"))) +
  scale_color_manual(breaks=c(3, 4, 5),
                     values=c("black", "black", rgb(0, 0, 0, alpha=0))) +
  scale_size_manual(breaks=c(3, 4, 5),
                    values=c(3, 3, 4)) +
  scale_fill_manual(breaks=c(1, 2, 3, 4, 6, 8),
                    guide=guide_legend(override.aes = list(color=rainbow(6),



FWIW I created a video describing the evolution of this response that is available on YouTube.




This was a fantastic work-around and your video has some wonderful insights. My dataset is much more complex than mtcars, so taking your approach and applying it to my data was a great way to go. Wonderful solution, thanks for this and the video!

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.