How to Insert Pictures into Each Individual Bar in a Ggplot Graph

How to insert pictures into each individual bar in a ggplot graph

You didn't provide a reprex, so I need to make something up. I would probably do it like this.

library(tidyverse)
library(ggtextures)
library(magick)
#> Linking to ImageMagick 6.9.9.39
#> Enabled features: cairo, fontconfig, freetype, lcms, pango, rsvg, webp
#> Disabled features: fftw, ghostscript, x11

data <- tibble(
count = c(5, 6, 6, 4, 2, 3),
animal = c("giraffe", "elephant", "horse", "bird", "turtle", "dog"),
image = list(
image_read_svg("http://steveharoz.com/research/isotype/icons/giraffe.svg"),
image_read_svg("http://steveharoz.com/research/isotype/icons/elephant.svg"),
image_read_svg("http://steveharoz.com/research/isotype/icons/horse.svg"),
image_read_svg("http://steveharoz.com/research/isotype/icons/bird.svg"),
image_read_svg("http://steveharoz.com/research/isotype/icons/turtle.svg"),
image_read_svg("http://steveharoz.com/research/isotype/icons/dog.svg")
)
)

ggplot(data, aes(animal, count, fill = animal, image = image)) +
geom_isotype_col(
img_height = grid::unit(1, "null"), img_width = NULL,
ncol = 1, nrow = 1, hjust = 1, vjust = 0.5
) +
coord_flip() +
guides(fill = "none") +
theme_minimal()

Sample Image

Created on 2019-11-03 by the reprex package (v0.3.0)

Inserting an image to a bar chart in ggplot

One option is to use ggimage. Source: https://guangchuangyu.github.io/pkgdocs/ggimage.html. Try this:

library(ggplot2)
library(ggimage)
library(dplyr)

set.seed(1234)

img <- list.files(system.file("extdata", package="ggimage"),
pattern="png", full.names=TRUE)

df = data.frame(
group = c('a', 'b', 'c'),
value = 1:3,
image = sample(img, size=3, replace = TRUE)
) %>%
mutate(value1 = .5 * value)

ggplot(df, aes(group, value)) +
geom_col() +
geom_image(aes(image=image, y = value1), size=.2)

Sample Image

Created on 2020-03-19 by the reprex package (v0.3.0)

Inserting an image to ggplot2

try ?annotation_custom in ggplot2

example,

library(png)
library(grid)
img <- readPNG(system.file("img", "Rlogo.png", package="png"))
g <- rasterGrob(img, interpolate=TRUE)

qplot(1:10, 1:10, geom="blank") +
annotation_custom(g, xmin=-Inf, xmax=Inf, ymin=-Inf, ymax=Inf) +
geom_point()

Making the images go over the barcharts in ggplot

The easy part is the axis text, e.g. you could add some labels for the groups via scale_x_discrete(labels = c("", "Bottom 3", "", "", "Top 3", "")). Another option would be to make use of faceting.

Concerning the positioning of the logos. I'm not sure whether this could be achieved via ggtextures. As far as I get it ggtextures is meant to add textures on column charts, which probably is the reason why one of the logos gets clipped off. Instead I would suggest to make use of the ggtext package which allows to add logos as text labels and gives you a lot of control on where to put the logos. One drawback is that ggtext does not work with svg so you first have to download the logos and convert them to png.

library(magick)
#> Linking to ImageMagick 6.9.12.3
#> Enabled features: cairo, fontconfig, freetype, heic, lcms, pango, raw, rsvg, webp
#> Disabled features: fftw, ghostscript, x11

# download svgs, convert to png and save
urls <- paste0(
"https://www.bundesliga.com/assets/clublogo/DFL-CLU-",
c("00000G", "000017", "000018", "000003", "000004", "000005"),
".svg"
)
lapply(urls, function(x) download.file(x, destfile = fs::path_file(x)))
fn <- fs::path_file(urls)
lapply(fn, function(x) image_write(image_convert(image_read_svg(x)), path = gsub("\\.svg", ".png", x), format = "png"))

library(tidyverse)
library(ggtext)

data <- tibble(
Diff_home_perc = c(-0.3, -0.2, -0.1, -0.05, 0.2, 0.3),
Team = c("A", "B", "C", "D", "E", "F"),
ind = c("Green", "Green", "Green", "Red", "Red", "Red"),
label = gsub("\\.svg", ".png", fs::path_file(fn)),
vjust = ifelse(Diff_home_perc < 0, 0, 1)
)
data$label <- paste0("<img src='", data$label, "' width = '40'/>")
data$vjust <- ifelse(abs(data$Diff_home_perc) < .1, 1 - data$vjust, data$vjust)
ggplot(data, aes(reorder(Team, Diff_home_perc), Diff_home_perc, fill = ind)) +
ylab("Ratio of points from home games") +
ylim(-0.3, 0.3) +
ylab("Difference") +
xlab("Team") +
geom_col(position = "dodge2") +
ggtext::geom_richtext(aes(label = label, vjust = vjust),
label.colour = NA, fill = NA, label.padding = unit(c(0, 0.25, -.3, 0.25), "lines")
) +
guides(fill = "none") +
theme_minimal() +
theme(
plot.title = element_blank(),
axis.title.x = element_blank(),
text = element_text(size = 15)
) +
scale_x_discrete(labels = c("", "Bottom 3", "", "", "Top 3", ""))

Sample Image

Is it possible to use a custom picture as the bars in a bar chart?

You can try

library(jpeg)
library(grid)
library(lattice)

#download Chrismas tree image which will be used as bar in barplot
download.file("https://upload.wikimedia.org/wikipedia/commons/f/fa/Poinsettia_tree.jpg", "Poinsettia_tree.jpg")
chrismas_tree <- readJPEG("Poinsettia_tree.jpg")

#sample data for barplot
counts <- table(mtcars$gear)

#barplot
barchart(counts, origin=0, col="white",horizontal = FALSE,
panel=function(x, y, ...) {
panel.barchart(x, y, ...)
grid.raster(chrismas_tree, y=0, height=y, x=x,
default.units="native",
just="bottom",
width=unit(0.2,"npc"))
},
ylab = "Counts",
xlab = "Gear",
main = "Gear counts plot (mtcars)")

Output plot

Add image background to ggplot barplot so that image is only visible inside of bars

This reminds me of a similar problem here, where the accepted solution used geom_ribbon() to provide the masking layer.

Going on a similar vein, since the mask needs to surround individual bars in this case, we are looking to create a polygon layer that handles holes gracefully. Last I checked, geom_polygon doesn't do so great, but geom_polypath from the ggpolypath package does.

Reproducible example, using the R logo as sample image & a built-in data frame:

library(ggplot2)
library(grid)
library(jpeg)

montage <- readJPEG(system.file("img", "Rlogo.jpg", package="jpeg"))
mont <- rasterGrob(montage, width = unit(1,"npc"),
height = unit(1,"npc"))

p <- ggplot(mpg, aes(x = class)) +
annotation_custom(mont, -Inf, Inf, -Inf, Inf) +
geom_bar(color = "black", fill = NA) +
coord_flip() +
theme_bw()

p

plot 1

Create a data frame of coordinates for the masking layer:

library(dplyr)
library(tidyr)

# convert the xmin/xmax/ymin/ymax values for each bar into
# x/y coordinates for a hole in a large polygon,
# then add coordinates for the large polygon

new.data <- layer_data(p, 2L) %>%

select(ymin, ymax, xmin, xmax) %>%
mutate(group = seq(1, n())) %>%
group_by(group) %>%
summarise(coords = list(data.frame(x = c(xmin, xmax, xmax, xmin),
y = c(ymin, ymin, ymax, ymax),
order = seq(1, 4)))) %>%
ungroup() %>%
unnest() %>%

rbind(data.frame(group = 0,
x = c(-Inf, Inf, Inf, -Inf),
y = c(-Inf, -Inf, Inf, Inf),
order = seq(1, 4)))

> new.data
# A tibble: 32 x 4
group x y order
<dbl> <dbl> <dbl> <int>
1 1 0.55 0 1
2 1 1.45 0 2
3 1 1.45 5 3
4 1 0.55 5 4
5 2 1.55 0 1
6 2 2.45 0 2
7 2 2.45 47 3
8 2 1.55 47 4
9 3 2.55 0 1
10 3 3.45 0 2
# ... with 22 more rows

Add the masking layer:

library(ggpolypath)

p +
geom_polypath(data = new.data,
aes(x = x, y = y, group = group),
inherit.aes = FALSE,
rule = "evenodd",
fill = "white", color = "black")

plot 2

p.s. The old adage "just because you can, doesn't mean you should" probably applies here...

How to put labels over geom_bar for each bar in R with ggplot2

Try this:

ggplot(data=dat, aes(x=Types, y=Number, fill=sample)) + 
geom_bar(position = 'dodge', stat='identity') +
geom_text(aes(label=Number), position=position_dodge(width=0.9), vjust=-0.25)

ggplot output



Related Topics



Leave a reply



Submit