1

I'm trying to create a chart like the below image; essentailly its a filled shape denoting a percentage (most likely an image of a human, but this could be anything theortically).

I've managed to do this is Excel, albeit with a lot of botching of bar charts. But is there a way to do this in R, preferably using ggplot?

I've read the similar question Use an image as area fill in an R plot which doesn't quite do the same thing, but I cannot envisage a solution using this method.

Any help is appreciated!

EDIT:

As pointed out, this has been answered using plotly: Use a custom icon in plotly's pie chart. Is this possible to do with ggplot?

Example chart

Note I stole this image from a google image search for infographics.

Peter
  • 107
  • 1
  • 9
  • Thanks. Have edited to reflect just ggplot as an answer in this would be good, but the plotly solution is very nice to have! – Peter Jan 30 '18 at 13:33
  • If you specifically wanted ggplot, probably a better duplicate to close into is: [Use an image as area fill in an R plot](https://stackoverflow.com/questions/45777519/use-an-image-as-area-fill-in-an-r-plot) – smci May 17 '18 at 00:53

1 Answers1

4

You really only need to modify the plotting command from the answer that @PoGibas linked: here


library(png)
library(ggplot2)

genderselection <- read.table(text="
  Gender Freq
                              F   70
                              M   30
                              ", header=T)
pcts <- round(prop.table(genderselection$Freq)*100)

# Load png file from imgur as binary
con <- url("https://i.imgur.com/vFDSFYX.png",
           open='rb')
rawpng <- readBin(con, what='raw', n=50000)
close(con)

img <- readPNG(rawpng)
h <- dim(img)[1]
w <- dim(img)[2]

# Find the rows where feet starts and head ends
pos1 <- which(apply(img[,,1], 1, function(y) any(y==1)))
mn1 <- min(pos1)
mx1 <- max(pos1)
pospctM <- round((mx1-mn1)*pcts[2]/100+mn1)
pospctF <- round((mx1-mn1)*pcts[1]/100+mn1)

# Fill bodies with a different color according to percentages
# Note that this relies on the fact that the png is a bitmap.
# The png is expressed as a matrix with a cell for each pixel
# and 3 layers for r,g,b.
dim(img)
#> [1] 360 360   3

# Create a 2d matrix by just taking the red values
# Image is black and white so black corresponds to 0
# white corresponds to 1. Then change the values of
#  the cells to correspond to one of three categories.
imgmtx <- img[h:1,,1]
whitemtx <- (imgmtx==1)
colmtx <- matrix(rep(FALSE,h*w),nrow=h)
midpt <- round(w/2)-10
colmtx[mx1:pospctM,1:midpt] <- TRUE
colmtx[mx1:pospctF,(midpt+1):w] <- TRUE
imgmtx[whitemtx & colmtx] <- 0.5

# Need to melt the matrix into a data.frame that ggplot can understand
df <- reshape2::melt(imgmtx)
head(df)
#>   Var1 Var2 value
#> 1    1    1     0
#> 2    2    1     0
#> 3    3    1     0
#> 4    4    1     0
#> 5    5    1     0
#> 6    6    1     0

cols <- c(rgb(255,255,255,maxColorValue = 255),
          rgb(209,230,244,maxColorValue = 255), 
          rgb(42,128,183,maxColorValue = 255))

# Then use a heatmap with 3 colours for background, and percentage fills
# Converting the fill value to a factor causes a discrete scale.
# geom_tile takes three columns: x, y, fill corresponding to 
# x-coord, y-coord, and colour of the cell.
ggplot(df, aes(x = Var2, y = Var1, fill = factor(value)))+
  geom_tile() +
  scale_fill_manual(values = cols) +
  theme(legend.position = "none")

Eumenedies
  • 1,618
  • 9
  • 13