Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Implements @baptiste's much better method for making this work
library(ggplot2) ## devtools::install_github("hadley/ggplot2)
library(grid) ## rasterGrob
library(EBImage) ## readImage (alternatively: magick::image_read)
library(ggthemes) ## theme_minimal
## ##########
## INDEPENDENT CODE TO BE SOURCED:
## ##########
# user-level interface to the element grob
my_axis = function(img, angle = 90) {
structure(
list(img=img, angle=angle),
class = c("element_custom", "element_blank", "element_text", "element") # inheritance test workaround
)
}
# returns a gTree with two children: the text label, and a rasterGrob below
element_grob.element_custom <- function(element, x, ...) {
stopifnot(length(x) == length(element$img))
tag <- names(element$img)
# add vertical padding to leave space
g1 <- textGrob(paste0(tag, "\n\n\n\n\n"), x=x, rot = element$angle, vjust=0.6)
g2 <- mapply(rasterGrob, x=x, image=element$img[tag],
MoreArgs=list(vjust=0.7, interpolate=FALSE,
height=unit(3,"lines")),
SIMPLIFY=FALSE)
gTree(children=do.call(gList, c(g2, list(g1))), cl="custom_axis")
}
# gTrees don't know their size and ggplot would squash it, so give it room
grobHeight.custom_axis = heightDetails.custom_axis = function(x, ...)
unit(6, "lines")
## ##########
## END
## ##########
## ##########
## OBTAIN FLAGS:
## ##########
library(rvest)
## GDP per capita, top 10 countries
url <- "https://en.wikipedia.org/wiki/List_of_countries_by_GDP_(nominal)_per_capita"
html <- read_html(url)
gdppc <- html_table(html_nodes(html, "table")[3])[[1]][1:10,]
## clean up; remove non-ASCII and perform type conversions
gdppc$Country <- gsub("Â ", "", gdppc$Country)
gdppc$Rank <- iconv(gdppc$Rank, "latin1", "ASCII", sub="")
gdppc$Country <- iconv(gdppc$Country, "latin1", "ASCII", sub="")
gdppc$`US$` <- as.integer(sub(",", "", gdppc$`US$`))
## flag images (yes, this processing could be done neater, I'm sure)
## get the 200px versions
flags_img <- html_nodes(html_nodes(html, "table")[3][[1]], "img")[1:10]
flags_url <- paste0('http://', sub('[0-9]*px', '200px', sub('\\".*$', '', sub('^.*src=\\"//', '', flags_img))))
flags_name <- sub('.*(Flag_of)', '\\1', flags_url)
if(!dir.exists("flags")) dir.create("flags")
for(flag in seq_along(flags_url)) {
switch(Sys.info()[['sysname']],
Windows= {download.file(flags_url[flag], destfile=file.path("flags", paste0(flag,"_", flags_name[flag])), method="auto", mode="wb")},
Linux = {download.file(flags_url[flag], destfile=file.path("flags", paste0(flag,"_", flags_name[flag])))},
Darwin = {print("Not tested on Mac. Use one of the above and find out?")})
}
## ##########
## END
## ##########
## load the images from filenames
pics <- vector(mode="list", length=npoints)
image.file <- dir("flags", full.names=TRUE)
image.file <- image.file[order(as.integer(sub("_.*","",sub("flags/","",image.file))))]
for(i in 1:npoints) {
pics[[i]] <- magick::image_read(image.file[i])
# pics[[i]] <- EBImage::readImage(image.file[i])
}
names(pics) <- sub(".svg.png","",sub(".*Flag_of_","",image.file))
## create a dummy dataset
npoints <- length(flags_name)
y <- gdppc$`US$`
x <- names(pics)
dat <- data.frame(x=factor(x, levels=names(pics)), y=y)
## create the graph, as per normal now with @baptiste's adapted grob processing
## NB: #85bb65 is the color of money in the USA apparently.
gg <- ggplot(dat, aes(x=x, y=y/1e3L, group=1))
gg <- gg + geom_bar(col="black", fill="#85bb65", stat="identity")
gg <- gg + scale_x_discrete()
gg <- gg + theme_minimal()
gg <- gg + scale_fill_discrete(guide=FALSE)
gg <- gg + theme(plot.background = element_rect(fill="grey90"))
gg <- gg + labs(title="GDP per capita",
subtitle="Top 10 countries",
x="", y="$US/1000",
caption=paste0("Source: ",url))
gg <- gg + theme(axis.text.x = my_axis(pics, angle = 0), ## that's much better
axis.text.y = element_text(size=14),
axis.title.x = element_blank())
gg
@creativedoctor

This comment has been minimized.

Copy link

@creativedoctor creativedoctor commented Oct 17, 2018

How do you handle spacing between images and image size? Some of the flags on my laptop look jumbled up on top of each other (horizontally). I tried resizing with magick::image_resize, but if I keep the crop factor, when plotting the image the flags just enlarge again and makes the same thing - only more pixelated.

@loscialer

This comment has been minimized.

Copy link

@loscialer loscialer commented Sep 7, 2021

Hi Jon,
I was wondering if you have a solution to resize the images. I tried with magick::image_scale and image_resize but they did not work.

@jonocarroll

This comment has been minimized.

Copy link
Owner Author

@jonocarroll jonocarroll commented Sep 7, 2021

I'm not sure about the magick issues you're both having but I have an updated method for achieving this here: https://jcarroll.com.au/2019/08/13/ggtext-for-images-as-x-axis-labels/

@loscialer

This comment has been minimized.

Copy link

@loscialer loscialer commented Sep 7, 2021

Thank you very much for your quick reply.
However the new method does not work for me.
I tried to run your script but when i run the line
gdppc$Country <- gsub("Â ", "", gdppc$Country)
the following error appears
Error: Assigned data gsub("Â ", "", gdppc$Country) must be compatible with existing data.
x Existing data has 11 rows.
x Assigned data has 0 rows.
i Only vectors of size 1 are recycled.
Run rlang::last_error() to see where the error occurred.
In addition: Warning message:
Unknown or uninitialised column: Country.

In my case I have named list of 4 images and when i run
scale_x_discrete(name = NULL, labels = image_list_cor) I get the following erro
Error: gridtext has encountered a tag that isn't supported yet: pointer:
Only a very limited number of tags are currently supported.

Here is my code

Load images

`vp_cor <- image_read("C:/Users/jc448892/Desktop/Images/vp_cor.png")
p_cor <- image_read("C:/Users/jc448892/Desktop/Images/p_cor.png")
g_cor <- image_read("C:/Users/jc448892/Desktop/Images/g_cor.png")
vg_cor <- image_read("C:/Users/jc448892/Desktop/Images/vg_cor.png")

image_list_cor <- list(vg_cor, g_cor, p_cor,vp_cor)
names(image_list_cor) <- c("Very good", "Good", "Poor", "Very poor")

create a dummy dataset

status_cor <- pp_df %>% group_by(coral_image) %>% count
sum(status_cor$n)
status_cor <- status_cor %>% mutate(prop = round(n/73, 2))
y_cor <- status_cor$prop
x_cor <- names(image_list_cor)
dat_cor <- data.frame(x = factor(x_cor, levels=names(image_list_cor)), y=y_cor)

Make the plot

ggplot(dat_cor, aes(x= x, y=y)) +
geom_bar(col="black", fill="coral1", stat="identity") +
scale_x_discrete(name = NULL, labels = image_cor_list) +
theme(plot.background = element_rect(fill="grey90")) +
labs(title="What is the image the best represents the corals you see in the GBR?",
x="", y="Proportion") +
theme(axis.text.x = element_markdown(color = 'black, size = 7),
axis.text.y = element_text(size=14),
axis.title.x = element_blank())
`

@jonocarroll

This comment has been minimized.

Copy link
Owner Author

@jonocarroll jonocarroll commented Sep 7, 2021

I can't debug files from your desktop, but the Wikipedia table has changed so that code no longer applies. You'll need this change to make that part work

url      <- "https://en.wikipedia.org/wiki/List_of_countries_by_GDP_(nominal)_per_capita"
html     <- xml2::read_html(url)
gdppc    <- html_table(html_nodes(html, "table")[2])[[1]][2:12,]

## clean up; remove non-ASCII and perform type conversions
gdppc$Country <- sub(" (more)", "",  gdppc$`Country/Territory`, fixed = TRUE)
gdppc$Country <- gsub("Â ", "", gdppc$Country)
gdppc$Country <- iconv(gdppc$Country, "latin1", "ASCII", sub="")
gdppc$Country[10] <- "United States of America"
gdppc$Country[2] <- "Liechtenshein"
gdppc$`US$`   <- as.integer(sub(",", "", gdppc$`World Bank[5]`))

but even then, there's something wrong with the flag images and readPNG is complaining.

The idea of using element_markdown() should be solid, though.

@loscialer

This comment has been minimized.

Copy link

@loscialer loscialer commented Sep 7, 2021

Thank you for that
I m just not sure how to store my images that are not online but on a normal directory
Should I just put the file paths as values of the character vector labels ?
for example
labels <- setNames(c("C:/Users/jc448892/Desktop/Images/very_poor.png", "C:/Users/jc448892/Desktop/Images/poor.png", "C:/Users/jc448892/Desktop/Images/good.png", "C:/Users/jc448892/Desktop/Images/very_good.png"), c("Very good", "Good", "Poor", "Very poor"

Sorry I am pretty new to R

@loscialer

This comment has been minimized.

Copy link

@loscialer loscialer commented Sep 7, 2021

All good I managed to do it!
Thank you very much for your patience

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment